UIImage有效内容裁剪:如何获取透明图片有效区域rect并裁剪输出
UIImage有效非透明区域裁剪方案
核心逻辑是遍历图片所有像素点,记录所有非透明像素的坐标边界,最终根据边界计算有效内容的CGRect,再对原图进行裁剪。
实现步骤
- 第一步:修正UIImage方向(如果图片orientation不是向上,先转为向上方向,避免像素坐标错乱),将UIImage转换为可读取的像素位图,获取每个像素的alpha通道值。
- 第二步:遍历所有像素,统计有效边界。初始化四个边界变量:
minX初始为图片宽度最大值,maxX初始为0,minY初始为图片高度最大值,maxY初始为0。遇到alpha大于阈值的非透明像素时,同步更新四个边界值。 - 第三步:边界合法性校验。如果遍历完成后
maxX < minX,说明整张图都是透明区域,直接返回原图即可;否则用边界值计算有效内容的rect,注意要适配UIImage的scale属性,将像素坐标转为point坐标系。 - 第四步:根据得到的有效rect裁剪原图,生成新的UIImage。
代码示例
Swift 实现
计算有效内容rect
func validContentRect(for image: UIImage) -> CGRect { // 先修正图片方向为向上 guard let upOriImage = image.fixedOrientation(), let cgImage = upOriImage.cgImage else { return .zero } let width = cgImage.width let height = cgImage.height let colorSpace = CGColorSpaceCreateDeviceRGB() let bytesPerPixel = 4 let bytesPerRow = bytesPerPixel * width let bitsPerComponent = 8 let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else { return .zero } context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) guard let pixelBuffer = context.data else { return .zero } let pixels = pixelBuffer.bindMemory(to: UInt8.self, capacity: width * height * 4) var minX = width var maxX = 0 var minY = height var maxY = 0 let alphaThreshold: UInt8 = 0 // 可调整阈值,过滤接近透明的噪点 for y in 0..<height { for x in 0..<width { let pixelIndex = (y * width + x) * 4 let alpha = pixels[pixelIndex + 3] if alpha > alphaThreshold { minX = min(minX, x) maxX = max(maxX, x) minY = min(minY, y) maxY = max(maxY, y) } } } guard maxX >= minX, maxY >= minY else { return CGRect(origin: .zero, size: image.size) } // 转换为point坐标系适配scale let scale = image.scale return CGRect( x: CGFloat(minX) / scale, y: CGFloat(minY) / scale, width: CGFloat(maxX - minX + 1) / scale, height: CGFloat(maxY - minY + 1) / scale ) } // 辅助方法:修正图片方向为向上 extension UIImage { func fixedOrientation() -> UIImage? { guard imageOrientation != .up else { return self } var transform = CGAffineTransform.identity switch imageOrientation { case .down, .downMirrored: transform = transform.translatedBy(x: size.width, y: size.height) transform = transform.rotated(by: .pi) case .left, .leftMirrored: transform = transform.translatedBy(x: size.width, y: 0) transform = transform.rotated(by: .pi / 2) case .right, .rightMirrored: transform = transform.translatedBy(x: 0, y: size.height) transform = transform.rotated(by: -.pi / 2) default: break } switch imageOrientation { case .upMirrored, .downMirrored: transform = transform.translatedBy(x: size.width, y: 0) transform = transform.scaledBy(x: -1, y: 1) case .leftMirrored, .rightMirrored: transform = transform.translatedBy(x: size.height, y: 0) transform = transform.scaledBy(x: -1, y: 1) default: break } guard let cgImage = cgImage, let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: cgImage.colorSpace!, bitmapInfo: cgImage.bitmapInfo.rawValue) else { return nil } context.concatenate(transform) switch imageOrientation { case .left, .leftMirrored, .right, .rightMirrored: context.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width)) default: context.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) } return context.makeImage().map { UIImage(cgImage: $0, scale: scale, orientation: .up) } } }
裁剪图片
func cropToValidContent(_ image: UIImage) -> UIImage? { let validRect = validContentRect(for: image) guard validRect.size != .zero else { return image } UIGraphicsBeginImageContextWithOptions(validRect.size, false, image.scale) defer { UIGraphicsEndImageContext() } image.draw(at: CGPoint(x: -validRect.origin.x, y: -validRect.origin.y)) return UIGraphicsGetImageFromCurrentImageContext() }
注意事项
如果图片存在透明度极低的噪点,可以调整alphaThreshold的数值(取值范围0~255),比如设置为10,即可过滤掉几乎不可见的透明像素,避免裁剪边界包含多余的无效区域。
内容的提问来源于stack exchange,提问作者Wicky Wu
相关产品推荐
相关产品推荐

