Simon Simon - 3 months ago 20
iOS Question

How do I autocrop a UIImage?

I have a UIImage which contains a shape; the rest is transparent. I'd like to get another UIImage by cropping out as much of the transparent part as possible, still retaining all of the non-transparent pixels - similar to the autocrop function in GIMP. How would I go about doing this?

Answer

This approach may be a little more invasive than what you were hoping for, but it gets the job done. What I'm doing is creating a bitmap context for the UIImage, obtaining a pointer to the raw image data, then sifting through it looking for non-transparent pixels. My method returns a CGRect which I use to create a new UIImage.

- (CGRect)cropRectForImage:(UIImage *)image {

CGImageRef cgImage = image.CGImage;
CGContextRef context = [self createARGBBitmapContextFromImage:cgImage];
if (context == NULL) return CGRectZero; 

size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
CGRect rect = CGRectMake(0, 0, width, height);

CGContextDrawImage(context, rect, cgImage);

unsigned char *data = CGBitmapContextGetData(context);
CGContextRelease(context);

//Filter through data and look for non-transparent pixels.
int lowX = width;
int lowY = height;
int highX = 0;
int highY = 0;
if (data != NULL) {
    for (int y=0; y<height; y++) {
        for (int x=0; x<width; x++) {
            int pixelIndex = (width * y + x) * 4 /* 4 for A, R, G, B */;
            if (data[pixelIndex] != 0) { //Alpha value is not zero; pixel is not transparent.
                if (x < lowX) lowX = x;
                if (x > highX) highX = x;
                if (y < lowY) lowY = y;
                if (y > highY) highY = y;
            }
        }
    }
    free(data);
} else {
    return CGRectZero;
}

return CGRectMake(lowX, lowY, highX-lowX, highY-lowY);
}

The method to create the Bitmap Context:

- (CGContextRef)createARGBBitmapContextFromImage:(CGImageRef)inImage {

CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void *bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;

// Get image width, height. We'll use the entire image.
size_t width = CGImageGetWidth(inImage);
size_t height = CGImageGetHeight(inImage);

// Declare the number of bytes per row. Each pixel in the bitmap in this
// example is represented by 4 bytes; 8 bits each of red, green, blue, and
// alpha.
bitmapBytesPerRow = (width * 4);
bitmapByteCount = (bitmapBytesPerRow * height);

// Use the generic RGB color space.
colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL) return NULL;

// Allocate memory for image data. This is the destination in memory
// where any drawing to the bitmap context will be rendered.
bitmapData = malloc( bitmapByteCount );
if (bitmapData == NULL)
{
    CGColorSpaceRelease(colorSpace);
    return NULL;
}

// Create the bitmap context. We want pre-multiplied ARGB, 8-bits
// per component. Regardless of what the source image format is
// (CMYK, Grayscale, and so on) it will be converted over to the format
// specified here by CGBitmapContextCreate.
context = CGBitmapContextCreate (bitmapData,
                                 width,
                                 height,
                                 8,      // bits per component
                                 bitmapBytesPerRow,
                                 colorSpace,
                                 kCGImageAlphaPremultipliedFirst);
if (context == NULL) free (bitmapData);

// Make sure and release colorspace before returning
CGColorSpaceRelease(colorSpace);

return context;
}

And finally, get your new cropped UIImage from the returned CGRect:

CGRect newRect = [self cropRectForImage:oldImage];
CGImageRef imageRef = CGImageCreateWithImageInRect(oldImage.CGImage, newRect);
UIImage *newImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);

I grabbed a bit of that code from this very useful article. Hope it helps!