iphoneiosipaduiimage

Is programmatically inverting the colors of an image possible?


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


Solution

  • 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