Jak wykonać autocrop UIImage?

Mam interfejs, który zawiera kształt; reszta jest przezroczysta. Chciałbym uzyskać inny interfejs użytkownika, wycinając jak najwięcej przezroczystej części, zachowując wszystkie nieprzezroczyste piksele - podobnie jak funkcja autocrop w GIMP. Jak miałbym to zrobić?

Author: Peter Hosey, 2012-01-30

5 answers

To podejście może być trochę bardziej inwazyjne niż to, na co liczyłeś, ale robi swoje. To, co robię, to tworzenie kontekstu bitmapowego dla UIImage, uzyskiwanie wskaźnika do surowych danych obrazu, a następnie przesiewanie go w poszukiwaniu nieprzezroczystych pikseli. Moja metoda zwraca CGRect, którego używam do tworzenia nowego interfejsu użytkownika.

- (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);
}

Metoda tworzenia kontekstu bitmapowego:

- (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;
}

I na koniec, uzyskaj nowy przycięty interfejs z zwróconego CGRect:

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

Chwyciłem kawałek tego kodu z tego bardzo przydatnego Artykułu. Mam nadzieję, że to pomoże!

 23
Author: daltonclaybrook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-17 21:23:46

Wersja Swift:

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)
}
}

Metoda tworzenia kontekstu bitmapowego:

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
}

I na koniec, uzyskaj nowy kadrowany interfejs z zwróconej 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
}
 9
Author: Sahil Kapoor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-25 10:13:52

Poprawione z odpowiedzi @ Danny182, dodałem również white-space (dowolne piksele jaśniejsze niż 0xe0e0e0) przycinanie na własne potrzeby.

Użycie:

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
    }
}
 5
Author: Daddycat Tan Yin See,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-24 08:07:07

Swift 3 (nie jest to rozszerzenie UIImage, potrzebowałem go w innej klasie) może zaoszczędzić czas dla kogoś:

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
Author: Danny182,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-08 18:17:47

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
Author: Jason Cragun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-13 03:58:19