Skip to content

Instantly share code, notes, and snippets.

@MojtabaHs
Created December 15, 2022 09:59
Show Gist options
  • Save MojtabaHs/745e73afb2eb2e4322e4af55294cffcb to your computer and use it in GitHub Desktop.
Save MojtabaHs/745e73afb2eb2e4322e4af55294cffcb to your computer and use it in GitHub Desktop.
UIImage Color Replace using pixel buffer
// MARK: - Color Replacement
extension UIImage {
func image(byReplacing sourceColor: UIColor, with destinationColor: UIColor, minTolerance: Float, maxTolerance: Float) -> UIImage? {
guard let inputCGImage = cgImage else {
print("unable to get cgImage")
return self
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let width = inputCGImage.width
let height = inputCGImage.height
let bytesPerPixel = 4
let bitsPerComponent = 8
let bytesPerRow = bytesPerPixel * width
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
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 self
}
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 self
}
let pixelBuffer = buffer.bindMemory(to: UInt8.self, capacity: bytesPerRow*height)
// components of the source color
guard let sourceComponents = sourceColor.cgColor.components else { return self }
let source255Components: [Float] = sourceComponents.map({
Float($0 * CGFloat(255))
})
// components of the destination color
guard let destinationComponents = destinationColor.cgColor.components else { return self }
let destination255Components: [Float] = destinationComponents.map({
Float($0 * CGFloat(255))
})
// loop through each pixel's components
for byte in stride(from: 0, to: bytesPerRow*height, by: 4) {
var rgb: [Float] = [ Float(pixelBuffer[byte]), Float(pixelBuffer[byte+1]), Float(pixelBuffer[byte+2]) ]
// delta components
var ratio: Float = 0.0
for subsequence in source255Components.enumerated() {
ratio += abs(rgb[subsequence.offset]-source255Components[subsequence.offset])
}
// ratio of 'how far away' each component is from the source color
ratio /= (255.0*3.0)
if ratio>maxTolerance { ratio = 1.0 } // if ratio is too far away, set it to max.
if ratio<minTolerance { ratio = 0.0 } // if ratio isn't far enough away, set it to min.
// blend color components
for subsequence in destination255Components.enumerated() {
pixelBuffer[byte+subsequence.offset] = UInt8(ratio*rgb[subsequence.offset]+(1.0-ratio)*destination255Components[subsequence.offset])
}
}
let outputCGImage = context.makeImage()!
let outputImage = UIImage(cgImage: outputCGImage, scale: self.scale, orientation: self.imageOrientation)
return outputImage
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment