CGContextDrawImage inconsistent byte values across devices? - iphone

I'm trying to compare two images (actually locate a smaller "sub-image" in bigger image) and I'm loading the images using the method provided below.
The code below now contains a testing for-loop which sums up all the individual byte values. What I discovered is that this sum and therefor bytes, differ, depending on which device it is being run. My question is why is that happening ?
// Black and white configuration:
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
NSUInteger bytesPerPixel = 1;
NSUInteger bitsPerComponent = 8;
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
// Image
CGImageRef imageRef = [[UIImage imageNamed:#"image.jpg"] CGImage];
NSUInteger imageWidth = CGImageGetWidth(imageRef);
NSUInteger imageHeight = CGImageGetHeight(imageRef);
NSUInteger imageSize = imageHeight * imageWidth * bytesPerPixel;
NSUInteger imageBytesPerRow = bytesPerPixel * imageWidth;
unsigned char *imageRawData = calloc(imageSize, sizeof(unsigned char));
CGContextRef imageContext = CGBitmapContextCreate(imageRawData, imageWidth, imageHeight, bitsPerComponent,
imageBytesPerRow, colorSpace, bitmapInfo);
// Draw the actual image to the bitmap context
CGContextDrawImage(imageContext, CGRectMake(0, 0, imageWidth, imageHeight), imageRef);
CGContextRelease(imageContext);
NSUInteger sum = 0;
for (int byteIndex = 0; byteIndex < imageSize; byteIndex++)
{
sum += imageRawData[byteIndex];
}
NSLog(#"Sum: %i", sum); // Output on simulator: Sum: 18492272
// Output on iPhone 3GS: Sum: 18494036
// Output on another 3GS: Sum: 18494015
// Output on iPhone 4: Sum: 18494015
free(imageRawData);
CGColorSpaceRelease(colorSpace);

Are the devices all running the same version of the OS? Another possibility (beyond colorspaces, which someone already mentioned) is that the JPG decoding libraries may be subtly different. As JPEG is a lossy image format, it's not inconceivable that different decoders would produce resulting bitmaps that were not bit-equal. It's seems reasonable to posit that, given the heavy use of images in iOS UI, that the JPG decoder is something that would be undergoing constant tuning for maximum performance.
I'd even believe it conceivable that between the same OS version running on different models of device (i.e. different processors), the results could be not bit-equal if there were multiple versions of the JPG decoder, each heavily optimized for a specific CPU, although that would not explain the difference between 2 devices of the same model, with the same OS.
You might try to re-run the experiment with an image in a lossless format.
It also may be worth pointing out that providing your own backing memory for a CGBitmapContext, but not making special allowances for word alignment is likely to lead to poor performance. For instance, you have:
NSUInteger imageBytesPerRow = bytesPerPixel * imageWidth;
If imageBytesPerRow is not a multiple of the CPU's native word length, you're going to get sub-optimal performance.

I assume the "device grey" color space varies by device. Try with a device independent color space.

Related

CGBitmapContextCreate and interlaced? image

I'm converting some image drawing code from Cairo to Quartz and I'm slowly making progress and learning Quartz along the way but I've run into a problem with the image format.
In the Cairo version it works like this:
unsigned short *d = (unsigned short*)imageSurface->get_data();
int stride = imageSurface->get_stride() >> 1;
int height = imageHeight;
int width = imageWidth;
do {
d = *p++; // p = raw image data
width --;
if( width == 0 ) {
height --;
width = imageWidth;
d += stride;
}
} while( height );
Now this produces the image as expected on the Cairo::ImageSurface. I've converted this over to how use Quartz and it is making progress but I'm not sure where I'm going wrong:
NSInteger pixelLen = (width * height) * 8;
unsigned char *d = (unsigned char*)malloc(pixelLen);
unsigned char *rawPixels = d;
int height = imageHeight;
int width = imageWidth;
do {
d = *p++; // p = raw image data
width --;
if( width == 0 ) {
height --;
width = imageWidth;
}
} while( height );
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(rawPixels, imageWidth, imageHeight, 8, tileSize * sizeof(int), colorSpace, kCGBitmapByteOrderDefault);
CGImageRef image = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
UIImage *resultUIImage = [UIImage imageWithCGImage:image];
CGImageRelease(image);
Now this is obviously heading in the right direction as it produces something that looks a bit like the desired image but it creates 4 copies of the image in a row, each with different pixels filled in so I'm assuming this is an interlaced image (I don't know a great deal about image formats) and that I need to somehow combine them somehow to create a complete image but I don't know how to do that with Quartz.
I think the stride has something to do with the problem but from what I understand this is the byte distance from one row of pixels to another which would not be relevant in the context of Quartz?
It sounds like stride would correspond to rowBytes or bytesPerRow. This value is important because it is not necessarily equal to width * bytesPerPixel because rows might be padded to optimized offsets.
It's not completely obvious what the Cairo code is doing, and it doesn't look quite correct either. Either way, without the stride part, your loop makes no sense because it makes an exact copy of the bytes.
The loop in the Cairo code is copying a row of bytes, then jumping over the next row of data.

iPhone App Green Screen Replacement

Q: I'm looking to use the iPhone camera to take a photo and then replace the green screen in that photo with another photo.
What's the best way to dive into this? I couldn't find many resources online.
Thanks in advance!
Conceptually, all that you need to do is loop through the pixel data of the photo taken by the phone, and for each pixel that is not within a certain range of green, copy the pixel into the same location on your background image.
Here is an example I modified from keremic's answer to another stackoverflow question.
NOTE: This is untested and just intended to give you an idea of a technique that will work
//Get data into C array
CGImageRef image = [UIImage CGImage];
NSUInteger width = CGImageGetWidth(image);
NSUInteger height = CGImageGetHeight(image);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel_ * width;
NSUInteger bitsPerComponent = 8;
unsigned char *data = malloc(height * width * bytesPerPixel);
// you will need to copy your background image into resulting_image_data.
// which I am not showing here
unsigned char *resulting_image_data = malloc(height * width * bytesPerPixel);
CGContextRef context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height));
CGContextRelease(context);
//loop through each pixel
for(int row = 0; row < height; row++){
for(int col = 0; col < width*bytesPerPixel; col=col+4){
red = data[col];
green = data[col + 1];
blue = data[col + 2];
alpha = data[col + 3];
// if the pixel is within a shade of green
if(!(green > 250 && red < 10 && blue < 10)){
//copy them over to the background image
resulting_image_data[row*col] = red;
resulting_image_data[row*col+1] = green;
resulting_image_data[row*col+2] = blue;
resulting_image_data[row*col+3] = alpha;
}
}
}
//covert resulting_image_data into a UIImage
Have a look at compiling OpenCV for iPhone - not an easy task, but it gives you access to a whole library of really great image processing tools.
I'm using openCV for an app I'm developing at the moment (not all that dissimilar to yours) - for what you're trying to do, openCV would be a great solution, although it requires a bit of learning etc. Once you've got OpenCV working, the actual task of removing green shouldn't be too hard.
Edit: This link will be a helpful resource if you do decide to use OpenCV: Compiling OpenCV for iOS

