CIImage with alpha from sourceFrame of a video - swift

I am trying to get CIImage with alpha, from a video that has an alpha.
The video is coded in HEVC with Alpha.
When I am doing this:
if let sourcePixel = request.sourceFrame(byTrackID: 3) {
let image = CIImage(cvPixelBuffer: sourcePixel
}
The alpha in the image is 1 (opaque).
If I test a pixel value from the relevant plane of sourcePixel, I got the correct alpha according to the value of the video.
if let sourcePixel = request.sourceFrame(byTrackID: 3) {
let results = CVPixelBufferLockBaseAddress(sourcePixel, .readOnly)
if let baseAddress = CVPixelBufferGetBaseAddressOfPlane(sourcePixel, 0) {
//Get width and height of buffer
let bytesPerRow = CVPixelBufferGetBytesPerRow(sourcePixel)
let buffer = baseAddress.assumingMemoryBound(to: UInt8.self)
var r = buffer[0]
var g = buffer[1]
var b = buffer[2]
var a = buffer[3]
print("value: ,(\(r),\(g),\(b),\(a))")
}
}
I tried to get the bitmap of the sourcePixel, and crate the CIImage from the bitmap.
But I didn't manage. I got image that looks like all the values are zero
if let sourcePixel = request.sourceFrame(byTrackID: layerInstruction.trackID) {
print (sourcePixel)
let results = CVPixelBufferLockBaseAddress(sourcePixel, .readOnly)
if let baseAddress = CVPixelBufferGetBaseAddressOfPlane(sourcePixel, 0) {
let width = CVPixelBufferGetWidth(sourcePixel)
let height = CVPixelBufferGetHeight(sourcePixel)
let bitmapData = Data(bytesNoCopy: baseAddress, count: width * height * 4, deallocator: .none)
let ciImage = CIImage(bitmapData: bitmapData, bytesPerRow: width * 4, size: CGSize(width: width, height: height), format: .ABGR8, colorSpace: CGColorSpaceCreateDeviceRGB())
}
}
Does using the bitmap is the right approach? if yes what am I doing wrong? If not what should I use?
And another related question, If I print the sourceFrame I got
<CVPixelBuffer 0x282458fa0 width=1080 height=1920 pixelFormat=420f iosurface=0x281778830 planes=2 poolName=22133:decode_1>.
<Plane 0 width=1080 height=1920 bytesPerRow=1088>.
<Plane 1 width=540 height=960 bytesPerRow=1088>
<attributes={
PixelFormatDescription = {
BitsPerComponent = 8;
ComponentRange = FullRange;
ContainsAlpha = 0;
ContainsGrayscale = 0;
ContainsRGB = 0;
ContainsYCbCr = 1;
FillExtendedPixelsCallback = {length = 24, bytes = 0x00000000000000001060248b010000000000000000000000};
IOSurfaceCoreAnimationCompatibility = 1;
IOSurfaceCoreAnimationCompatibilityHTPCOK = 1;
IOSurfaceOpenGLESFBOCompatibility = 1;
IOSurfaceOpenGLESTextureCompatibility = 1;
OpenGLESCompatibility = 1;
PixelFormat = 875704422;
Planes = (
{
BitsPerBlock = 8;
BlackBlock = {length = 1, bytes = 0x00};
},
{
BitsPerBlock = 16;
BlackBlock = {length = 2, bytes = 0x8080};
HorizontalSubsampling = 2;
VerticalSubsampling = 2;
}
);
};
} propagatedAttachments={
AlphaChannelMode = StraightAlpha;
CVFieldCount = 1;
CVImageBufferChromaLocationBottomField = Left;
CVImageBufferChromaLocationTopField = Left;
CVImageBufferColorPrimaries = "ITU_R_709_2";
CVImageBufferTransferFunction = "ITU_R_709_2";
CVImageBufferYCbCrMatrix = "ITU_R_709_2";
CVPixelAspectRatio = {
HorizontalSpacing = 1;
VerticalSpacing = 1;
};
} nonPropagatedAttachments={.
Why does the video have two planes?
why does it say that it contains no alpha?

Related

Histogram of UIImage - calculating data of Lookup Table

Code
This is my code for creating Lookup Table from UIImage - in order to use it as data for image's histogram. But I'm not quite happy with the performance time of it.
extension UIImage {
func getImageLookupTable() -> [UIColor: [Int]] {
let startTime = CFAbsoluteTimeGetCurrent()
guard let cgImage = self.cgImage else { return [:] }
guard let imageData = cgImage.dataProvider?.data as Data? else { return [:] }
let size = cgImage.width * cgImage.height
var LUT: [UIColor: [Int]] = [:]
//Grayscale, single channel image
if cgImage.bitsPerPixel == 8 {
let buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: size)
_ = imageData.copyBytes(to: buffer)
var gray_channel: [Int] = Array(repeating: Int(0), count: 256)
for pixel in buffer {
var grayVal : UInt32 = 0
if cgImage.byteOrderInfo == .orderDefault || cgImage.byteOrderInfo == .order16Little {
grayVal = UInt32((pixel) & 255)
gray_channel[Int(grayVal)]+=1
}
}
LUT[.black] = gray_channel
print(gray_channel)
}
//RGBA or BGRA, 4 channels image
if cgImage.bitsPerPixel == 32 {
let buffer = UnsafeMutableBufferPointer<UInt32>.allocate(capacity: size)
_ = imageData.copyBytes(to: buffer)
var red_channel:[Int] = Array(repeating: Int(0), count: 256)
var green_channel:[Int] = Array(repeating: Int(0), count: 256)
var blue_channel:[Int] = Array(repeating: Int(0), count: 256)
for pixel in buffer {
var r : UInt32 = 0
var g : UInt32 = 0
var b : UInt32 = 0
if cgImage.byteOrderInfo == .orderDefault || cgImage.byteOrderInfo == .order32Big {
r = pixel & 255
red_channel[Int(r)]+=1
g = (pixel >> 8) & 255
blue_channel[Int(g)]+=1
b = (pixel >> 16) & 255
green_channel[Int(b)]+=1
}
else if cgImage.byteOrderInfo == .order32Little {
r = (pixel >> 16) & 255
g = (pixel >> 8) & 255
b = pixel & 255
blue_channel[Int(b)]+=1
green_channel[Int(g)]+=1
red_channel[Int(r)]+=1
}
}
LUT[.red] = red_channel
LUT[.blue] = blue_channel
LUT[.green] = green_channel
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("Time elapsed for \(self.size): \(timeElapsed) s.")
return LUT
}
}
Problem
It takes around 10.15s for image (RGBA) with size: 3024 x 4032, which is definitely to much.
How can I improve it, what would you recommend ?

How to apply LUT (from .png) to an image? (Swift + Xcode)

I am trying to apply a LUT to an image by pressing a button.
Because, I am new to programming I mostly copied code and tried to modify it for my own project. I get no error messages, but when I try to press the button there is no effect on the image.
When I change the filter-name to sth. different than: "CIColorCube", the app even crashes with the error message: "Unexpectedly found nil while unwrapping an Optional value"
My LUT Image that I am using. (size: 512x512)
func colorCubeFilterFromLUT(imageName : String) -> CIFilter? {
let size = 512
let lutImage = UIImage(named: "LUT.png")!.cgImage
let lutWidth = lutImage!.width
let lutHeight = lutImage!.height
let rowCount = lutHeight / size
let columnCount = lutWidth / size
if ((lutWidth % size != 0) || (lutHeight % size != 0) || (rowCount * columnCount != size)) {
NSLog("Invalid colorLUT %#", "LUT.png");
return nil
}
let bitmap = getBytesFromImage(image: UIImage(named: "LUT.png"))!
let floatSize = MemoryLayout<Float>.size
let cubeData = UnsafeMutablePointer<Float>.allocate(capacity: size * size * size * 4 * floatSize)
var z = 0
var bitmapOffset = 0
for _ in 0 ..< rowCount {
for y in 0 ..< size {
let tmp = z
for _ in 0 ..< columnCount {
for x in 0 ..< size {
let alpha = Float(bitmap[bitmapOffset]) / 255.0
let red = Float(bitmap[bitmapOffset+1]) / 255.0
let green = Float(bitmap[bitmapOffset+2]) / 255.0
let blue = Float(bitmap[bitmapOffset+3]) / 255.0
let dataOffset = (z * size * size + y * size + x) * 4
cubeData[dataOffset + 3] = alpha
cubeData[dataOffset + 2] = red
cubeData[dataOffset + 1] = green
cubeData[dataOffset + 0] = blue
bitmapOffset += 4
}
z += 1
}
z = tmp
}
z += columnCount
}
let colorCubeData = NSData(bytesNoCopy: cubeData, length: size * size * size * 4 * floatSize, freeWhenDone: true)
// create CIColorCube Filter
let filter = CIFilter(name: "CIColorCube")
filter?.setValue(colorCubeData, forKey: "inputCubeData")
filter?.setValue(size, forKey: "inputCubeDimension")
return filter
}
func getBytesFromImage(image:UIImage?) -> [UInt8]?
{
var pixelValues: [UInt8]?
if let imageRef = image?.cgImage {
let width = Int(imageRef.width)
let height = Int(imageRef.height)
let bitsPerComponent = 8
let bytesPerRow = width * 4
let totalBytes = height * bytesPerRow
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
let colorSpace = CGColorSpaceCreateDeviceRGB()
var intensities = [UInt8](repeating: 0, count: totalBytes)
let contextRef = CGContext(data: &intensities, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo)
contextRef?.draw(imageRef, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)))
pixelValues = intensities
}
return pixelValues!
}
#IBAction func filterRetro(_ sender: Any) {
let inputImage = CIImage(image: photo.image!)
let filteredImage = inputImage?.applyingFilter("CIColorCube")
let renderedImage = context.createCGImage(filteredImage!, from: (filteredImage?.extent)!)
photo.image = UIImage(cgImage: renderedImage!)
}
Please add this code in your IBAction func filterRetro
if let photoImage = photo.image {
let inputImage = CIImage(image: photoImage)
let filteredImage = inputImage.applyingFilter("CIColorCube")
let filteredExtent = filteredImage.extent
/* hoping this will return a CGImage */
let renderedImage = context.createCGImage(filteredImage, from: filteredExtent)
photo.image = UIImage(cgImage: renderedImage)
}
The issue with optional wrapping inside the button press action is solved. But not with whole code. Let me know whether this works or not.
[edit]
inputImage.applyingFilter("name") method is calling the CIFilter.init(name). For which doc says,
The name of the filter. You must make sure the name is spelled
correctly, otherwise your app will run but not produce any output
images. For that reason, you should check for the existence of the
filter after calling this method.
I would suggest using a separate variable to create the filter and if the filter exists then only apply to the image.
let filter = CIFilter("CIColorCube")
if filter {
let filteredImage = inputImage.applyingFilter("CIColorCube")
}
try this.
thanks,
J

