Created
January 23, 2021 22:06
-
-
Save fl034/a0a8c1403eae78db9d72ee0625651147 to your computer and use it in GitHub Desktop.
UIImage+Trim.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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