4

Original image: enter image description here Filtered image: enter image description here

I am trying to crop UIImages (photos in a phone's camera roll) into squares. Here is part of the code I am using, where 'image' is the image that is being cropped:

if( image.size.height > image.size.width )
{
    dimension = image.size.width;
    imageRef = CGImageCreateWithImageInRect([image CGImage], CGRectMake((image.size.height-dimension)/2, 0, dimension, dimension));

If I am using the original image, it looks like this at this point: enter image description here

Which is fine and what I expect - I have a rotation algorithm not shown here that sorts this out.

If I'm using the filtered image, it looks like this: enter image description here

...not square cropped, but weirdly zoomed in instead. So this seems to be where the problem lies, and I don't know why these filtered images are behaving differently.

}
else
{
    dimension = image.size.height;
    imageRef = CGImageCreateWithImageInRect([image CGImage], CGRectMake((image.size.width-dimension)/2, 0, dimension, dimension));
}

CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
CGColorSpaceRef colorSpaceInfo = CGImageGetColorSpace(imageRef);
CGContextRef bitmap;

bitmap = CGBitmapContextCreate(NULL, dimension, dimension, CGImageGetBitsPerComponent(imageRef), CGImageGetBytesPerRow(imageRef), colorSpaceInfo, bitmapInfo);

My problem is that at that last line, CGBitmapContextCreate, I sometimes get the following error:

<Error>: CGBitmapContextCreate: invalid data bytes/row: should be at least 7744 for 8 integer bits/component, 3 components, kCGImageAlphaNoneSkipLast.

What's odd is that usually, this doesn't happen - so far, I've only encountered this error when the original image has height greater than width, and it has been filtered by another app called Camera+...the exact same photo before filtering causes no problems, and a filtered landscape photo also seems to be fine.

Can anyone guide me here or help me explain what's actually going on? I understand enough from the error message to know that if I replace CGImageGetBytesPerRow(imageRef) with some arbitrary number higher than 7744, the error no longer happens, but I don't understand enough about this CGImage stuff to know what effect that's actually having on anything, and it doesn't seem to me like much of an actual solution. This code is based on other cropping examples I've seen on the web, and so my understanding of these bitmap functions is limited.

Any thoughts would be hugely appreciated!

EDIT

I found this question on SO: In this CGBitmapContextCreate, why is bytesPerRow 0? and it prompted me to try setting the bytesPerRow parameter to 0. Turns out this eliminates the error, but my cropping routine doesn't work properly in the same situations as when this error was occurring before. This might take a special person to answer, but does anyone know enough about image filtering to take a guess at why portrait-oriented, camera+ filtered photos are somehow being treated differently by this code? I've updated the title since the question has changed slightly.

EDIT2

I've added example images into the code above, and in the end, after any necessary rotating, the final cropped images look like this:

with original image: enter image description here - perfect!

with filtered image: enter image description here - terrible!

The code that's used to create these final, supposedly cropped images is this:

CGContextDrawImage(bitmap, CGRectMake(0, 0, dimension, dimension), imageRef);
CGImageRef ref = CGBitmapContextCreateImage(bitmap);
image = [UIImage imageWithCGImage:ref];
UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
Community
  • 1
  • 1
cowfaboo
  • 709
  • 1
  • 8
  • 15
  • Can you post an example of the before and after filtered images? – Adam B Aug 10 '12 at 21:07
  • Image examples are now posted - the second set of images is where the bizarre behaviour is occurring. Any insight you have would be much appreciated. – cowfaboo Aug 14 '12 at 16:19

2 Answers2

2

In short - camera+ filtered images, for some reason, are coming in with a different imageOrientation value than the original images. Somehow, this fact causes a line like this:

imageRef = CGImageCreateWithImageInRect([image CGImage], CGRectMake((image.size.height-dimension)/2, 0, dimension, dimension));

to behave differently depending on the imageOrientation of image. So, while the original image, whose image orientation was either right or left, was being rotated onto its side by this line (which is why I was cropping with an x offset in both portrait and landscape sizes), the filtered image's orientation is up. Because of this, I wasn't getting the rotation I expected, and so the filtered images were getting stretched. To solve this, I am checking the orientation of the image before calling CGImageCreateWithImageInRect, and if it is portrait-sized but has an up orientation, I crop with a y offset instead of x (like the line of code David H mentions below).

My guess is that calling [image CGImage] rotates the image to a relative up position...so if the orientation is right, the image gets rotated 90 degrees counter-clockwise, but if the orientation is up, it doesn't get rotated at all. I still don't understand why filtered images end up with a different orientation than their originals, but I assume it is just some sort of side effect in camera+'s filtering code. All this orientation stuff could stand to be a lot simpler, but this seems to be the solution for now.

cowfaboo
  • 709
  • 1
  • 8
  • 15
1

A few comments:

1) you want to round the hex value of the value when you divide by 2 so as to not get on a fractional pixel boundary (user roundf())

2) you do not handle the case of both dimensions the same

3) in the first create, you are setting the x offset not the y - use this modfied line:

imageRef = CGImageCreateWithImageInRect([image CGImage], CGRectMake(0, (image.size.height-dimension)/2, dimension, dimension));
David H
  • 40,852
  • 12
  • 92
  • 138
  • Thanks for the answer David - I am actually handling the case of equal dimensions, but I didn't bother including it here because it's a simple case that isn't contributing to this particular problem. Also, I'm actually setting the x offset in both creates for a reason. If you look at the images I've posted, `CGImageCreateWithImageInRect` seems to throw portrait-oriented images on their side, and so I still end up having to crop in the x direction, before rotating the resulting image back to its original orientation. If you have any other suggestions, I'd be glad to hear them. – cowfaboo Aug 14 '12 at 16:25
  • I believe I gave you the answer before but you didn't change your code. If the height > width, then you are going to clip the y axis, so the rectangle will be sized to the width, and there will be a y offset. If the reverse is true, then you will clip to the height, and there will be an x offset. In your code you always apply the offset to "x". The stretching is probably due to your trying to grab a rectangle from the original which is outside the boundaries of the original image. – David H Aug 14 '12 at 17:45
  • I understand what you're saying, and you're right that this is where the stretching is coming from. However, if I were to simply change my code to what you're suggesting, then only the filtered images would be cropped properly, and the normal images would instead end up stretched. The question of why the filtered images behave differently than the non-filtered ones remains, and I've found that it has to do with the fact that the filtered images are coming in with a different orientation property than the non-filtered ones. I have accounted for this, and things seem to be working now. – cowfaboo Aug 14 '12 at 18:28
  • 1
    Did the width and height come from the CGImageRef or the UIImage? I would have suggested you use the CGImageGet... functions to get the dimensions. Those should be of the actual bitmap. – David H Aug 14 '12 at 18:45
  • My dimensions are coming from the UIImage, and you're right, that's making things more complicated than they need to be. Problem seems to be solved now regardless, but could stand to be simplified, so thanks for that - I definitely have a much better understanding of what's going on in this code now. – cowfaboo Aug 14 '12 at 18:55