Transparency of UIImage is Missed when Converting to Texture - ios

I am converting a UIImage to texture, then I am converting that texture to UIImage. The transparent areas are replaced with black color.
The function I am using to convert a UIImage to texture.
func imageToTexture(imageNamed: String, device: MTLDevice) -> MTLTexture {
let bytesPerPixel = 4
let bitsPerComponent = 8
var image = UIImage(named: imageNamed)!
let width = Int(image.size.width)
let height = Int(image.size.height)
let bounds = CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height))
var rowBytes = width * bytesPerPixel
var colorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: rowBytes, space: colorSpace, bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).rawValue)
context!.clear(bounds)
context?.translateBy(x: CGFloat(width), y: CGFloat(height))
// CGContextTranslateCTM(context!, CGFloat(width), CGFloat(height))
context?.scaleBy(x: -1.0, y: -1.0)
// CGContextScaleCTM(context!, -1.0, -1.0)
context?.draw(image.cgImage!, in: bounds)
// CGContextDrawImage(context, bounds, image.CGImage)
var texDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm , width: width, height: height, mipmapped: true)
var texture = device.makeTexture(descriptor: texDescriptor)
texture?.label = imageNamed
// var pixelsData = CGBitmapContextGetData(context!)
var pixelsData = context?.data
var region = MTLRegionMake2D(0, 0, width, height)
texture?.replace(region: region, mipmapLevel: 0, withBytes: pixelsData!, bytesPerRow: rowBytes)
makeImage(from: texture!)
return texture!
}
The function converting texture to UIImage. After changing UIImage to texture, I am calling to convert it back to UIImage. It is not.
func makeImage(from texture: MTLTexture) -> UIImage? {
let width = texture.width
let height = texture.height
let bytesPerRow = width * 4
let data = UnsafeMutableRawPointer.allocate(bytes: bytesPerRow * height, alignedTo: 4)
defer {
data.deallocate(bytes: bytesPerRow * height, alignedTo: 4)
}
let region = MTLRegionMake2D(0, 0, width, height)
texture.getBytes(data, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)
var buffer = vImage_Buffer(data: data, height: UInt(height), width: UInt(width), rowBytes: bytesPerRow)
let map: [UInt8] = [2, 1, 0, 3]
vImagePermuteChannels_ARGB8888(&buffer, &buffer, map, 0)
guard let colorSpace = CGColorSpace(name: CGColorSpace.genericRGBLinear) else { return nil }
guard let context = CGContext(data: data, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow,
space: colorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) else { return nil }
guard let cgImage = context.makeImage() else { return nil }
let x = UIImage(cgImage: cgImage)
return x;
}
Here the output image is black in the transparent areas. I don't know where I messed up.

Related

How to change color for point in UIImage?

I have a code that changes the color of a certain point in an image to transparent.
func processByPixel(in image: UIImage, byPoint: CGPoint) -> UIImage? {
guard let inputCGImage = image.cgImage else { print("unable to get cgImage"); return nil }
let colorSpace = CGColorSpaceCreateDeviceRGB()
let width = inputCGImage.width
let height = inputCGImage.height
let bytesPerPixel = 4
let bitsPerComponent = 8
let bytesPerRow = bytesPerPixel * width
let bitmapInfo = RGBA32.bitmapInfo
guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
print("Cannot create context!"); return nil
}
context.draw(inputCGImage, in: CGRect(x: 0, y: 0, width: width, height: height))
guard let buffer = context.data else { print("Cannot get context data!"); return nil }
let pixelBuffer = buffer.bindMemory(to: RGBA32.self, capacity: width * height)
let offset = Int(byPoint.x) * width + Int(byPoint.y)
pixelBuffer[offset] = .transparent
let outputCGImage = context.makeImage()!
let outputImage = UIImage(cgImage: outputCGImage, scale: image.scale, orientation: image.imageOrientation)
return outputImage
}
By tapping on the picture, I calculate the point on which it was clicked and pass it to this function.
The problem is that the color changes slightly with the offset.
For example I will pass CGPoint(x: 0, y:0) but change color to 0, 30
I think that the offset variable is not calculated correctly
Calculate the offset like this:
let offset = Int(byPoint.y) * width + Int(byPoint.x)
It is the number of vertical lines times line length plus x.

Wrong colors in bitmap drawing (Creating image by context)

Color distortion occurs when pictures are superimposed on each other. What am I doing wrong?
// My pixel struct
struct RGBA: Codable {
var r: UInt8
var g: UInt8
var b: UInt8
var a: UInt8
}
// Creating UIImage by pixels
let rgba = pixels.flatMap({ $0 })
let colorSpace = CGColorSpaceCreateDeviceRGB()
let data = UnsafeMutableRawPointer(mutating: rgba)
let bitmapContext = CGContext(data: data,
width: size.width,
height: size.height,
bitsPerComponent: 8,
bytesPerRow: 4 * size.width,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
guard let image = bitmapContext?.makeImage() else { return nil }
return UIImage(cgImage: image)
I tried using this code but it didn't work
CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue).rawValue)
Thanks

Generate gradient image texture on iOS

