2012-01-30 21 views
14

我有一個包含形狀的UIImage;其餘部分是透明的。我希望通過儘可能多地切出透明部分來獲得另一個UIImage,但仍保留所有非透明像素 - 類似於GIMP中的自動裁剪功能。我會如何去做這件事?如何autocrop一個UIImage?

回答

23

這種方法可能比您希望的更有侵略性,但它可以完成工作。我在做的是爲UIImage創建一個位圖上下文,獲取一個指向原始圖像數據的指針,然後篩選它尋找不透明的像素。我的方法返回一個CGRect,我用它來創建一個新的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); 
} 

創建位圖上下文的方法:

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

最後,從返回的CGRect讓您的新裁剪的UIImage:

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

我抓起了一下代碼,從this very useful article。希望能幫助到你!

+0

感謝。工作很棒... :) – Thampuran 2014-11-11 19:21:01

9

斯威夫特版本:

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

創建位圖上下文的方法:

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 
} 

最後,從返回的CGRect讓您的新裁剪的UIImage:

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

你的解決方案拯救了我的生命。非常感謝!!! – 2017-03-02 11:25:32

0

Swift 3(這不是一個UIImage擴展,我需要它的另一個類),它可以節省時間的人:

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

感謝代碼@ Danny182,但是在返回行中有一個錯字(highX - lowY) - >應該是highX - lowX – 2016-11-24 07:36:34

+0

也許這就是爲什麼它不能正常工作。我會嘗試一下。 TY – Danny182 2016-11-25 17:11:34

+0

CGColorSpaceCreateDeviceRGB()不返回一個可選項,無檢查它是沒有必要的。 – 2017-09-26 19:51:55

4

從@ Danny182的回答改進,我還添加了白色空間(任何像素亮度比0xe0e0e0)修邊我自己的需要。

用法:

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

完美無缺!一個建議是應用Swift命名約定,並在返回一個新對象後調用'trim()'函數'trimmed()'。如果返回'void'並修改現有的UIImage實例,命名將是正確的。 – blackjacx 2017-01-18 09:26:17

+0

@Daddycat Tan Yin See,很好的解決方案 - 這裏的內存管理如何?是malloc照顧? – shadowf 2018-01-23 23:52:16

0

夫特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; 
} 

}