37

I am trying to develop a small application. In it, I need to detect the color of a pixel within an UIView. I have a CGPoint defining the pixel I need. The colors of the UIView are changed using CoreAnimation.

I know there are some complex ways to extract color information from UIImages. However I couldn't find a solution for UIViews.

In Pseudo-Code I am looking for something like

pixel = [view getPixelAtPoint:myPoint];
UIColor *mycolor = [pixel getColor];

Any input greatly appreciated.

balpha
  • 50,022
  • 18
  • 110
  • 131
0x90
  • 6,079
  • 2
  • 36
  • 55

5 Answers5

81

Here is more efficient solution:

// UIView+ColorOfPoint.h
@interface UIView (ColorOfPoint)
- (UIColor *) colorOfPoint:(CGPoint)point;
@end

// UIView+ColorOfPoint.m
#import "UIView+ColorOfPoint.h"
#import <QuartzCore/QuartzCore.h>

@implementation UIView (ColorOfPoint)

- (UIColor *) colorOfPoint:(CGPoint)point
{
    unsigned char pixel[4] = {0};

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    CGContextRef context = CGBitmapContextCreate(pixel, 1, 1, 8, 4, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast);

    CGContextTranslateCTM(context, -point.x, -point.y);

    [self.layer renderInContext:context];

    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);

    //NSLog(@"pixel: %d %d %d %d", pixel[0], pixel[1], pixel[2], pixel[3]);

    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

Link to files: https://github.com/ivanzoid/ikit/tree/master/UIView+ColorOfPoint

ivanzoid
  • 5,952
  • 2
  • 34
  • 43
  • Warning in .m, removed with `#import ` – Jonny Dec 08 '11 at 16:18
  • It looks like you are rendering just a single pixel. Very cool. – mahboudz Jan 22 '12 at 19:04
  • Can this code be modified to work out the color of a whole row of pixels? – Thomas Clayson Feb 12 '13 at 11:40
  • Could you explain CGContextTranslateCTM(context, -point.x, -point.y);? – Raphael Oliveira Aug 15 '13 at 20:04
  • Just a comment, this is only "more efficient" if your renderInContext isn't particularly expensive. If you're eg. rendering from a PDF then caching an image and testing that will be more efficient. – Mark Oct 20 '13 at 11:33
  • @RaphaelOliveira the CGContextTranslate moves the potential pixel to position 0,0 so that it will be the only pixel rendered – jjxtra Dec 09 '13 at 01:17
  • cast to `(CGBitmapInfo)kCGImageAlphaPremultipliedFirst` to remove compile warning – beryllium Mar 17 '14 at 18:34
  • 1
    @ivanzoid I've added a swift translation. Feel free to copy – Warren Burton Nov 15 '14 at 12:01
  • Hi I'm trying to do the same but its picked color with some mixing with the background color, even if I have set the background to nill, how can I remove blending? I have tried all the mode available context?.setBlendMode(CGBlendMode.), can you check, please https://stackoverflow.com/questions/51192147/color-picking-color-is-not-exact-after-picking-getting-some-black-channel-or-a – Iraniya Naynesh Jul 12 '18 at 09:36
25

A swift'ified version of ivanzoid's answer

Swift 3

extension CALayer {

    func colorOfPoint(point:CGPoint) -> CGColor {

        var pixel: [CUnsignedChar] = [0, 0, 0, 0]

        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)

        let context = CGContext(data: &pixel, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)

        context!.translateBy(x: -point.x, y: -point.y)

        self.render(in: context!)

        let red: CGFloat   = CGFloat(pixel[0]) / 255.0
        let green: CGFloat = CGFloat(pixel[1]) / 255.0
        let blue: CGFloat  = CGFloat(pixel[2]) / 255.0
        let alpha: CGFloat = CGFloat(pixel[3]) / 255.0

        let color = UIColor(red:red, green: green, blue:blue, alpha:alpha)

        return color.cgColor
    }
}

Swift 2

