Created
May 28, 2020 08:43
-
-
Save zrzka/50ec9e78fd2326c4d6af2ba40b3e802e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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