I need to generate a 256x1 texture on iOS. I have 4 colors Red, Green, Yellow, Blue with Red on one end and Blue on the other, and Yellow and Green at 3/4th and 1/4th position respectively. The colors in between need to be linearly interpolated. I need to use this texture in Metal for lookup in shader code. What is the easiest way to generate this texture in code?
Since MTKTextureLoader doesn't currently support the creation of 1D textures (this has been a feature request since at least 2016), you'll need to create your texture manually.
Assuming you already have your image loaded, you can ask it for its CGImage, then use this method to extract the pixel data and load it into a texture:
func texture1DForImage(_ cgImage: CGImage, device: MTLDevice) -> MTLTexture? {
let width = cgImage.width
let height = 0
let bytesPerRow = width * 4 // RGBA, 8 bits per component
let bitmapInfo: UInt32 = /* default byte order | */ CGImageAlphaInfo.premultipliedLast.rawValue
let colorSpace = CGColorSpace(name: CGColorSpace.sRGB)!
let context = CGContext(data: nil,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo)!
let bounds = CGRect(x: 0, y: 0, width: width, height: height)
context.draw(cgImage, in: bounds)
guard let data = context.data?.bindMemory(to: UInt8.self, capacity: bytesPerRow) else { return nil }
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm,
width: width,
height: height,
mipmapped: false)
textureDescriptor.textureType = .type1D
textureDescriptor.usage = [ .shaderRead ]
let texture = device.makeTexture(descriptor: textureDescriptor)!
texture.replace(region: MTLRegionMake1D(0, width), mipmapLevel: 0, withBytes: data, bytesPerRow: bytesPerRow)
return texture
}

How to convert bgra8Unorm iOS-Metal texture to rgba8Unorm texture?

I am working with iOS 11, XCode 9 and Metal 2. I have a MTLTexture with pixel format bgra8Unorm. I cannot change this pixel format, because according to pixelFormat documentation:
The pixel format for a Metal layer must be bgra8Unorm, bgra8Unorm_srgb, rgba16Float, BGRA10_XR, or bgra10_XR_sRGB.
The other pixel formats are not suitable for my application.
Now I want to create an UIImage from the texture. I am able to do so by extracting the pixel bytes from the texture (doc):
getBytes(_:bytesPerRow:bytesPerImage:from:mipmapLevel:slice:)
I am processing these bytes to get an UIImage:
func getUIImageForRGBAData(data: Data) -> UIImage? {
let d = (data as NSData)
let width = GlobalConfiguration.textureWidth
let height = GlobalConfiguration.textureHeight
let rowBytes = width * 4
let size = rowBytes * height
let pointer = malloc(size)
memcpy(pointer, d.bytes, d.length)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: pointer, width: width, height: height, bitsPerComponent: 8, bytesPerRow: rowBytes, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
let imgRef = context.makeImage()
let image = UIImage(cgImage: imgRef!)
return image
}
However, CGContext assumes that the pixels are in the rgba8 format. For example, red texture pixels are blue in the final UIImage. Is there a way to change the pixelFormat in this process to get the proper colors?
This function will swizzle the bytes of a .bgra8Unorm texture into RGBA order and create a UIImage from the data:
func makeImage(from texture: MTLTexture) -> UIImage? {
let width = texture.width
let height = texture.height
let bytesPerRow = width * 4
let data = UnsafeMutableRawPointer.allocate(bytes: bytesPerRow * height, alignedTo: 4)
defer {
data.deallocate(bytes: bytesPerRow * height, alignedTo: 4)
}
let region = MTLRegionMake2D(0, 0, width, height)
texture.getBytes(data, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)
var buffer = vImage_Buffer(data: data, height: UInt(height), width: UInt(width), rowBytes: bytesPerRow)
let map: [UInt8] = [2, 1, 0, 3]
vImagePermuteChannels_ARGB8888(&buffer, &buffer, map, 0)
guard let colorSpace = CGColorSpace(name: CGColorSpace.genericRGBLinear) else { return nil }
guard let context = CGContext(data: data, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow,
space: colorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) else { return nil }
guard let cgImage = context.makeImage() else { return nil }
return UIImage(cgImage: cgImage)
}
Caveat: This function is very expensive. Creating an image from a Metal texture every frame is almost never what you want to do.

Cannot invoke initializer for type 'CGBitmapInfo' with an argument list of type '(UInt32)'

I am add an image as a texture to a sphere in metal. using swift 2.0, but I got a error CGBitmapInfo(options), it said: Cannot invoke initializer for type 'CGBitmapInfo' with an argument list of type '(UInt32)'...Anybody knows how to fix it? Here is my code:
func textureForImage(image:UIImage, device:MTLDevice) -> MTLTexture?
{
let imageRef = image.CGImage
let width = CGImageGetWidth(imageRef)
let height = CGImageGetHeight(imageRef)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let rawData = calloc(height * width * 4, sizeof(UInt8))
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width
let bitsPerComponent = 8
let options = CGImageAlphaInfo.PremultipliedLast.rawValue | CGBitmapInfo.ByteOrder32Big.rawValue
let context = CGBitmapContextCreate(rawData,
width,
height,
bitsPerComponent,
bytesPerRow,
colorSpace,
CGBitmapInfo(options))
CGContextDrawImage(context, CGRectMake(0, 0, CGFloat(width), CGFloat(height)), imageRef)
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(.RGBA8Unorm,
width: Int(width),
height: Int(height),
mipmapped: true)
let texture = device.newTextureWithDescriptor(textureDescriptor)
let region = MTLRegionMake2D(0, 0, Int(width), Int(height))
texture.replaceRegion(region,
mipmapLevel: 0,
slice: 0,
withBytes: rawData,
bytesPerRow: bytesPerRow,
bytesPerImage: bytesPerRow * height)
free(rawData)
return texture
}
In the current version of Swift, the final parameter of CGBitmapContextCreate is a UInt32, so in place of CGBitmapInfo(options) above, you should just pass options (the expression assigned to options is inferred to have type UInt32).

Resources