Skip to content

Instantly share code, notes, and snippets.

@AdamLantz
Created June 20, 2018 16:03
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AdamLantz/d5d841e60583e740c0b5f515ba5064fb to your computer and use it in GitHub Desktop.
Save AdamLantz/d5d841e60583e740c0b5f515ba5064fb to your computer and use it in GitHub Desktop.
Swift 4 - Crop transparent pixels from UIImage
//Swift 4 modifications for this gist: https://gist.github.com/krooked/9c4c81557fc85bc61e51c0b4f3301e6e
import Foundation
import UIKit
extension UIImage {
func cropImageByAlpha() -> UIImage {
let cgImage = self.cgImage
let context = createARGBBitmapContextFromImage(inImage: cgImage!)
let height = cgImage!.height
let width = cgImage!.width
var rect: CGRect = CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height))
context?.draw(cgImage!, in: rect)
let pixelData = self.cgImage!.dataProvider!.data
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
var minX = width
var minY = height
var maxX: Int = 0
var maxY: 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 */
if data[Int(pixelIndex)] != 0 { //Alpha value is not zero pixel is not transparent.
if (x < minX) {
minX = x
}
if (x > maxX) {
maxX = x
}
if (y < minY) {
minY = y
}
if (y > maxY) {
maxY = y
}
}
}
}
rect = CGRect( x: CGFloat(minX), y: CGFloat(minY), width: CGFloat(maxX-minX), height: CGFloat(maxY-minY))
let imageScale:CGFloat = self.scale
let cgiImage = self.cgImage?.cropping(to: rect)
return UIImage(cgImage: cgiImage!, scale: imageScale, orientation: self.imageOrientation)
}
private func createARGBBitmapContextFromImage(inImage: CGImage) -> CGContext? {
let width = cgImage!.width
let height = cgImage!.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, bytesPerRow: bitmapBytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
return context
}
}
@AdamLantz
Copy link
Author

Based upon this gist targeting an older version of Swift:
https://gist.github.com/krooked/9c4c81557fc85bc61e51c0b4f3301e6e

@mehulparmar4ever
Copy link

some square particles seen in whole image, like blurry rectangle image. @AdamLantz , Do you feel like same in output image created by your above code?

@Iomegan
Copy link

Iomegan commented Apr 14, 2019

Should you be looking for a solution for NSImage (macOS): https://github.com/mipmip/NSImage-Trim/blob/master/Pod/Classes/NSImage%2BTrim.m

@Coder-ACJHP
Copy link

Great work, thank you so much

@v-rusu
Copy link

v-rusu commented Apr 22, 2020

Awesome work! But, there's a little catch here.

On line 13, you are drawing the cgImage to the cgContext:
context?.draw(cgImage!, in: rect)

But on line 15, the pixelData comes from the cgImage, instead of what's in the cgContext:
let pixelData = self.cgImage!.dataProvider!.data
The issue with this is that the ARGB order is no longer guaranteed.

I suggest the following edit for lines 15-16:

let redrawnCGImage = context.makeImage()
let pixelData = redrawnCGImage!.dataProvider!.data
        
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

@ZazAsif
Copy link

ZazAsif commented May 12, 2020

How can we do the same thing using metal?

@Coder-ACJHP
Copy link

Coder-ACJHP commented May 16, 2020 via email

@ZazAsif
Copy link

ZazAsif commented Jun 19, 2020

Awesome work! But, there's a little catch here.

On line 13, you are drawing the cgImage to the cgContext:
context?.draw(cgImage!, in: rect)

But on line 15, the pixelData comes from the cgImage, instead of what's in the cgContext:
let pixelData = self.cgImage!.dataProvider!.data
The issue with this is that the ARGB order is no longer guaranteed.

I suggest the following edit for lines 15-16:

let redrawnCGImage = context.makeImage()
let pixelData = redrawnCGImage!.dataProvider!.data
        
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

Excellent brother... Solved my problem

@ricsantos
Copy link

I tried the above edits, but found in practice only the first crop was correct. Any subsequent crops actually returned (almost) the entire image.

I think I will end up going with this solution: https://stackoverflow.com/a/48759198/883413

@aSeniyants
Copy link

thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment