Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect cropping rectangle for UIImage with transparent background

What is the strategy to take a UIImage with a transparent background and determine the smallest rectangle to crop to so that only the visible image data is left (along with the extra transparent background if the image data is not rectangular of course)?

I have found lots of information on cropping a UIImage to a CGRect, plenty of cropping view controllers that require user intervention, and several open source libraries with image processing classes and categories (including MGImageUtilities and NYXImagesKit), but nothing yet that solves my particular problem.

My current app is targeting iOS 5.0, so compatibility with that would be optimal.

EDIT: By the way, I am hoping that there is a better way than brute force looking at every pixel in the worst case scenario of the image data in rows and columns looking for the edge boundaries.

like image 342
BP. Avatar asked May 15 '13 15:05

BP.


2 Answers

Have you had a chance to see https://gist.github.com/spinogrizz/3549921 ?

it looks like it's exactly what you need.

just so it's not lost, a copy & paste from that page:

- (UIImage *) imageByTrimmingTransparentPixels {
    int rows = self.size.height;
    int cols = self.size.width;
    int bytesPerRow = cols*sizeof(uint8_t);

    if ( rows < 2 || cols < 2 ) {
        return self;
    }

    //allocate array to hold alpha channel
    uint8_t *bitmapData = calloc(rows*cols, sizeof(uint8_t));

    //create alpha-only bitmap context
    CGContextRef contextRef = CGBitmapContextCreate(bitmapData, cols, rows, 8, bytesPerRow, NULL, kCGImageAlphaOnly);

    //draw our image on that context
    CGImageRef cgImage = self.CGImage;
    CGRect rect = CGRectMake(0, 0, cols, rows);
    CGContextDrawImage(contextRef, rect, cgImage);

    //summ all non-transparent pixels in every row and every column
    uint16_t *rowSum = calloc(rows, sizeof(uint16_t));
    uint16_t *colSum = calloc(cols, sizeof(uint16_t));

    //enumerate through all pixels
    for ( int row = 0; row < rows; row++) {
        for ( int col = 0; col < cols; col++)
        {
            if ( bitmapData[row*bytesPerRow + col] ) { //found non-transparent pixel
                rowSum[row]++;
                colSum[col]++;
            }
        }
    }

    //initialize crop insets and enumerate cols/rows arrays until we find non-empty columns or row
    UIEdgeInsets crop = UIEdgeInsetsMake(0, 0, 0, 0);

    for ( int i = 0; i<rows; i++ ) {        //top
        if ( rowSum[i] > 0 ) {
            crop.top = i; break;
        }
    }

    for ( int i = rows; i >= 0; i-- ) {     //bottom
        if ( rowSum[i] > 0 ) {
            crop.bottom = MAX(0, rows-i-1); break;
        }
    }

    for ( int i = 0; i<cols; i++ ) {        //left
        if ( colSum[i] > 0 ) {
            crop.left = i; break;
        }
    }

    for ( int i = cols; i >= 0; i-- ) {     //right
        if ( colSum[i] > 0 ) {
            crop.right = MAX(0, cols-i-1); break;
        }
    }

    free(bitmapData);
    free(colSum);
    free(rowSum);

    if ( crop.top == 0 && crop.bottom == 0 && crop.left == 0 && crop.right == 0 ) {
        //no cropping needed
        return self;
    }
    else {
        //calculate new crop bounds
        rect.origin.x += crop.left;
        rect.origin.y += crop.top;
        rect.size.width -= crop.left + crop.right;
        rect.size.height -= crop.top + crop.bottom;

        //crop it
        CGImageRef newImage = CGImageCreateWithImageInRect(cgImage, rect);

        //convert back to UIImage
        return [UIImage imageWithCGImage:newImage];
    }
}
like image 137
John Boker Avatar answered Oct 06 '22 18:10

John Boker


here it is in Swift 4

    extension UIImage {

    func cropAlpha() -> UIImage {

        let cgImage = self.cgImage!;

        let width = cgImage.width
        let height = cgImage.height

        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bytesPerPixel:Int = 4
        let bytesPerRow = bytesPerPixel * width
        let bitsPerComponent = 8
        let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue

        guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo),
            let ptr = context.data?.assumingMemoryBound(to: UInt8.self) else {
                return self
        }

        context.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: width, height: height))

        var minX = width
        var minY = height
        var maxX: Int = 0
        var maxY: Int = 0

        for x in 1 ..< width {
            for y in 1 ..< height {

                let i = bytesPerRow * Int(y) + bytesPerPixel * Int(x)
                let a = CGFloat(ptr[i + 3]) / 255.0

                if(a>0) {
                    if (x < minX) { minX = x };
                    if (x > maxX) { maxX = x };
                    if (y < minY) { minY = y};
                    if (y > maxY) { maxY = y};
                }
            }
        }

        let rect = CGRect(x: CGFloat(minX),y: CGFloat(minY), width: CGFloat(maxX-minX), height: CGFloat(maxY-minY))
        let imageScale:CGFloat = self.scale
        let croppedImage =  self.cgImage!.cropping(to: rect)!
        let ret = UIImage(cgImage: croppedImage, scale: imageScale, orientation: self.imageOrientation)

        return ret;
    }
}
like image 45
Jason Cragun Avatar answered Oct 06 '22 18:10

Jason Cragun