Skip to content

Instantly share code, notes, and snippets.

@fl034
Created January 23, 2021 22:06
Show Gist options
  • Save fl034/a0a8c1403eae78db9d72ee0625651147 to your computer and use it in GitHub Desktop.
Save fl034/a0a8c1403eae78db9d72ee0625651147 to your computer and use it in GitHub Desktop.
UIImage+Trim.swift
//
// UIImage+Trim.swift
//
//
// Created by Frank Lehmann on 19.01.21.
//
import UIKit
import AlamofireImage
extension UIImage {
func transparencyInsetsRequiringFullOpacity(_ fullyOpaque: Bool) -> UIEdgeInsets? {
// Draw our image on that context
guard let cgImage = cgImage else { return nil }
let width = cgImage.width
let height = cgImage.height
let bytesPerRow = width * MemoryLayout<UInt8>.size
// Allocate array to hold alpha channel
var bitmapData = [UInt8](repeating: 0, count: width * height)
guard let context = CGContext(data: &bitmapData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.alphaOnly.rawValue) else { return nil }
let rect = CGRect(origin: .zero, size: CGSize(width: width, height: height))
context.draw(cgImage, in: rect)
// Sum all non-transparent pixels in every row and every column
var rowSum = [UInt16](repeating: 0, count: height)
var colSum = [UInt16](repeating: 0, count: width)
for row in 0..<height {
for col in 0..<width {
if fullyOpaque {
// Found non-transparent pixel
if (bitmapData[row*bytesPerRow + col] == .max) {
rowSum[row] += 1
colSum[col] += 1
}
} else {
// Found non-transparent pixel
if bitmapData[row*bytesPerRow + col] != .zero {
rowSum[row] += 1
colSum[col] += 1
}
}
}
}
// Initialize crop insets and enumerate cols/rows arrays until we find non-empty columns or row
var crop = UIEdgeInsets()
// Top
for i in 0..<height {
if rowSum[i] > 0 {
crop.top = CGFloat(i)
break
}
}
// Bottom
for i in 0..<height {
let correctI = height - i - 1
if rowSum[correctI] > 0 {
crop.bottom = max(0, CGFloat(height) - CGFloat(correctI) - 1)
break
}
}
// Left
for i in 0..<width {
if colSum[i] > 0 {
crop.left = CGFloat(i)
break
}
}
// Right
for i in 0..<width {
let correctI = width - i - 1
if colSum[correctI] > 0 {
crop.right = max(0, CGFloat(width) - CGFloat(correctI) - 1)
break
}
}
bitmapData = []
colSum = []
rowSum = []
return crop
}
func imageByTrimmingTransparentPixels(requireFullOpacity fullyOpaque: Bool) -> UIImage {
if (size.height < 2 || size.width < 2) {
return self
}
var rect = CGRect(origin: .zero, size: CGSize(width: size.width * scale, height: size.height * scale))
guard let crop = self.transparencyInsetsRequiringFullOpacity(fullyOpaque) else { return self }
guard crop.top > 0 || crop.bottom > 0 || crop.left > 0 || crop.right > 0 else { return self }
// 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
guard let cgImage = cgImage else { return self }
guard let newImage = cgImage.cropping(to: rect) else { return self }
// Convert back to UIImage
return UIImage(cgImage: newImage, scale: scale, orientation: imageOrientation)
}
var trimmingTransparentPixels: UIImage {
self.imageByTrimmingTransparentPixels(requireFullOpacity: false)
}
}
extension DynamicImageFilter {
static let trimmingTransparentPixelsFilter: DynamicImageFilter = .init("trimmingTransparentPixelsFilter") { $0.trimmingTransparentPixels }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment