Empty CGContext - swift

In Objective-C I was able to use CGBitmapContextCreate to create an empty context. I am trying to to the same in Swift 3, but for some reason it is nil. What am I missing?
let inImage: UIImage = ...
let width = Int(inImage.size.width)
let height = Int(inImage.size.height)
let bitmapBytesPerRow = width * 4
let bitmapByteCount = bitmapBytesPerRow * height
let pixelData = UnsafeMutablePointer<UInt8>.allocate(capacity: bitmapByteCount)
let context = CGContext(data: pixelData,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bitmapBytesPerRow,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.alphaOnly.rawValue)

I'm not sure what the code in the liked article would do, but two things are different with your Swift code.
bytesPerRow: width // width * 4 (== bitmapBytesPerRow)
space : NULL // CGColorSpaceCreateDeviceRGB()
The documentation of CGBitmapContextCreate does not say anything about supplying NULL for colorspace, but the header doc says The number of components for each pixel is specified by space, so, at least, CGColorSpaceCreateDeviceRGB() is not appropriate for alphaOnly (which should have only 1 component per pixel).
As far as I tested, this code returns non-nil CGContext:
let bitmapBytesPerRow = width //<-
let bitmapByteCount = bitmapBytesPerRow * height
let pixelData = UnsafeMutablePointer<UInt8>.allocate(capacity: bitmapByteCount)
let context = CGContext(data: pixelData,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bitmapBytesPerRow,
space: CGColorSpaceCreateDeviceGray(), //<-
bitmapInfo: CGImageAlphaInfo.alphaOnly.rawValue)
But, not sure if this works for your purpose or not.

I was working on this thing and faced same issue.
The solution I found is to use
var colorSpace = CGColorSpace.init(name: CGColorSpace.sRGB)!
let context = CGContext(data: nil,
width: Int(outputSize.width),
height: Int(outputSize.height),
bitsPerComponent: self.bitsPerComponent,
bytesPerRow: bitmapBytesPerRow,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
Actually my image's color space was Indexed Which can't be used to make a context.
So instead of using image's own colorSpace I made my own by using
var colorSpace = CGColorSpace.init(name: CGColorSpace.sRGB)!
and passed it to the context.
it resolved my error (nil context issue).

Related

Creating CGImage via byte array sometimes results in EXC_BAD_ACCESS

I'm trying to create a CGImage from a byte array, with something similar to this example function that generates a red square on a black field:
var bgrArray: [UInt8] = Array(repeating: 0, count: 480*480*4)
for i in 50..<250 {
for j in 50..<250 {
bgrArray[(i*480+j)*4] = 255
bgrArray[(i*480+j)*4+1] = 0
bgrArray[(i*480+j)*4+2] = 0
bgrArray[(i*480+j)*4+3] = 0
}
}
let releaseMaskImagePixelData: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
return
}
let provider = CGDataProvider(dataInfo: nil, data: bgrArray, size: bgrArray.count, releaseData: releaseMaskImagePixelData)!
let colorspace = CGColorSpaceCreateDeviceRGB()
let bitsPerComponent = 8;
let bitsPerPixel = 32;
let bytesPerRow = 4 * 480;
let bitmapInfo: CGBitmapInfo = [.byteOrder32Big, CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipLast.rawValue)]
let img = CGImage(width: 480, height: 480, bitsPerComponent: bitsPerComponent, bitsPerPixel: bitsPerPixel, bytesPerRow: bytesPerRow, space: colorspace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent)
let uiimage = UIImage(cgImage: img!)
return CIImage(image: uiimage)!
I have this exact bit of code copied into two different projects, in one project it always succeeds and in the other the line let img = CGImage(width: 480, height: 480, bitsPerComponent: bitsPerComponent, bitsPerPixel: bitsPerPixel, bytesPerRow: bytesPerRow, space: colorspace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) always fails with a EXC_BAD_ACCESS. The fact that it succeeds in one project and fails in another confuses me, is this bit of code able to be impacted by other things going on in an app?
Turns out this is due to optimization, seems like the exception occurs when optimization is turned on.