Creating UIImage from raw RGBA data

I have been trying to convert an array RGBA data (int bytes) into a UIImage. My code looks like as follows:
/*height and width are integers denoting the dimensions of the image*/
unsigned char *rawData = malloc(width*height*4);
for (int i=0; i<width*height; ++i)
{
rawData[4*i] = <red_val>;
rawData[4*i+1] = <green_val>;
rawData[4*i+2] = <blue_val>;
rawData[4*i+3] = 255;
}
/*I Have the correct values displayed
- ensuring the rawData is well populated*/
NSLog(#"(%i,%i,%i,%f)",rawData[0],rawData[1],rawData[2],rawData[3]/255.0f);
NSLog(#"(%i,%i,%i,%f)",rawData[4],rawData[5],rawData[6],rawData[7]/255.0f);
NSLog(#"(%i,%i,%i,%f)",rawData[8],rawData[9],rawData[10],rawData[11]/255.0f);
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL,
rawData,
width*height*4,
NULL);
int bitsPerComponent = 8;
int bitsPerPixel = 32;
int bytesPerRow = 4*width;
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef imageRef = CGImageCreate(width,
height,
8,
32,
4*width,colorSpaceRef,
bitmapInfo,
provider,NULL,NO,renderingIntent);
/*I get the current dimensions displayed here */
NSLog(#"width=%i, height: %i", CGImageGetWidth(imageRef),
CGImageGetHeight(imageRef) );
UIImage *newImage = [UIImage imageWithCGImage:imageRef];
/*This is where the problem lies.
The width, height displayed are of completely different dimensions
viz. the width is always zero and the height is a very huge number */
NSLog(#"resultImg width:%i, height:%i",
newImage.size.width,newImage.size.height);
return newImage;
The output image that I receive is an image of width 0, and height 1080950784 (assuming my initil height and width were 240 and 240). I have been trying to get this sorted out and have checked many related forums e.g. (link text) on how to go about it but with little success.
It turns out the problem is a pretty silly mistake that both of us overlooked. UIImage dimensions are stored as floats, not integers. :D
Try
NSLog(#"resultImg width:%f, height:%f",
newImage.size.width,newImage.size.height);
Instead. The image size has been transferred correctly.
The problem is solved now. I get the image that I want displayed but still unable to figure out why the width and height are different. Essentially nothing wrong with the program per say. The only problem being width and height.
I just independently verified this problem, I think it should be reported as a bug to Apple. +(UIImage)imageWithCGImage: doesn't properly transfer the width and height of the source CGImageRef to the UIImage.

Reading and editing pixels of image on iPhone

Curious about how to read and edit a picture's pixels on the iPhone. Am I better of using an array of points with colours?
I want to do things like.. if a CGPoint intersects with a "brown" spot on the picture, set the colour of all brown pixels in a radius to white. More questions to come, but this is a start.
Cheers
The picture data is available to you as precisely that -- a two-dimensional array of pixels, each pixel being represented by a 32 bit integer. For each of the color components (red, green, blue, and alpga) there is an 8 bit value. The ordering of these 8-bit-wide values within the 32 bit integer varies with the format of the picture data. The apple doc about all this is really good. While there is some attractive Apple stuff using CGDataProviderCopyData to give you a pointer into the actual data storage of a UIImage, in practice this can be a headache, because the format of that internal storage can vary widely from one image to the next. In practice, most people doing image processing seem to use this approach:
CGImageRef image = [UIImage CGImage];
NSUInteger width = CGImageGetWidth(image);
NSUInteger height = CGImageGetHeight(image);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData_ = malloc(height * width * 4);
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel_ * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height));
CGContextRelease(context);
// rawData contains image data in the RGBA8888 format.
// for any pixel at coordinate x,y -- the value is
//
int pixelIndex = (bytesPerRow * y) + x * bytesPerPixel;
unsigned char red = rawData[pixelIndex];
green = rawData[pixelIndex + 1];
blue = rawData[pixelIndex + 2];
alpha = rawData[pixelIndex + 3];

