Skip to content

Instantly share code, notes, and snippets.

@zrzka
Created May 28, 2020 08:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zrzka/50ec9e78fd2326c4d6af2ba40b3e802e to your computer and use it in GitHub Desktop.
Save zrzka/50ec9e78fd2326c4d6af2ba40b3e802e to your computer and use it in GitHub Desktop.
//
// Copyright © 2020 Robert Vojta. All rights reserved.
//
#import "ViewController.h"
@interface UIView(ColorAtPoint)
- (UIColor *)colorAt:(CGPoint)point;
@end
@implementation UIView(ColorAtPoint)
- (UIColor *)colorAt:(CGPoint)point {
UIView *targetOpaqueView = self;
while (!targetOpaqueView.isOpaque && targetOpaqueView.superview != nil) {
targetOpaqueView = targetOpaqueView.superview;
}
CGPoint targetPoint = [self convertPoint:point toView:targetOpaqueView];
unsigned char pixel[4] = {0};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL) {
return nil;
}
CGContextRef context = CGBitmapContextCreate(pixel, 1, 1, 8, 4, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
if (context == NULL) {
CGColorSpaceRelease(colorSpace);
return nil;
}
CGContextTranslateCTM(context, -targetPoint.x, -targetPoint.y);
[targetOpaqueView.layer renderInContext:context];
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
UIColor *color = [UIColor colorWithRed:pixel[0]/255.0
green:pixel[1]/255.0
blue:pixel[2]/255.0
alpha:pixel[3]/255.0];
return color;
}
@end
@interface UIColor(ColorSpaceConversion)
- (UIColor *)convertedToColorSpace:(CGColorSpaceRef)colorSpace;
- (UIColor *)convertedToColorSpace:(CGColorSpaceRef)colorSpace inten:(CGColorRenderingIntent)intent;
@end
@implementation UIColor(ColorSpaceConversion)
- (UIColor *)convertedToColorSpace:(CGColorSpaceRef)colorSpace {
return [self convertedToColorSpace:colorSpace inten:kCGRenderingIntentDefault];
}
- (UIColor *)convertedToColorSpace:(CGColorSpaceRef)colorSpace inten:(CGColorRenderingIntent)intent {
CGColorRef converted = CGColorCreateCopyByMatchingToColorSpace(colorSpace, intent, self.CGColor, NULL);
if (converted == NULL) {
return nil;
}
UIColor *color = [[UIColor alloc] initWithCGColor:converted];
CGColorRelease(converted);
return color;
}
@end
@interface UIColor(EuclideanDistance)
- (CGFloat)euclideanDistanceTo:(UIColor *)other;
@end
@implementation UIColor(EuclideanDistance)
- (CGFloat)euclideanDistanceTo:(UIColor *)other {
CIColor *ciColor = [[CIColor alloc] initWithColor:self];
CIColor *ciColorOther = [[CIColor alloc] initWithColor:other];
if (ciColor.numberOfComponents != ciColor.numberOfComponents) {
NSException *exception = [NSException exceptionWithName:NSInvalidArgumentException
reason:@"Colors differ in numberOfComponents"
userInfo:nil];
@throw exception;
}
if (ciColor.alpha != 1.0 || ciColorOther.alpha != 1.0) {
NSException *exception = [NSException exceptionWithName:NSInvalidArgumentException
reason:@"Transparent colors are not supported"
userInfo:nil];
@throw exception;
}
CGFloat dr = ciColorOther.red - ciColor.red;
CGFloat dg = ciColorOther.green - ciColor.green;
CGFloat db = ciColorOther.blue - ciColor.blue;
return sqrt(dr * dr + dg * dg + db * db);
}
@end
@interface UIColor(Matching)
- (BOOL)matchesTo:(UIColor *)other;
- (BOOL)matchesTo:(UIColor *)other euclideanDistanceTolerance:(CGFloat)tolerance;
@end
@implementation UIColor(Matching)
- (BOOL)matchesTo:(UIColor *)other {
return [self matchesTo:other euclideanDistanceTolerance:0.01];
}
- (BOOL)matchesTo:(UIColor *)other euclideanDistanceTolerance:(CGFloat)tolerance {
CGFloat distance = [self euclideanDistanceTo:other];
return distance <= tolerance;
}
@end
@interface ViewController ()
@property (nonatomic, strong) IBOutlet UIImageView *imageView;
@property (nonatomic, strong) IBOutlet UIView *leftView;
@property (nonatomic, strong) IBOutlet UIView *rightView;
@end
@implementation ViewController
- (IBAction)handleTapGesture:(UITapGestureRecognizer *)sender {
CGPoint location = [sender locationInView:self.imageView];
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceDisplayP3);
if (colorSpace == NULL) {
return;
}
UIColor *assetsCatalogRedColor = [UIColor colorNamed:@"CustomRedColor"];
UIColor *redColor = [assetsCatalogRedColor convertedToColorSpace:colorSpace];
UIColor *tappedColor = [[self.imageView colorAt:location] convertedToColorSpace:colorSpace];
CGColorSpaceRelease(colorSpace);
self.leftView.backgroundColor = tappedColor;
self.rightView.backgroundColor = redColor;
if (redColor == nil || tappedColor == nil) {
return;
}
@try {
BOOL matches = [tappedColor matchesTo:redColor];
NSLog(@"Tapped color matches CustomRedColor: %@", matches ? @"true" : @"false");
}
@catch (NSException *exception) {
NSLog(@"Something went wrong: %@", exception);
}
}
@end
//
// Copyright © 2020 Robert Vojta. All rights reserved.
//
import UIKit
import CoreGraphics
extension UIView {
func color(at point: CGPoint) -> UIColor? {
var targetOpaqueView: UIView = self
// Traverse the view hierarchy to find a parent which is opaque
while !targetOpaqueView.isOpaque && targetOpaqueView.superview != nil {
targetOpaqueView = targetOpaqueView.superview!
}
// Convert the point from our view to the target one
let targetPoint: CGPoint = convert(point, to: targetOpaqueView)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
var pixel: [UInt8] = [0, 0, 0, 0]
guard let context = CGContext(data: &pixel, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else {
return nil
}
context.translateBy(x: -targetPoint.x, y: -targetPoint.y)
// Render the target opaque view to get all the possible transparency right
targetOpaqueView.layer.render(in: context)
return UIColor(red: CGFloat(pixel[0])/255.0, green: CGFloat(pixel[1])/255.0, blue: CGFloat(pixel[2])/255.0, alpha: CGFloat(pixel[3])/255.0)
}
}
extension UIColor {
func converted(toColorSpace colorSpace: CGColorSpace, intent: CGColorRenderingIntent = .defaultIntent) -> UIColor? {
guard let converted = cgColor.converted(to: colorSpace, intent: intent, options: nil) else {
return nil
}
return UIColor(cgColor: converted)
}
}
extension UIColor {
func euclideanDistance(to other: UIColor) -> CGFloat? {
let ciColor = CIColor(color: self)
let ciColorOther = CIColor(color: other)
guard ciColor.numberOfComponents == ciColorOther.numberOfComponents,
ciColor.alpha == 1.0, ciColorOther.alpha == 1.0 else {
return nil;
}
let dr = ciColorOther.red - ciColor.red
let dg = ciColorOther.green - ciColor.green
let db = ciColorOther.blue - ciColor.blue
return sqrt(dr * dr + dg * dg + db * db)
}
}
extension UIColor {
func matches(to other: UIColor, euclideanDistanceTolerance tolerance: CGFloat = 0.01) -> Bool? {
guard let distance = euclideanDistance(to: other) else {
return nil
}
return distance <= tolerance
}
}
class ViewController: UIViewController {
@IBOutlet var imageView: UIImageView!
@IBOutlet var leftView: UIView!
@IBOutlet var rightView: UIView!
private let assetsCatalogRedColor: UIColor = UIColor(named: "CustomRedColor")!
@IBAction
func handleTap(sender: UITapGestureRecognizer) {
let location = sender.location(in: imageView)
guard let colorSpace = CGColorSpace(name: CGColorSpace.displayP3),
let redColor = assetsCatalogRedColor.converted(toColorSpace: colorSpace, intent: .defaultIntent),
let tappedColor = imageView.color(at: location)?.converted(toColorSpace: colorSpace, intent: .defaultIntent) else {
return
}
let matches = tappedColor.matches(to: redColor) ?? false
print("Tapped color matches CustomRedColor: \(matches)")
leftView.backgroundColor = tappedColor
rightView.backgroundColor = redColor
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment