Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change color of certain pixels in a UIImage

For a given multi-color PNG UIImage (with transparency), what is the best/Swift-idiomatic way to:

  1. create a duplicate UIImage
  2. find all black pixels in the copy and change them to red
  3. (return the modified copy)

There are a few related questions on SO but I haven't been able to find something that works.

like image 951
mjswensen Avatar asked Jul 27 '15 18:07

mjswensen


People also ask

How do you change the color of a pixel in Python?

setColor(p, c) sets the color of pixel (p) as input and a color (c), then sets the pixel to that color.

How do I change the UIImage color in Objective C?

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.

How do I change the color of an image in Swift?

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.


2 Answers

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.

like image 118
Rob Avatar answered Oct 16 '22 06:10

Rob


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 🎉

like image 22
Coder ACJHP Avatar answered Oct 16 '22 05:10

Coder ACJHP