Create a mask from difference between two images (iPhone)

How can I detect the difference between 2 images, creating a mask of the area that's different in order to process the area that's common to both images (gaussian blur for example)?
EDIT: I'm currently using this code to get the RGBA value of pixels:
+ (NSArray*)getRGBAsFromImage:(UIImage*)image atX:(int)xx andY:(int)yy count:(int)count
{
NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
// First get the image into your data buffer
CGImageRef imageRef = [image CGImage];
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = malloc(height * width * 4);
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
// Now your rawData contains the image data in the RGBA8888 pixel format.
int byteIndex = (bytesPerRow * yy) + xx * bytesPerPixel;
for (int ii = 0 ; ii < count ; ++ii)
{
CGFloat red = (rawData[byteIndex] * 1.0) / 255.0;
CGFloat green = (rawData[byteIndex + 1] * 1.0) / 255.0;
CGFloat blue = (rawData[byteIndex + 2] * 1.0) / 255.0;
CGFloat alpha = (rawData[byteIndex + 3] * 1.0) / 255.0;
byteIndex += 4;
UIColor *acolor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
[result addObject:acolor];
}
free(rawData);
return result;
}
The problem is, the images are being captured from the iPhone's camera so they are not exactly the same position. I need to create areas of a couple of pixels and extracting the general color of the area (maybe by adding up the RGBA values and dividing by the number of pixels?). How could I do this and then translate it to a CGMask?
I know this is a complex question, so any help is appreciated.
Thanks.
I think the simplest way to do this would be to use a difference blend mode. The following code is based on code I use in CKImageAdditions.
+ (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;
}
There are three reasons pixels will change from one iPhone photo to the next, the subject changed, the iPhone moved, and random noise. I assume for this question, you're most interested in the subject changes, and you want to process out the effects of the other two changes. I also assume the app intends the user to keep the iPhone reasonably still, so iPhone movement changes are less significant than subject changes.
To reduce the effects of random noise, just blur the image a little. A simple averaging blur, where each pixel in the resulting image is an average of the original pixel with its nearest neighbors should be sufficient to smooth out any noise in a reasonably well lit iPhone image.
To address iPhone movement, you can run a feature detection algorithm on each image (look up feature detection on Wikipedia for a start). Then calculate the transforms needed to align the least changed detected features.
Apply that transform to the blurred images, and find the difference between the images. Any pixels with a sufficient difference will become your mask. You can then process the mask to eliminate any islands of changed pixels. For example, a subject may be wearing a solid colored shirt. The subject may move from one image to the next, but the area of the solid colored shirt may overlap resulting in a mask with a hole in the middle.
In other words, this is a significant and difficult image processing problem. You won't find the answer in a stackoverflow.com post. You will find the answer in a digital image processing textbook.
Can't you just subtract pixel values from the images, and process pixels where the difference i 0?
Every pixel which does not have a suitably similar pixel in the other image within a certain radius can be deemed to be part of the mask. It's slow, (though there's not much that would be faster) but it works fairly simply.
Go through the pixels, copy the ones that are different in the lower image to a new one (not opaque).
Blur the upper one completely, then show the new one above.