What is the strategy to take a UIImage with a transparent background and determine the smallest rectangle to crop to so that only the visible image data is left (along with the extra transparent background if the image data is not rectangular of course)?
I have found lots of information on cropping a UIImage to a CGRect, plenty of cropping view controllers that require user intervention, and several open source libraries with image processing classes and categories (including MGImageUtilities and NYXImagesKit), but nothing yet that solves my particular problem.
My current app is targeting iOS 5.0, so compatibility with that would be optimal.
EDIT: By the way, I am hoping that there is a better way than brute force looking at every pixel in the worst case scenario of the image data in rows and columns looking for the edge boundaries.
Have you had a chance to see https://gist.github.com/spinogrizz/3549921 ?
it looks like it's exactly what you need.
just so it's not lost, a copy & paste from that page:
- (UIImage *) imageByTrimmingTransparentPixels {
int rows = self.size.height;
int cols = self.size.width;
int bytesPerRow = cols*sizeof(uint8_t);
if ( rows < 2 || cols < 2 ) {
return self;
}
//allocate array to hold alpha channel
uint8_t *bitmapData = calloc(rows*cols, sizeof(uint8_t));
//create alpha-only bitmap context
CGContextRef contextRef = CGBitmapContextCreate(bitmapData, cols, rows, 8, bytesPerRow, NULL, kCGImageAlphaOnly);
//draw our image on that context
CGImageRef cgImage = self.CGImage;
CGRect rect = CGRectMake(0, 0, cols, rows);
CGContextDrawImage(contextRef, rect, cgImage);
//summ all non-transparent pixels in every row and every column
uint16_t *rowSum = calloc(rows, sizeof(uint16_t));
uint16_t *colSum = calloc(cols, sizeof(uint16_t));
//enumerate through all pixels
for ( int row = 0; row < rows; row++) {
for ( int col = 0; col < cols; col++)
{
if ( bitmapData[row*bytesPerRow + col] ) { //found non-transparent pixel
rowSum[row]++;
colSum[col]++;
}
}
}
//initialize crop insets and enumerate cols/rows arrays until we find non-empty columns or row
UIEdgeInsets crop = UIEdgeInsetsMake(0, 0, 0, 0);
for ( int i = 0; i<rows; i++ ) { //top
if ( rowSum[i] > 0 ) {
crop.top = i; break;
}
}
for ( int i = rows; i >= 0; i-- ) { //bottom
if ( rowSum[i] > 0 ) {
crop.bottom = MAX(0, rows-i-1); break;
}
}
for ( int i = 0; i<cols; i++ ) { //left
if ( colSum[i] > 0 ) {
crop.left = i; break;
}
}
for ( int i = cols; i >= 0; i-- ) { //right
if ( colSum[i] > 0 ) {
crop.right = MAX(0, cols-i-1); break;
}
}
free(bitmapData);
free(colSum);
free(rowSum);
if ( crop.top == 0 && crop.bottom == 0 && crop.left == 0 && crop.right == 0 ) {
//no cropping needed
return self;
}
else {
//calculate new crop bounds
rect.origin.x += crop.left;
rect.origin.y += crop.top;
rect.size.width -= crop.left + crop.right;
rect.size.height -= crop.top + crop.bottom;
//crop it
CGImageRef newImage = CGImageCreateWithImageInRect(cgImage, rect);
//convert back to UIImage
return [UIImage imageWithCGImage:newImage];
}
}
here it is in 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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With