Drawing pixels on the screen using CoreGraphics in Swift

The code below is trying to set pixels to an offline bitmap and draw the bitmap to the screen. Unfortunately, it crashes.
import UIKit
class GameView: UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
func createBitmapContext(pixelsWide: Int, _ pixelsHigh: Int) -> CGContextRef? {
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * pixelsWide
let bitsPerComponent = 8
let byteCount = (bytesPerRow * pixelsHigh)
let colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)
let pixels = UnsafeMutablePointer<CUnsignedChar>.alloc(byteCount)
if pixels == nil {
return nil
}
let bitmapInfo = CGImageAlphaInfo.PremultipliedFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue
let context = CGBitmapContextCreate(pixels, pixelsWide, pixelsHigh, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo)
return context
}
override func drawRect(rect: CGRect) {
let width = 200
let height = 300
let boundingBox = CGRectMake(0, 0, CGFloat(width), CGFloat(height))
let context = createBitmapContext(width, height)
let data = CGBitmapContextGetData(context)
var currentPixel: [UInt32] = unsafeBitCast(data, [UInt32].self)
var n = 0
for var j = 0; j < height; j++ {
for var i = 0; i < width; i++ {
currentPixel[n++] = 0
}
}
let image = CGBitmapContextCreateImage(context!)
CGContextDrawImage(context!, boundingBox, image)
}
}
There are couple changes required.
1. Crashing because of there was memory overrun.
2. You are creating image from newly created context and writing to same context instead of current drawing context.
Use this modified drawRect Function:
override func drawRect(rect: CGRect) {
let width = 200
let height = 300
let boundingBox = CGRectMake(0, 0, CGFloat(width), CGFloat(height))
let context = createBitmapContext(width, height)
let data = CGBitmapContextGetData(context)
let pixels = UnsafeMutablePointer<CUnsignedChar>(data)
var n = 0
for var j = 0; j < height; j++ {
for var i = 0; i < width; i++ {
pixels[n++] = 0 //B
pixels[n++] = 255 //G
pixels[n++] = 0 //R
pixels[n++] = 255 //A
}
}
let image = CGBitmapContextCreateImage(context!)
if let currentContext: CGContext! = UIGraphicsGetCurrentContext() {
UIImagePNGRepresentation(UIImage(CGImage:image!))?.writeToFile("/Users/admin/Desktop/aaaaa.png", atomically: true)
CGContextDrawImage(currentContext, boundingBox, image)
}
}