HEVC with alpha showing all frames at the same time

I'm making a video with transparent background using HEVC with alpha, based on a bunch of NSImages, a CVPixelBuffer pool and AVAssetWriter. The NSImages are rendered by an SCNRenderer's [snapshot][1] method.
To turn the NSImages into CVPixelbuffers so I can append them to the pool, I first get a new pixel buffer from the pool, then use this code
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer)
let colorSpace = CGColorSpaceCreateDeviceRGB()
guard let context = CGContext(
data: pixelData,
width: Int(image.size.width),
height: Int(image.size.height),
bitsPerComponent: 8,
bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
)
else { return }
let graphicsContext = NSGraphicsContext(cgContext: context, flipped: false)
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = graphicsContext
image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
NSGraphicsContext.restoreGraphicsState()
CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
In the exported movie, the colors look good, and the transparency kicks in fine.
But all the frames are added on top of each other, as in the example below.
SIDE NOTE: The weird thing is that if I obtain the pixel buffer with Metal (using the SCNRenderer's render method), but leave everything else as is, it works fine. That's why I'm suspecting that it might be my CGContext that is acting up. Why don't I just use Metal, then? Would love to, but I can't get antialiasing.

iOS: How to get pixel data array from CGImage in Swift

I need to get pixel data as byte array from a CGImage that can be RGB8, RGB16, GRAYSCALE8 or GRAYSCALE16. Previous solutions such as this one produce a dark or distorted image.
Per the link provided in your question, you can get the pixelData by doing
extension UIImage {
func pixelData() -> [UInt8]? {
let size = self.size
let dataSize = size.width * size.height * 4
var pixelData = [UInt8](repeating: 0, count: Int(dataSize))
let colorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: &pixelData,
width: Int(size.width),
height: Int(size.height),
bitsPerComponent: 8,
bytesPerRow: 4 * Int(size.width),
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue)
guard let cgImage = self.cgImage else { return nil }
context?.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
return pixelData
}
}
However, as a developer, the concerning objects here are the bitmapInfo and colorSpace. Your image may be getting distorted or colored differently depending on the information provided. The exact solution will be dependent upon how you obtained the image and what color schemes were provided from the image. You may just need to play with the variables.
I've never had an issue using CGColorSpaceCreateDeviceRGB() as my colorSpace but I have had to alter my bitmapInfo many times as my images were coming in as a different value.
Here is the location to reference the different types of bitmaps. More than likely though, you only need a variation of the CGImageAlphaInfo which can be located here.
If necessary, you can change the colorSpace. The default CGcolorSpace webpage can be found here. However, you could probably get away with one of the default ones located here

how to properly extract the array of numbers from an image in swift?

