Reputation: 3506
I want to take an image and invert the colors in iOS.
Upvotes: 28
Views: 24420
Reputation: 796
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))
}
}
// 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://stackoverflow.com/a/72711507/1898677
Upvotes: 0
Reputation: 101
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)
}
}
Upvotes: 0
Reputation: 1367
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)
}
}
Upvotes: 6
Reputation: 331
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
Upvotes: 27
Reputation: 26187
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)
}
}
Upvotes: 5
Reputation: 26383
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:
Upvotes: 3
Reputation: 100632
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:
Upvotes: 44
Reputation: 71048
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.
Upvotes: 5