Accessing one class from another: Instance Member cannot be used on type

I'm getting into Swift by using the Xcode Playground, and I'm following an online course regarding simple image filtering.
So far, here's my code:
import UIKit
let image = UIImage(named: "sample")!
let rgbaImage = RGBAImage(image: image)
class ImageInfo {
let rgbaImage = RGBAImage(image: image)
func getAvgCols() -> [String:Int] {
var totalRed = 0
var totalGreen = 0
var totalBlue = 0
var avgCol = [String:Int]()
for y in 0..<rgbaImage!.height{
for x in 0..<rgbaImage!.width{
let index = y * rgbaImage!.width + x
let pixel = rgbaImage!.pixels[index]
totalRed += Int(pixel.red)
totalGreen += Int(pixel.green)
totalBlue += Int(pixel.blue)
}
}
let pixelCount = rgbaImage!.width * rgbaImage!.height
avgCol["Red"] = (totalRed / pixelCount)
avgCol["Green"] = (totalGreen / pixelCount)
avgCol["Blue"] = (totalBlue / pixelCount)
return avgCol
}
}
let imageInfo = ImageInfo()
var avgRed = imageInfo.getAvgCols()["Red"]!
var avgGreen = imageInfo.getAvgCols()["Green"]!
var avgBlue = imageInfo.getAvgCols()["Blue"]!
var modifier = 20
// IMAGE EDITING LOOP
for y in 0..<rgbaImage!.height{
for x in 0..<rgbaImage!.width{
let index = y * rgbaImage!.width + x
var pixel = rgbaImage!.pixels[index]
let redDelta = Int(pixel.red) - avgRed
let greenDelta = Int(pixel.green) - avgGreen
let blueDelta = Int(pixel.blue) - avgBlue
let redModifier = Double(modifier) * 0.49
let greenModifier = Double(modifier) * 0.25
let blueModifier = Double(modifier) * 0.07
pixel.red = UInt8(max(min(255, avgRed + Int(redModifier) * redDelta),0))
pixel.green = UInt8(max(min(255, avgGreen + Int(greenModifier) * greenDelta),0))
pixel.blue = UInt8(max(min(255, avgBlue + Int(blueModifier) * blueDelta),0))
rgbaImage!.pixels[index] = pixel
}
}
let newImage = rgbaImage!.toUIImage()!
So far, that works well. The last bit of code modifies the image on a per-pixel basis, and the end result is a poor-man's attempt at some sepia colour.
The problem, however, is when I stick the entire image editing loop in another class, like so:
class ImageEdit {
let imageInfo = ImageInfo()
var avgRed = imageInfo.getAvgCols()["Red"]!
var avgGreen = imageInfo.getAvgCols()["Green"]!
var avgBlue = imageInfo.getAvgCols()["Blue"]!
func applyFilter(filter: String, modifier: Int) -> UIImage{
for y in 0..<rgbaImage!.height{
for x in 0..<rgbaImage!.width{
let index = y * rgbaImage!.width + x
var pixel = rgbaImage!.pixels[index]
let redDelta = Int(pixel.red) - avgRed
let greenDelta = Int(pixel.green) - avgGreen
let blueDelta = Int(pixel.blue) - avgBlue
let redModifier = Double(modifier) * 0.49
let greenModifier = Double(modifier) * 0.25
let blueModifier = Double(modifier) * 0.07
pixel.red = UInt8(max(min(255, avgRed + Int(redModifier) * redDelta),0))
pixel.green = UInt8(max(min(255, avgGreen + Int(greenModifier) * greenDelta),0))
pixel.blue = UInt8(max(min(255, avgBlue + Int(blueModifier) * blueDelta),0))
rgbaImage!.pixels[index] = pixel
}
}
let newImage = rgbaImage!.toUIImage()!
return newImage
}
}
NOTE: Since I am using the play ground, both classes are in the same file. For clarification just take my previous code, and replace everything below class ImageInfo with class ImageEdit
I would need access to avgRed, avgGreen, and avgBlue, but all I'm getting is
instance member 'imageInfo' cannot be used on type 'ImageEdit'
and since I'm a noob at Swift, I'm not exactly sure how to fix it.
Did I miss anything?
EDIT: Here's RGBAImage. I did not write this code, it is free to download to anyone who takes the online course.
import UIKit
public struct Pixel {
public var value: UInt32
public var red: UInt8 {
get {
return UInt8(value & 0xFF)
}
set {
value = UInt32(newValue) | (value & 0xFFFFFF00)
}
}
public var green: UInt8 {
get {
return UInt8((value >> 8) & 0xFF)
}
set {
value = (UInt32(newValue) << 8) | (value & 0xFFFF00FF)
}
}
public var blue: UInt8 {
get {
return UInt8((value >> 16) & 0xFF)
}
set {
value = (UInt32(newValue) << 16) | (value & 0xFF00FFFF)
}
}
public var alpha: UInt8 {
get {
return UInt8((value >> 24) & 0xFF)
}
set {
value = (UInt32(newValue) << 24) | (value & 0x00FFFFFF)
}
}
}
public struct RGBAImage {
public var pixels: UnsafeMutableBufferPointer<Pixel>
public var width: Int
public var height: Int
public init?(image: UIImage) {
guard let cgImage = image.CGImage else { return nil }
// Redraw image for correct pixel format
let colorSpace = CGColorSpaceCreateDeviceRGB()
var bitmapInfo: UInt32 = CGBitmapInfo.ByteOrder32Big.rawValue
bitmapInfo |= CGImageAlphaInfo.PremultipliedLast.rawValue & CGBitmapInfo.AlphaInfoMask.rawValue
width = Int(image.size.width)
height = Int(image.size.height)
let bytesPerRow = width * 4
let imageData = UnsafeMutablePointer<Pixel>.alloc(width * height)
guard let imageContext = CGBitmapContextCreate(imageData, width, height, 8, bytesPerRow, colorSpace, bitmapInfo) else { return nil }
CGContextDrawImage(imageContext, CGRect(origin: CGPointZero, size: image.size), cgImage)
pixels = UnsafeMutableBufferPointer<Pixel>(start: imageData, count: width * height)
}
public func toUIImage() -> UIImage? {
let colorSpace = CGColorSpaceCreateDeviceRGB()
var bitmapInfo: UInt32 = CGBitmapInfo.ByteOrder32Big.rawValue
bitmapInfo |= CGImageAlphaInfo.PremultipliedLast.rawValue & CGBitmapInfo.AlphaInfoMask.rawValue
let bytesPerRow = width * 4
let imageContext = CGBitmapContextCreateWithData(pixels.baseAddress, width, height, 8, bytesPerRow, colorSpace, bitmapInfo, nil, nil)
guard let cgImage = CGBitmapContextCreateImage(imageContext) else {return nil}
let image = UIImage(CGImage: cgImage)
return image
}
}
The problem is that you are calling the attribute imageInfo inside the ImageEdit before initialising the class.
It is only when the initialisation is called that everything becomes something. So avgRed can't get values from a function of imageInfo because it has not been constructed yet.
Doing this inside a function (init() is basically a function) is just find. Doing this inside a class doesn't work.
This does not work :
class A {
let attribute : Int = 0
init() {
}
}
class B {
let attributeA = A()
let attributeB = attributeA.attribute // this is your problem
}
This does :
For all the attributes that rely on other attributes to be constructed, you use a late init. All the attributes that rely on external input, failable functions,... you declare as an optional.
Late init : var / let attrName : Type
Optional : var / let attrName : Type ? / !
class B {
let attributeA = A()
let attributeB : Int
var attributeC : Int?
init () {
attributeB = attributeA.attribute
attributeC = somethingOptional()
}
func somethingOptional() -> Int? {
return 0 // not really optional, but just illustration
}
}
So for you it would be something like this :
class ImageEdit {
let imageInfo = ImageInfo()
let dict : [String:Int]
init () {
dict = imageInfo.getAvgCols()
}
}

Detect cropping rectangle for UIImage with transparent background

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;
}
}
Refer CoreImage Slides on WWDC.
Your answer is direct.