我有一个包含形状的UIImage;其余的是透明的。我想通过裁剪尽可能多的透明部分来获得另一个UIImage,仍保留所有非透明像素 - 类似于GIMP中的autocrop功能。我该怎么做呢?
这种方法可能比您希望的更具侵入性,但它可以完成工作。我正在做的是为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抓起了一些代码。希望能帮助到你!
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)
}
}
创建位图上下文的方法:
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
}
从@ 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
}
}
斯威夫特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;
}
}
我在这里向您介绍Swift 4的版本。除了上面的代码,每个人都忘记了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)
}
为了获得裁剪的图像,通常:
UIImage(cgImage: image.cgImage!.cropping(to: croppedRect)!)
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
}
}