For a given multi-color PNG UIImage
(with transparency), what is the best/Swift-idiomatic way to:
UIImage
There are a few related questions on SO but I haven't been able to find something that works.
setColor(p, c) sets the color of pixel (p) as input and a color (c), then sets the pixel to that color.
extension UIImage{ static func multiplyImageByConstantColor(image:UIImage,color:UIColor) -> UIImage{ let backgroundSize = image. size UIGraphicsBeginImageContext(backgroundSize) guard let ctx = UIGraphicsGetCurrentContext() else {return image} var backgroundRect=CGRect() backgroundRect.
The absolute simplest way to change colors of images (or icons in this case) is to use the SF Symbols where applicaple. This is a set of symbols Apple provides that can easily be used in your own app. You can download an app to help you find the correct symbol for you needs here.
You have to extract the pixel buffer of the image, at which point you can loop through, changing pixels as you see fit. At the end, create a new image from the buffer.
In Swift 3, this looks like:
func processPixels(in image: UIImage) -> 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("unable to create context") return nil } context.draw(inputCGImage, in: CGRect(x: 0, y: 0, width: width, height: height)) guard let buffer = context.data else { print("unable to get context data") return nil } let pixelBuffer = buffer.bindMemory(to: RGBA32.self, capacity: width * height) for row in 0 ..< Int(height) { for column in 0 ..< Int(width) { let offset = row * width + column if pixelBuffer[offset] == .black { pixelBuffer[offset] = .red } } } let outputCGImage = context.makeImage()! let outputImage = UIImage(cgImage: outputCGImage, scale: image.scale, orientation: image.imageOrientation) return outputImage } struct RGBA32: Equatable { private var color: UInt32 var redComponent: UInt8 { return UInt8((color >> 24) & 255) } var greenComponent: UInt8 { return UInt8((color >> 16) & 255) } var blueComponent: UInt8 { return UInt8((color >> 8) & 255) } var alphaComponent: UInt8 { return UInt8((color >> 0) & 255) } init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) { let red = UInt32(red) let green = UInt32(green) let blue = UInt32(blue) let alpha = UInt32(alpha) color = (red << 24) | (green << 16) | (blue << 8) | (alpha << 0) } static let red = RGBA32(red: 255, green: 0, blue: 0, alpha: 255) static let green = RGBA32(red: 0, green: 255, blue: 0, alpha: 255) static let blue = RGBA32(red: 0, green: 0, blue: 255, alpha: 255) static let white = RGBA32(red: 255, green: 255, blue: 255, alpha: 255) static let black = RGBA32(red: 0, green: 0, blue: 0, alpha: 255) static let magenta = RGBA32(red: 255, green: 0, blue: 255, alpha: 255) static let yellow = RGBA32(red: 255, green: 255, blue: 0, alpha: 255) static let cyan = RGBA32(red: 0, green: 255, blue: 255, alpha: 255) static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue static func ==(lhs: RGBA32, rhs: RGBA32) -> Bool { return lhs.color == rhs.color } }
For Swift 2 rendition, see previous revision of this answer.
For getting better result we can search color range in image pixels, referring to @Rob answer I made update and now the result is better.
func processByPixel(in image: UIImage) -> 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) for row in 0 ..< Int(height) { for column in 0 ..< Int(width) { let offset = row * width + column /* * Here I'm looking for color : RGBA32(red: 231, green: 239, blue: 247, alpha: 255) * and I will convert pixels color that in range of above color to transparent * so comparetion can done like this (pixelColorRedComp >= ourColorRedComp - 1 && pixelColorRedComp <= ourColorRedComp + 1 && green && blue) */ if pixelBuffer[offset].redComponent >= 230 && pixelBuffer[offset].redComponent <= 232 && pixelBuffer[offset].greenComponent >= 238 && pixelBuffer[offset].greenComponent <= 240 && pixelBuffer[offset].blueComponent >= 246 && pixelBuffer[offset].blueComponent <= 248 && pixelBuffer[offset].alphaComponent == 255 { pixelBuffer[offset] = .transparent } } } let outputCGImage = context.makeImage()! let outputImage = UIImage(cgImage: outputCGImage, scale: image.scale, orientation: image.imageOrientation) return outputImage }
I hope this helps someone 🎉
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With