I'm trying to extract the array of numbers from a UIImage in swift but at the end I got only a bunch of zeros no useful information at all.
that's the code I wrote to try accomplishing this.
var photo = UIImage(named: "myphoto.jpg")!
var withAlpha = true
var bytesPerPixels: Int = withAlpha ? 4 : 3
var width: Int = Int(photo.size.width)
var height: Int = Int(photo.size.height)
var bitsPerComponent: Int = 8
var bytesPerRow = bytesPerPixels * width
var totalPixels = (bytesPerPixels * width) * height
var alignment = MemoryLayout<UInt32>.alignment
var data = UnsafeMutableRawPointer.allocate(byteCount: totalPixels, alignment: alignment )
var bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue).rawValue
var colorSpace = CGColorSpaceCreateDeviceRGB()
let ctx = CGContext(data: data, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo)
let bindedPointer: UnsafeMutablePointer<UInt32> = data.bindMemory(to: UInt32.self, capacity: totalPixels)
var pixels = UnsafeMutableBufferPointer.init(start: bindedPointer, count: totalPixels)
for p in pixels{
print(p, Date())
}
At the end I tried to bind the unsafeMutableRawPointer to extract the values but got no success,
what could I be missing here?
Thank you all in advance.
A few observations:
You need to draw the image to the context.
I’d also suggest that rather than creating a buffer that you have to manage manually, that you pass nil and let the OS create (and manage) that buffer for you.
Note that totalPixels should be just width * height.
Your code assumes the scale of the image is 1. That’s not always a valid assumption. I’d grab the cgImage and use its width and height.
Even if you have only three components, you still need to use 4 bytes per pixel.
Thus:
guard
let photo = UIImage(named: "myphoto.jpg”),
let cgImage = photo.cgImage
else { return }
let bytesPerPixels = 4
let width = cgImage.width
let height = cgImage.height
let bitsPerComponent: Int = 8
let bytesPerRow = bytesPerPixels * width
let totalPixels = width * height
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue).rawValue
let colorSpace = CGColorSpaceCreateDeviceRGB()
guard
let ctx = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo),
let data = ctx.data
else { return }
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
let pointer = data.bindMemory(to: UInt32.self, capacity: totalPixels)
let pixels = UnsafeMutableBufferPointer(start: pointer, count: totalPixels)
for p in pixels {
print(String(p, radix: 16), Date())
}
You need to draw the image into the context.
ctx?.draw(photo.cgImage!, in: CGRect(origin: .zero, size: photo.size))
Add that just after creating the CGContext.

Xcode 8 / Swift 3 - Type 'CGColorRenderingIntent' has no member 'RenderingIntentDefault'

I've successfully converted many errors to Swift 3 except for the last line. It works in Xcode 7 but not Xcode 8.
It's also worth noting that Xcode 7 has documentation on CGColorRenderingIntent but Xcode 8 doesn't.
Type 'CGColorRenderingIntent' has no member 'RenderingIntentDefault'
Code I'm working with:
import CoreImage
// omitted code
public func imageFromPixels(pixels: ([Pixel], width: Int, height: Int)) -> CIImage {
let bitsPerComponent = 8
let bitsPerPixel = 32
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) // alpha is last
let providerRef = CGDataProvider(data: NSData(bytes: pixels.0, length: pixels.0.count * sizeof(Pixel)))
let image = CGImageCreate(pixels.1, pixels.2, bitsPerComponent, bitsPerPixel, pixels.1 * sizeof(Pixel), rgbColorSpace, bitmapInfo, providerRef!, nil, true, CGColorRenderingIntent.RenderingIntentDefault)
return CIImage(CGImage: image!)
}
Apple documentation:
enum CGColorRenderingIntent : Int32 {
case RenderingIntentDefault
case RenderingIntentAbsoluteColorimetric
case RenderingIntentRelativeColorimetric
case RenderingIntentPerceptual
case RenderingIntentSaturation
}
Updated Code:
let image = CGImage(width: pixels.1,
height: pixels.2,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: pixels.1 * sizeof(Pixel),
space: rgbColorSpace,
bitmapInfo: bitmapInfo,
provider: providerRef!,
decode: nil,
shouldInterpolate: true,
intent: .defaultIntent)
return CGImage(CGImage: image!) // Incorrect argument label in call (have 'CGImage:', expected 'copy:')
⌘-click on the symbol CGColorRenderingIntent and you will see
public enum CGColorRenderingIntent : Int32 {
case defaultIntent
case absoluteColorimetric
case relativeColorimetric
case perceptual
case saturation
}
So it's
let image = CGImage(width: pixels.1,
height: pixels.2,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: pixels.1 * sizeof(Pixel),
space: rgbColorSpace,
bitmapInfo: bitmapInfo,
provider: providerRef!,
decode: nil,
shouldInterpolate: true,
intent: .defaultIntent)
return CIImage(cgImage: image!)
even the initializers of CGImage and CIImage have been changed.