extension CALayer {

    func colorOfPoint(point:CGPoint)->CGColorRef
    {
        var pixel:[CUnsignedChar] = [0,0,0,0]

        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue)

        let context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, colorSpace, bitmapInfo.rawValue)

        CGContextTranslateCTM(context, -point.x, -point.y)

        self.renderInContext(context!)

        let red:CGFloat = CGFloat(pixel[0])/255.0
        let green:CGFloat = CGFloat(pixel[1])/255.0
        let blue:CGFloat = CGFloat(pixel[2])/255.0
        let alpha:CGFloat = CGFloat(pixel[3])/255.0

        let color = UIColor(red:red, green: green, blue:blue, alpha:alpha)

        return color.CGColor
    }

}

based around the underlying CALayer but easily translatable back to UIView.

Riad Krim
  • 944
  • 1
  • 10
  • 23
Warren Burton
  • 17,451
  • 3
  • 53
  • 73
  • 4
    i tried to use it but it returns just zeros. so i always get white color. unfortunately my programming skills are not so advanced to find out myself. thank you – Lachtan Jun 10 '16 at 17:07
11

Swift 4 & 4.2. Tested with XCode 10, iOS 12, Simulator & iPhone 6+ Running iOS 12.1.

This code work fine.

extension UIView {
    func colorOfPoint(point: CGPoint) -> UIColor {
        let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)

        var pixelData: [UInt8] = [0, 0, 0, 0]

        let context = CGContext(data: &pixelData, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)

        context!.translateBy(x: -point.x, y: -point.y)

        self.layer.render(in: context!)

        let red: CGFloat = CGFloat(pixelData[0]) / CGFloat(255.0)
        let green: CGFloat = CGFloat(pixelData[1]) / CGFloat(255.0)
        let blue: CGFloat = CGFloat(pixelData[2]) / CGFloat(255.0)
        let alpha: CGFloat = CGFloat(pixelData[3]) / CGFloat(255.0)

        let color: UIColor = UIColor(red: red, green: green, blue: blue, alpha: alpha)

        return color
    }
}
Wo_0NDeR ᵀᴹ
  • 563
  • 5
  • 16
8

It is pretty horrible and slow. Basically you create a bitmap context with a backing store you allocate so you can read the memory, then you render the views layer in the context and read the appropriate point in ram.

If you know how to do it for an image already you can do something like this:

- (UIImage *)imageForView:(UIView *)view {
  UIGraphicsBeginImageContext(view.frame.size);
  [view.layer renderInContext: UIGraphicsGetCurrentContext()];
  UIImage *retval = UIGraphicsGetImageFromCurrentImageContext(void);
  UIGraphicsEndImageContext();

  return retval;
}

And then you will get an image where you can get the pixel data. I am sure the mechanism you have for dealing with images involves rendering them into a context anyway, so you can merge that with this and factor out the actual image creation. So if you take that could and remove the bit where you load the image and replace it with the context render:

[view.layer renderInContext: UIGraphicsGetCurrentContext()];

you should be good.

Collin Price
  • 5,620
  • 3
  • 33
  • 35
Louis Gerbarg
  • 43,356
  • 8
  • 80
  • 90
2

Fixed some minor errors

- (UIImage *)imageForView:(UIView *)view {
    UIGraphicsBeginImageContext(view.frame.size);
    [view.layer renderInContext: UIGraphicsGetCurrentContext()];
    UIImage *retval = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return retval;
}

Also, add

#import <QuartzCore/QuartzCore.h>  

to your file to avoid warning messages.

Combining your code with the code found here works flawlessly.

0x90
  • 6,079
  • 2
  • 36
  • 55
  • Why does it look like you simply copied Louis's code and removed the superfluous "void" in the call to `UIGraphicsGetImageFromCurrentImageContext()`? – Lily Ballard Oct 28 '10 at 04:58
  • 1
    That is what I did. What is a better way to handle corrections on here? – 0x90 Oct 28 '10 at 21:33