How do I autocrop a UIImage?

后端 未结 6 488
南方客
南方客 2020-12-23 15:04

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 retain

相关标签:
6条回答
  • 2020-12-23 15:50

    I present you here the version of Swift 4. In addition to the code above, everybody forget context.clear() !!

    func cropRect() -> CGRect {
        let cgImage = self.cgImage!
    
        let bitmapBytesPerRow = cgImage.width * 4
        let bitmapByteCount = bitmapBytesPerRow * cgImage.height
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapData = malloc(bitmapByteCount)
    
        if bitmapData == nil {
            return CGRect(x: 0, y: 0, width: 0, height: 0)
        }
    
        let context = CGContext (data: bitmapData, width: cgImage.width, height: cgImage.height, bitsPerComponent: 8,bytesPerRow: bitmapBytesPerRow,space: colorSpace,bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
    
        if context == nil {
            return CGRect(x: 0, y: 0, width: 0, height: 0)
        }
    
        let height = cgImage.height
        let width = cgImage.width
    
        let rect = CGRect(x: 0, y: 0, width: width, height: height)
        context?.clear(rect)
        context?.draw(cgImage, in: rect)
    
        let data = context?.data
    
        if data == nil {
            return CGRect(x: 0, y: 0, width: 0, height: 0)
        }
    
        var lowX = width
        var lowY = height
        var highX: Int = 0
        var highY: Int = 0
    
        //Filter through data and look for non-transparent pixels.
        for y in 0..<height {
            for x in  0..<width {
                let pixelIndex = (width * y + x) * 4 /* 4 for A, R, G, B */
                let color = data!.load(fromByteOffset: pixelIndex, as: UInt32.self)
    
                if color != 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
                    }
                }
            }
        }
    
    
        return CGRect(x: lowX, y: lowY, width: highX-lowX, height: highY-lowY)
    }
    

    For getting the cropped image, just usual:

    UIImage(cgImage: image.cgImage!.cropping(to: croppedRect)!)
    
    0 讨论(0)
  • 2020-12-23 15:54

    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!

    0 讨论(0)
  • 2020-12-23 15:54

    Improved from @Danny182's answer, i also added white-space (any pixels brighter than 0xe0e0e0) trimming for my own need.

    Usage:

    let newimage = UIImage(named: "XXX")!.trim()
    

    import UIKit
    
    extension UIImage {
    
        func trim() -> UIImage {
            let newRect = self.cropRect
            if let imageRef = self.cgImage!.cropping(to: newRect) {
                return UIImage(cgImage: imageRef)
            }
            return self
        }
    
        var cropRect: CGRect {
            let cgImage = self.cgImage
            let context = createARGBBitmapContextFromImage(inImage: cgImage!)
            if context == nil {
                return CGRect.zero
            }
    
            let height = CGFloat(cgImage!.height)
            let width = CGFloat(cgImage!.width)
    
            let rect = CGRect(x: 0, y: 0, width: width, height: height)
            context?.draw(cgImage!, in: rect)
    
            //let data = UnsafePointer<CUnsignedChar>(CGBitmapContextGetData(context))
            guard let data = context?.data?.assumingMemoryBound(to: UInt8.self) else {
                return CGRect.zero
            }
    
            var lowX = width
            var lowY = height
            var highX: CGFloat = 0
            var highY: CGFloat = 0
    
            let heightInt = Int(height)
            let widthInt = Int(width)
            //Filter through data and look for non-transparent pixels.
            for y in (0 ..< heightInt) {
                let y = CGFloat(y)
                for x in (0 ..< widthInt) {
                    let x = CGFloat(x)
                    let pixelIndex = (width * y + x) * 4 /* 4 for A, R, G, B */
    
                    if data[Int(pixelIndex)] == 0  { continue } // crop transparent
    
                    if data[Int(pixelIndex+1)] > 0xE0 && data[Int(pixelIndex+2)] > 0xE0 && data[Int(pixelIndex+3)] > 0xE0 { continue } // crop white
    
                    if (x < lowX) {
                        lowX = x
                    }
                    if (x > highX) {
                        highX = x
                    }
                    if (y < lowY) {
                        lowY = y
                    }
                    if (y > highY) {
                        highY = y
                    }
    
                }
            }
    
            return CGRect(x: lowX, y: lowY, width: highX - lowX, height: highY - lowY)
        }
    
        func createARGBBitmapContextFromImage(inImage: CGImage) -> CGContext? {
    
            let width = inImage.width
            let height = inImage.height
    
            let bitmapBytesPerRow = width * 4
            let bitmapByteCount = bitmapBytesPerRow * height
    
            let colorSpace = CGColorSpaceCreateDeviceRGB()
    
            let bitmapData = malloc(bitmapByteCount)
            if bitmapData == nil {
                return nil
            }
    
            let context = CGContext (data: bitmapData,
                                     width: width,
                                     height: height,
                                     bitsPerComponent: 8,      // bits per component
                bytesPerRow: bitmapBytesPerRow,
                space: colorSpace,
                bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
    
            return context
        }
    }
    
    0 讨论(0)
  • 2020-12-23 15:58

    Swift Version:

    extension UIImage {
    func cropRect() -> CGRect {
        let cgImage = self.CGImage!
        let context = createARGBBitmapContextFromImage(cgImage)
        if context == nil {
            return CGRectZero
        }
    
        let height = CGFloat(CGImageGetHeight(cgImage))
        let width = CGFloat(CGImageGetWidth(cgImage))
    
        let rect = CGRectMake(0, 0, width, height)
        CGContextDrawImage(context, rect, cgImage)
    
        let data = UnsafePointer<CUnsignedChar>(CGBitmapContextGetData(context))
    
        if data == nil {
            return CGRectZero
        }
    
        var lowX = width
        var lowY = height
        var highX: CGFloat = 0
        var highY: CGFloat = 0
    
        //Filter through data and look for non-transparent pixels.
        for (var y: CGFloat = 0 ; y < height ; y++) {
            for (var x: CGFloat = 0; x < width ; x++) {
                let pixelIndex = (width * y + x) * 4 /* 4 for A, R, G, B */
    
                if data[Int(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
                    }
                }
            }
        }
    
    
        return CGRectMake(lowX, lowY, highX-lowX, highY-lowY)
    }
    }
    

    The method to create the Bitmap Context:

    func createARGBBitmapContextFromImage(inImage: CGImageRef) -> CGContextRef? {
    
        let width = CGImageGetWidth(inImage)
        let height = CGImageGetHeight(inImage)
    
        let bitmapBytesPerRow = width * 4
        let bitmapByteCount = bitmapBytesPerRow * height
    
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        if colorSpace == nil {
            return nil
        }
    
        let bitmapData = malloc(bitmapByteCount)
        if bitmapData == nil {
            return nil
        }
    
        let context = CGBitmapContextCreate (bitmapData,
            width,
            height,
            8,      // bits per component
            bitmapBytesPerRow,
            colorSpace,
            CGImageAlphaInfo.PremultipliedFirst.rawValue)
    
        return context
    }
    

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

    let image = // UIImage Source
    let newRect = image.cropRect()
    if let imageRef = CGImageCreateWithImageInRect(image.CGImage!, newRect) {
        let newImage = UIImage(CGImage: imageRef)
        // Use this new Image
    }
    
    0 讨论(0)
  • 2020-12-23 15:59

    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;
        }
        
    }
    
    0 讨论(0)
  • 2020-12-23 16:03

    Swift 3 (it's not a UIImage extension, I needed it in another class) it may save time for someone:

    class EditImage {
    
    static func cropRect(_ image: UIImage) -> CGRect {
        let cgImage = image.cgImage
        let context = createARGBBitmapContextFromImage(inImage: cgImage!)
        if context == nil {
            return CGRect.zero
        }
    
        let height = CGFloat(cgImage!.height)
        let width = CGFloat(cgImage!.width)
    
        let rect = CGRect(x: 0, y: 0, width: width, height: height)
        context?.draw(cgImage!, in: rect)
    
        //let data = UnsafePointer<CUnsignedChar>(CGBitmapContextGetData(context))
        let data = context?.data?.assumingMemoryBound(to: UInt8.self)
    
        if data == nil {
            return CGRect.zero
        }
    
        var lowX = width
        var lowY = height
        var highX: CGFloat = 0
        var highY: CGFloat = 0
    
        let heightInt = Int(height)
        let widthInt = Int(width)
        //Filter through data and look for non-transparent pixels.
        for y in (0 ..< heightInt) {
            let y = CGFloat(y)
            for x in (0 ..< widthInt) {
                let x = CGFloat(x)
                let pixelIndex = (width * y + x) * 4 /* 4 for A, R, G, B */
    
                if data?[Int(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
                    }
                }
            }
        }
    
    
        return CGRect(x: lowX, y: lowY, width: highX - lowY, height: highY - lowY)
    }
    
    static func createARGBBitmapContextFromImage(inImage: CGImage) -> CGContext? {
    
        let width = inImage.width
        let height = inImage.height
    
        let bitmapBytesPerRow = width * 4
        let bitmapByteCount = bitmapBytesPerRow * height
    
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        if colorSpace == nil {
            return nil
        }
    
        let bitmapData = malloc(bitmapByteCount)
        if bitmapData == nil {
            return nil
        }
    
        let context = CGContext (data: bitmapData,
            width: width,
            height: height,
            bitsPerComponent: 8,      // bits per component
            bytesPerRow: bitmapBytesPerRow,
            space: colorSpace,
        bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
    
        return context
    }
    }
    
    0 讨论(0)
提交回复
热议问题