Is programmatically inverting the colors of an image possible?
Asked Answered
N

8

26

I want to take an image and invert the colors in iOS.

Nocuous answered 12/7, 2011 at 23:40 Comment(0)
R
44

To expand on quixoto's answer and because I have relevant source code from a project of my own, if you were to need to drop to on-CPU pixel manipulation then the following, which I've added exposition to, should do the trick:

@implementation UIImage (NegativeImage)

- (UIImage *)negativeImage
{
    // get width and height as integers, since we'll be using them as
    // array subscripts, etc, and this'll save a whole lot of casting
    CGSize size = self.size;
    int width = size.width;
    int height = size.height;

    // Create a suitable RGB+alpha bitmap context in BGRA colour space
    CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
    unsigned char *memoryPool = (unsigned char *)calloc(width*height*4, 1);
    CGContextRef context = CGBitmapContextCreate(memoryPool, width, height, 8, width * 4, colourSpace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colourSpace);

    // draw the current image to the newly created context
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), [self CGImage]);

    // run through every pixel, a scan line at a time...
    for(int y = 0; y < height; y++)
    {
        // get a pointer to the start of this scan line
        unsigned char *linePointer = &memoryPool[y * width * 4];

        // step through the pixels one by one...
        for(int x = 0; x < width; x++)
        {
            // get RGB values. We're dealing with premultiplied alpha
            // here, so we need to divide by the alpha channel (if it
            // isn't zero, of course) to get uninflected RGB. We
            // multiply by 255 to keep precision while still using
            // integers
            int r, g, b; 
            if(linePointer[3])
            {
                r = linePointer[0] * 255 / linePointer[3];
                g = linePointer[1] * 255 / linePointer[3];
                b = linePointer[2] * 255 / linePointer[3];
            }
            else
                r = g = b = 0;

            // perform the colour inversion
            r = 255 - r;
            g = 255 - g;
            b = 255 - b;

            // multiply by alpha again, divide by 255 to undo the
            // scaling before, store the new values and advance
            // the pointer we're reading pixel data from
            linePointer[0] = r * linePointer[3] / 255;
            linePointer[1] = g * linePointer[3] / 255;
            linePointer[2] = b * linePointer[3] / 255;
            linePointer += 4;
        }
    }

    // get a CG image from the context, wrap that into a
    // UIImage
    CGImageRef cgImage = CGBitmapContextCreateImage(context);
    UIImage *returnImage = [UIImage imageWithCGImage:cgImage];

    // clean up
    CGImageRelease(cgImage);
    CGContextRelease(context);
    free(memoryPool);

    // and return
    return returnImage;
}

@end

So that adds a category method to UIImage that:

  1. creates a clear CoreGraphics bitmap context that it can access the memory of
  2. draws the UIImage to it
  3. runs through every pixel, converting from premultiplied alpha to uninflected RGB, inverting each channel separately, multiplying by alpha again and storing back
  4. gets an image from the context and wraps it into a UIImage
  5. cleans up after itself, and returns the UIImage
Religieux answered 12/7, 2011 at 23:55 Comment(8)
Thanks for supplying this code. How could I get this code to work with retina devices? e.g. if (retinaScale>0.0) { UIGraphicsBeginImageContextWithOptions(image.size, NO, retinaScale); } else { UIGraphicsBeginImageContext(image.size); }Hughs
@Hughs there shouldn't be any difference between this on a retina device and a non-retina device — it operates directly on the UIImage, and the thing that has to deal with the actual practicalities of display is the UIImageView.Religieux
To make this code Retina-ready, use: int width = size.width * self.scale; int height = size.height * self.scale;Traylor
Hi Tommy, i need to set alpha color to this ..This code works gud Instead of white color i want to put transperent color so i guessed if we set alpha color may be it works .... Can u please suggest me how to do this ? Thanks in advance @ReligieuxStridulous
@Stridulous assuming I got the code right, the alpha value should be in linePointer[3] and you don't need to do any conversion on it or anything. The multiplies and divides on R, G and B are just because they're premultiplied by alpha — alpha itself isn't premultiplied by anything.Religieux
@VincentTourraine And also replace UIImage *returnImage = [UIImage imageWithCGImage:cgImage] with [UIImage imageWithCGImage:cgImage scale:self.scale orientation:UIImageOrientationUp]Classis
@Religieux Could you provide this in Swift?Calysta
How do I call this?Thionic
B
26

With CoreImage:

#import <CoreImage/CoreImage.h>

@implementation UIImage (ColorInverse)

+ (UIImage *)inverseColor:(UIImage *)image {
    CIImage *coreImage = [CIImage imageWithCGImage:image.CGImage];
    CIFilter *filter = [CIFilter filterWithName:@"CIColorInvert"];
    [filter setValue:coreImage forKey:kCIInputImageKey];
    CIImage *result = [filter valueForKey:kCIOutputImageKey];
    return [UIImage imageWithCIImage:result];
}

@end
Beetle answered 26/3, 2014 at 18:25 Comment(2)
Last line would be better as: [UIImage imageWithCIImage:result scale:image.scale orientation:image.imageOrientation]; so that scale and orientation of the original image are preserved.Shep
How to use this/call this?Thionic
P
6

