5
votes

Let's say you start with a UIImage and you wish to crop it. The most common method is to use CGImageCreateWithImageInRect to get a CGImageRef and create a new image from it like so:

CGRect cropRect = CGRectMake(x*width, y*height, width, height);
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], cropRect);
UIImage *croppedImage = [UIImage imageWithCGImage:imageRef];

Now let's say you later need to convert this UIImage into an NSData object. For example, this will happen if you wish to store the image on disk using NSKeyedArchiver. Or you can get an NSData object out by doing the following explicitly:

NSData *imageData = UIImagePNGRepresentation(croppedImage);

I did this and later on I tried to reconstruct a new UIImage object by doing:

UIImage *image = [UIImage imageWithData:imageData];

I did this for a series of 48 images. Most of them were fine, but after every 4 images the next 2 were corrupted with the top and bottom of each image swapped. If I take out the conversion to NSData and back again, it works fine. I also get the exact same problem if I use NSKeyedArchiver to write an array of the images out to disk (which I'm guessing calls something like UIImagePNGRepresentation in its encoding routine). My theory is this must have something to do with how I build the images from CGImageRefs.

What is the workaround for this? I can't seem to figure out how to put CGImageRefs into NSData object and I'd prefer a solution that builds the UIImage correctly so that it may be serialized.

Edit:

Some people asked for the actual code being run, so I'm adding it here

    UIImage *image = self.animationObject.image;
for (int y=0; y<[self.animationInfoObject.numInY intValue]; y++) {
    for (int x=0; x<[self.animationInfoObject.numInX intValue]; x++) {
        CGRect cropRect = CGRectMake(x*width, y*height, width, height);
        CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], cropRect);
        UIImage *croppedImage = [UIImage imageWithCGImage:imageRef];
        NSData *imageData = UIImageJPEGRepresentation(croppedImage, 1.0);
        [self addImageToArray:imageData];
        CFRelease(imageRef);
    }
}

The image from self.animationObject.image is a grid of images with numInX and and numInY images in each direction. I crop each individual image out of the 2D grid and save them.

Later on, I put the images in a image view to animate them. Here is the code:

        NSMutableArray *animationArray = [[NSMutableArray alloc] init];
    for (int frame=0; frame<[[photoObject frameArray] count]; frame++) {
        NSData *imageData =[[photoObject imageArray] objectAtIndex:[[[photoObject frameArray] objectAtIndex:frame] integerValue]-1];
        UIImage *image = [UIImage imageWithData:imageData];
        [animationArray addObject:image];
    }

This all works fine on an iPhone 6 but does not work on an iPhone 5. It also seems to only be a problem when the grid of images is long. For example, for the example I'm using self.animationObject.image has numInY as 3 and numInX 2 and the total size of the image is 1536x3072 (each mini-image is 768x1024). It is the bottom two images that have their tops and bottoms flipped.

1
Some more useful details. I have composite images that each contain 6 smaller images in a 2x3 grid. The size of each image is 768x1024 so the total size of the composite image is 1536x3072. It is the 5th and 6th images in each composite that have their bottom and top halves swapped. So there is something strange about the CGImage that results from cropping to the bottom portion of a large image, perhaps? Again, I should mention that this works perfectly if the UIImage isn't converted to NSData and then back again.Mike
So in this exampe above, width is 768, height is 1024 and it breaks when x is 0 or 1 and y is 2. It works fine when y is 0 or 1. Is this a case where CGImageCreateWithImageInRect just can't deal with images that big?Mike
Its hard to see what could be causing that. I'd suggest packaging this as a test app and filing a radar at bugreporter.apple.com. Or if you have a DTS incident to spare, run it by them in hopes of getting a work-around.Pierre Houston
@Mike Could your share more code and also share few test images so we could test it over here?Burhanuddin Sunelwala
I agree with @Mike, can you show with code just how you looped over there 48 images too?Drakes

1 Answers

0
votes

You problem is due to image orientation.
Each UIImage contains an -imageOrientation property that defines the orientation that should be applied on the UIImage once rendered to screen, when you ask for the CGImage you lost that information.
If you obtain you image from camera inside the provided metadata dictionary there are also the EXIF orientation, so its depend where you data is coming from.
On my blog I've written an article about that and how to handle them correctly if the image comes from the camera.
A quick solution could be read the CGImage property and apply a transformation according to the -imageOrientation property. You can find different ways, here and here

This snippet is taken from here, credits to awolf

- (UIImage *)croppedImageInRect:(CGRect)rect
{
    double (^rad)(double) = ^(double deg) {
        return deg / 180.0 * M_PI;
    };

    CGAffineTransform rectTransform;
    switch (self.imageOrientation) {
        case UIImageOrientationLeft:
            rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(90)), 0, -self.size.height);
            break;
        case UIImageOrientationRight:
            rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-90)), -self.size.width, 0);
            break;
        case UIImageOrientationDown:
            rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-180)), -self.size.width, -self.size.height);
            break;
        default:
            rectTransform = CGAffineTransformIdentity;
    };
    rectTransform = CGAffineTransformScale(rectTransform, self.scale, self.scale);

    CGImageRef imageRef = CGImageCreateWithImageInRect([self CGImage], CGRectApplyAffineTransform(rect, rectTransform));
    UIImage *result = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
    CGImageRelease(imageRef);

    return result;
}