¿Cómo autocultivo un UIImage?

Tengo un UIImage que contiene una forma; el rest es transparente Me gustaría get otro UIImage recortando la mayor parte posible de la parte transparente, conservando todos los píxeles no transparentes, similar a la function de autocompilation en GIMP. ¿Cómo iba a hacer esto?

Este enfoque puede ser un poco más invasivo de lo que esperaba, pero hace el trabajo. Lo que estoy haciendo es crear un context de bitmap para el UIImage, get un puntero a los datos de image sin procesar, y luego searchlo en busca de píxeles no transparentes. Mi método devuelve un CGRect que utilizo para crear un nuevo 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); } 

El método para crear el context Bitmap:

 - (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 networking, 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 rendenetworking. 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; } 

Y, por último, obtenga su nuevo recortado UIImage del CGRect devuelto:

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

Cogí un poco de ese código de este artículo muy útil . ¡Espero eso ayude!

Versión 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) } } 

El método para crear el context Bitmap:

 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 } 

Y, por último, obtenga su nuevo recortado UIImage del CGRect devuelto:

 let image = // UIImage Source let newRect = image.cropRect() if let imageRef = CGImageCreateWithImageInRect(image.CGImage!, newRect) { let newImage = UIImage(CGImage: imageRef) // Use this new Image } 

Mejorada de la respuesta de @ Danny182, también agregué recorte de espacio en blanco (cualquier píxel más shiny que 0xe0e0e0) para mi propia necesidad.

Uso:

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

Swift 3 (no es una extensión UIImage, la necesitaba en otra class) puede ahorrar time para alguien:

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