Swift 3 update: (from @BadPirate Answer)

extension UIImage {
func inverseImage(cgResult: Bool) -> UIImage? {
    let coreImage = UIKit.CIImage(image: self)
    guard let filter = CIFilter(name: "CIColorInvert") else { return nil }
    filter.setValue(coreImage, forKey: kCIInputImageKey)
    guard let result = filter.value(forKey: kCIOutputImageKey) as? UIKit.CIImage else { return nil }
    if cgResult { // I've found that UIImage's that are based on CIImages don't work with a lot of calls properly
        return UIImage(cgImage: CIContext(options: nil).createCGImage(result, from: result.extent)!)
    }
    return UIImage(ciImage: result)
  }
}
Peggi answered 23/3, 2017 at 21:43 Comment(1)
yes, 100% without this cg fix, the image was not displaying when used as a Button image. thanksInsectivore
N
5

Sure, it's possible-- one way is using the "difference" blend mode (kCGBlendModeDifference). See this question (among others) for the outline of the code to set up the image processing. Use your image as the bottom (base) image, and then draw a pure white bitmap on top of it.

You can also do the per-pixel operation manually by getting the CGImageRef and drawing it into a bitmap context, and then looping over the pixels in the bitmap context.

Needs answered 12/7, 2011 at 23:49 Comment(3)
Please supply code that could replace @Tommy's answer (which I'm using now).Adytum
Great, nice solution.Classicize
Nice! This might be the easiest solution by far! Just used this to invert colors of a PDF file.Lutenist
C
5

Created a swift extension to do just this. Also because CIImage based UIImages break down (most libraries assume CGImage is set) I added an option to return a UIImage that is based on a modified CIImage:

extension UIImage {
    func inverseImage(cgResult: Bool) -> UIImage? {
        let coreImage = UIKit.CIImage(image: self)
        guard let filter = CIFilter(name: "CIColorInvert") else { return nil }
        filter.setValue(coreImage, forKey: kCIInputImageKey)
        guard let result = filter.valueForKey(kCIOutputImageKey) as? UIKit.CIImage else { return nil }
        if cgResult { // I've found that UIImage's that are based on CIImages don't work with a lot of calls properly
            return UIImage(CGImage: CIContext(options: nil).createCGImage(result, fromRect: result.extent))
        }
        return UIImage(CIImage: result)
    }
}
Cranberry answered 8/8, 2016 at 17:22 Comment(0)
B
3

Tommy answer is THE answer but I'd like to point out that could be a really intense and time consuming task for bigger images. There two frameworks that could help you in manipulating images:

  1. CoreImage
  2. Accelerator

    And it really worth to mention the amazing GPUImage framework from Brad Larson, GPUImage makes the routines run on the GPU using custom fragment shader in OpenGlES 2.0 environment, with remarkable speed improvement. With CoreImge if a negative filter is available you can choose CPU or GPU, using Accelerator all routines run on CPU but using vector math image processing.
Burdened answered 28/7, 2013 at 11:32 Comment(2)
Please supply code that could replace @Tommy's answer (which I'm using now).Adytum
Please, check here: developer.apple.com/library/mac/documentation/graphicsimaging/… and here: developer.apple.com/library/ios/documentation/Performance/… . On apple site you can also find some samplesBurdened
C
0

Updated to Swift 5 version of @MLBDG answer

extension UIImage {
    func inverseImage(cgResult: Bool) -> UIImage? {
        let coreImage = self.ciImage
        guard let filter = CIFilter(name: "CIColorInvert") else { return nil }
        filter.setValue(coreImage, forKey: kCIInputImageKey)
        guard let result = filter.value(forKey: kCIOutputImageKey) as? UIKit.CIImage else { return nil }
        if cgResult { // I've found that UIImage's that are based on CIImages don't work with a lot of calls properly
            return UIImage(cgImage: CIContext(options: nil).createCGImage(result, from: result.extent)!)
        }
        return UIImage(ciImage: result)
    }
}
Calotte answered 29/3, 2020 at 21:15 Comment(0)
L
0

I found this question while trying to invert the colors of a PDFDocument or a PDFPage.

For PDFs as well as UIImage it works pretty much as easy as @ben-zotto answered: Just use CGBlendMode.difference.

Absolutely no need to go through the image pixel by pixel.

My draw method (for a PDF) looks like below. You should be able to easily change this for your UIImage as both are using CGContext.

import PDFKit // Of course import UIKit in case you are inverting an image.

/// The following is for a `PDFPage`, for an image you could do pretty much the same.
class InvertedPDFPage: PDFPage {
    override func draw(with box: PDFDisplayBox, to context: CGContext) {
        super.draw(with: box, to: context)

        // These are the important two lines
        context.setBlendMode(.difference)
        context.fill(bounds(for: box))
    }
}

Off topic: PDF setup

// Set up the class above inside of your `PDFDocumentDelegate`
// and set it on your `PDFDocument`'s `delegate` property.

extension PDFViewController: PDFDocumentDelegate {
    func classForPage() -> AnyClass {
        return InvertedPDFPage.self
    }
}

It is based on this great answer on how to change a PDF's background color: https://mcmap.net/q/535464/-swift-pdfkit-change-background-color

Lutenist answered 12/7 at 7:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.