I am trying to do a pixel by pixel comparison of two UIImages and I need to retrieve the pixels that are different. Using this Generate hash from UIImage I found a way to generate a hash for a UIImage. Is there a way to compare the two hashes and retrieve the different pixels?
If you want to actually retrieve the difference, the hash cannot help you. You can use the hash to detect the likely presence of differences, but to get the actual differences, you have to use other techniques.
For example, to create a UIImage that consists of the difference between two images, see this accepted answer in which Cory Kilgor's illustrates the use of CGContextSetBlendMode with a blend mode of kCGBlendModeDifference:
+ (UIImage *) differenceOfImage:(UIImage *)top withImage:(UIImage *)bottom {
CGImageRef topRef = [top CGImage];
CGImageRef bottomRef = [bottom CGImage];
// Dimensions
CGRect bottomFrame = CGRectMake(0, 0, CGImageGetWidth(bottomRef), CGImageGetHeight(bottomRef));
CGRect topFrame = CGRectMake(0, 0, CGImageGetWidth(topRef), CGImageGetHeight(topRef));
CGRect renderFrame = CGRectIntegral(CGRectUnion(bottomFrame, topFrame));
// Create context
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if(colorSpace == NULL) {
printf("Error allocating color space.\n");
return NULL;
}
CGContextRef context = CGBitmapContextCreate(NULL,
renderFrame.size.width,
renderFrame.size.height,
8,
renderFrame.size.width * 4,
colorSpace,
kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
if(context == NULL) {
printf("Context not created!\n");
return NULL;
}
// Draw images
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextDrawImage(context, CGRectOffset(bottomFrame, -renderFrame.origin.x, -renderFrame.origin.y), bottomRef);
CGContextSetBlendMode(context, kCGBlendModeDifference);
CGContextDrawImage(context, CGRectOffset(topFrame, -renderFrame.origin.x, -renderFrame.origin.y), topRef);
// Create image from context
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage * image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGContextRelease(context);
return image;
}
Related
I'm writing this Today Widget, that needs to display an image.
I noticed, that every time the Widget loads, the image is redrawn. This takes about half a second.
After some investigation, I found out, that the culprit is, that the image file is in the Indexed color space.
So: my question is:
How do I convert this file to something that the iPhone can display more efficiently? For instance, an RGB file. I would then save it to a new file, and load that new file in my UIImageView.
I played around a bit with CGImage, since I believe that is the solution direction, but I end up with a white UIImageView.
This is my code:
UIImage * theCartoon = [UIImage imageWithData:imageData];
CGImageRef si = [theCartoon CGImage];
CGDataProviderRef src = CGImageGetDataProvider(si);
CGImageRef imageRef = CGImageCreateWithPNGDataProvider(src, NULL, NO, kCGRenderingIntentDefault);
cartoon.image = [[UIImage alloc] initWithCGImage:imageRef];
Any suggestions on this approach? Some obvious misprogramming?
Try this
// The source image
CGImageRef image = theCartoon.CGImage;
CGSize size = CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image));
// The result image in RGB color space
CGImageRef result = nil;
// Check color space
CGColorSpaceRef srcColorSpace = CGImageGetColorSpace(image);
if (CGColorSpaceGetModel(srcColorSpace) != kCGColorSpaceModelRGB) {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(nil, size.width, size.height, 8, 0, colorSpace, kCGImageAlphaNoneSkipLast);
CGRect rect = {CGPointZero, size};
CGContextDrawImage(context, rect, image);
result = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
}
It's been a while since the question was asked, but for others who might need this, here is my solution:
-(UIImage *) convertIndexedColorSpaceToRGB:(UIImage *) sourceImage {
CGImageRef originalImageRef = sourceImage.CGImage;
const CGBitmapInfo originalBitmapInfo = CGImageGetBitmapInfo(originalImageRef);
// See: http://stackoverflow.com/questions/23723564/which-cgimagealphainfo-should-we-use
const uint32_t alphaInfo = (originalBitmapInfo & kCGBitmapAlphaInfoMask);
CGBitmapInfo bitmapInfo = originalBitmapInfo;
switch (alphaInfo)
{
case kCGImageAlphaNone:
bitmapInfo |= kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
break;
case kCGImageAlphaPremultipliedFirst:
case kCGImageAlphaPremultipliedLast:
case kCGImageAlphaNoneSkipFirst:
case kCGImageAlphaNoneSkipLast:
break;
case kCGImageAlphaOnly:
case kCGImageAlphaLast:
case kCGImageAlphaFirst:
{
return sourceImage;
}
break;
}
const CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
const CGSize pixelSize = CGSizeMake(sourceImage.size.width * sourceImage.scale, sourceImage.size.height * sourceImage.scale);
const CGContextRef context = CGBitmapContextCreate(NULL,
pixelSize.width,
pixelSize.height,
CGImageGetBitsPerComponent(originalImageRef),
pixelSize.width*4,
colorSpace,
bitmapInfo
);
CGColorSpaceRelease(colorSpace);
if (!context) return sourceImage;
const CGRect imageRect = CGRectMake(0, 0, pixelSize.width, pixelSize.height);
UIGraphicsPushContext(context);
// Flip coordinate system. See: http://stackoverflow.com/questions/506622/cgcontextdrawimage-draws-image-upside-down-when-passed-uiimage-cgimage
CGContextTranslateCTM(context, 0, pixelSize.height);
CGContextScaleCTM(context, 1.0, -1.0);
[sourceImage drawInRect:imageRect];
UIGraphicsPopContext();
const CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *image = [UIImage imageWithCGImage:decompressedImageRef scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
CGImageRelease(decompressedImageRef);
return image; }
I 've a PNG loaded into an UIImage. I want to get a portion of the image based on a path (i.e. it might not be a rectangular). Say, it might be some shape with arcs, etc. Like a drawing path.
What would be the easiest way to do that?
Thanks.
I haven't run this, so it may not be perfect but this should give you an idea.
UIImage *imageToClip = //get your image somehow
CGPathRef yourPath = //get your path somehow
CGImageRef imageRef = [imageToClip CGImage];
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, CGImageGetColorSpace(imageRef), kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextAddPath(context, yourPath);
CGContextClip(context);
CGImageRef clippedImageRef = CGBitmapContextCreateImage(context);
UIImage *clippedImage = [UIImage imageWithCGImage:clippedImageRef];//your final, masked image
CGImageRelease(clippedImageRef);
CGContextRelease(context);
The easiest way to add a category to the UIImage with follow method:
-(UIImage *)scaleToRect:(CGRect)rect{
// Create a bitmap graphics context
// This will also set it as the current context
UIGraphicsBeginImageContext(size);
// Draw the scaled image in the current context
[self drawInRect:rect];
// Create a new image from current context
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
// Pop the current context from the stack
UIGraphicsEndImageContext();
// Return our new scaled image
return scaledImage;
}
found this little code snippet that seems to do what i want, but im getting yelled at by xcode saying self.CGimage isnt a property of my view controller. (which makes sense since thats a UIimage property). What changes would i need to make to this code for it to be functional? Thanks!
- (UIImage*) maskImage:(UIImage *)image withMask:(UIImage *)maskImage {
CGContextRef mainViewContentContext;
CGColorSpaceRef colorSpace;
UIImage* tempImage;
colorSpace = CGColorSpaceCreateDeviceRGB();
// create a bitmap graphics context the size of the image
mainViewContentContext = CGBitmapContextCreate (NULL, image.size.width, image.size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
// free the rgb colorspace
CGColorSpaceRelease(colorSpace);
CGImageRef maskingImage = [maskImage CGImage];
CGContextClipToMask(mainViewContentContext, CGRectMake(0, 0, maskImage.size.width, maskImage.size.height), maskingImage);
CGContextDrawImage(mainViewContentContext, CGRectMake(0, 0, image.size.width, image.size.height), self.CGImage);
// Create CGImageRef of the main view bitmap content, and then
// release that bitmap context
CGImageRef mainViewContentBitmapContext = CGBitmapContextCreateImage(mainViewContentContext);
// convert the finished resized image to a UIImage
UIImage *theImage = [UIImage imageWithCGImage:mainViewContentBitmapContext];
// image is retained by the property setting above, so we can
// release the original
CGContextRelease(mainViewContentContext);
CGImageRelease(mainViewContentBitmapContext);
maskingImage = nil;
CGImageRelease(maskingImage);
// return the image
return theImage;
}
Try replacing self.CGImage with image.CGImage.
Place this method in a UIImage category (or subclass).
The last two lines of code below it's returning gives me a potential memory leak warning. .....Is this a true positive warning or false positive warning? If true, how do i fix it? Thanks a lot for your help!
-(UIImage*)setMenuImage:(UIImage*)inImage isColor:(Boolean)bColor
{
int w = inImage.size.width + (_borderDeep * 2);
int h = inImage.size.height + (_borderDeep * 2);
CGColorSpaceRef colorSpace;
CGContextRef context;
if (YES == bColor)
{
colorSpace = CGColorSpaceCreateDeviceRGB();
context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
}
else
{
colorSpace = CGColorSpaceCreateDeviceGray();
context = CGBitmapContextCreate(NULL, w, h, 8, w, colorSpace, kCGImageAlphaNone);
}
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGContextDrawImage(context, CGRectMake(_borderDeep, _borderDeep, inImage.size.width, inImage.size.height), inImage.CGImage);
CGImageRef image = CGBitmapContextCreateImage(context);
CGContextRelease(context); //releasing context
CGColorSpaceRelease(colorSpace); //releasing colorSpace
//// The two lines of code above caused Analyzer gives me a warning of potential leak.....Is this a true positive warning or false positive warning? If true, how do i fix it?
return [UIImage imageWithCGImage:image];
}
You're leaking the CGImage object (that's stored in your image variable). You can fix this by releasing the image after creating the UIImage.
UIImage *uiImage = [UIImage imageWithCGImage:image];
CGImageRelease(image);
return uiImage;
The reason for this is that CoreGraphics follows the CoreFoundation ownership rules; in this case, the "Create" rule. Namely, functions with "Create" (or "Copy") return an object that you are required to release yourself. So in this case, CGBitmapContextCreateImage() is returning a CGImageRef that you are responsible for releasing.
Incidentally, why aren't you using the UIGraphics convenience functions to create your context? Those will handle putting the right scale on the resulting UIImage. If you want to match your input image, you can do that as well
CGSize size = inImage.size;
size.width += _borderDeep*2;
size.height += _borderDeep*2;
UIGraphicsBeginImageContextWithOptions(size, NO, inImage.scale); // could pass YES for opaque if you know it will be
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
[inImage drawInRect:(CGRect){{_borderDeep, _borderDeep}, inImage.size}];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
You have to free CGImageRef you made. CGBitmapContextCreateImage has "create" in the name, which means (Apple is strict with its naming conventions) that you are responsible for freeing this memory.
Replace the last line with
UIImage *uiimage = [UIImage imageWithCGImage:image];
CGImageRelease(image);
return uiimage;
I would like to crop an image in iOS based on an irregularly shaped mask. How can I do this?
I'm not sure you want to crop, but instead you want to mask the image. This is pretty easy to do but you'll eventually find that it works for some images and not others. This is because you need to have the proper alpha channel in the image.
Here the code I use, which I got from stackoverflow. (Problem with transparency when converting UIView to UIImage)
CGImageRef CopyImageAndAddAlphaChannel(CGImageRef sourceImage) {
CGImageRef retVal = NULL;
size_t width = CGImageGetWidth(sourceImage);
size_t height = CGImageGetHeight(sourceImage);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef offscreenContext = CGBitmapContextCreate(NULL, width, height,
8, 0, colorSpace, kCGImageAlphaPremultipliedFirst);
if (offscreenContext != NULL) {
CGContextDrawImage(offscreenContext, CGRectMake(0, 0, width, height), sourceImage);
retVal = CGBitmapContextCreateImage(offscreenContext);
CGContextRelease(offscreenContext);
}
CGColorSpaceRelease(colorSpace);
return retVal;
}
- (UIImage*)maskImage:(UIImage *)image withMask:(UIImage *)maskImage {
CGImageRef maskRef = maskImage.CGImage;
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
CGImageGetHeight(maskRef),
CGImageGetBitsPerComponent(maskRef),
CGImageGetBitsPerPixel(maskRef),
CGImageGetBytesPerRow(maskRef),
CGImageGetDataProvider(maskRef), NULL, false);
CGImageRef sourceImage = [image CGImage];
CGImageRef imageWithAlpha = sourceImage;
//add alpha channel for images that don't have one (ie GIF, JPEG, etc...)
//this however has a computational cost
// needed to comment out this check. Some images were reporting that they
// had an alpha channel when they didn't! So we always create the channel.
// It isn't expected that the wheelin application will be doing this a lot so
// the computational cost isn't onerous.
//if (CGImageGetAlphaInfo(sourceImage) == kCGImageAlphaNone) {
imageWithAlpha = CopyImageAndAddAlphaChannel(sourceImage);
//}
CGImageRef masked = CGImageCreateWithMask(imageWithAlpha, mask);
CGImageRelease(mask);
//release imageWithAlpha if it was created by CopyImageAndAddAlphaChannel
if (sourceImage != imageWithAlpha) {
CGImageRelease(imageWithAlpha);
}
UIImage* retImage = [UIImage imageWithCGImage:masked];
CGImageRelease(masked);
return retImage;
}
And you call it like this:
customImage = [customImage maskImage:customImage withMask:[UIImage imageNamed:#"CircularMask.png"]];
In this case, I'm using a circular mask to make a circular image. You'll need to make the irregular mask that suits your needs.