using the accelerate framework which I don’t really fully comprehend if I am honest, I am trying to create a flood fill for a UIImage. I have a UIImage extension I’ve create but the result I get is unpredictable if the image is of anything. If the UIImage is a single color image where every pixel is identical then the result is as expoected but if I take an image from my camera roll and attempt to run the floodfill I get a mess. This is the code I’m working with
import Accelerate
import Accelerate.vImage
import Foundation
import CoreGraphics
extension UIImage {
func floodFill(seedPoint: CGPoint, replacementColor: UIColor) -> UIImage? {
guard var buffer = self.toVImage() else {
return nil
}
guard var maskBuffer = createMaskBuffer(width: Int(buffer.width), height: Int(buffer.height)) else {
return nil
}
let replacementRGBA = replacementColor.toRGBA()
// Create a contiguous memory block for RGBA components
var rgbaComponents: [UInt8] = [replacementRGBA.0, replacementRGBA.1, replacementRGBA.2, replacementRGBA.3]
let rgbaPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: 4)
rgbaPointer.initialize(from: &rgbaComponents, count: 4)
// Pass the pointer to the array
vImageFloodFill_ARGB8888(&buffer, &maskBuffer, vImagePixelCount(seedPoint.x), vImagePixelCount(seedPoint.y), rgbaPointer, 4, vImage_Flags(kvImageNoFlags))
rgbaPointer.deallocate()
guard let cgImage = vImageToCGImage(buffer: buffer) else {
return nil
}
return UIImage(cgImage: cgImage)
}
private func toVImage() -> vImage_Buffer? {
guard let cgImage = self.cgImage else {
return nil
}
var format = vImage_CGImageFormat(bitsPerComponent: 8,
bitsPerPixel: 32,
colorSpace: nil,
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
version: 0,
decode: nil,
renderingIntent: .defaultIntent)
var buffer = vImage_Buffer()
let error = vImageBuffer_InitWithCGImage(&buffer, &format, nil, cgImage, vImage_Flags(kvImageNoFlags))
guard error == kvImageNoError else {
print("Error creating vImage buffer: (error)")
return nil
}
return buffer
}
private func vImageToCGImage(buffer: vImage_Buffer) -> CGImage? {
guard let context = CGContext(data: buffer.data,
width: Int(buffer.width),
height: Int(buffer.height),
bitsPerComponent: 8,
bytesPerRow: buffer.rowBytes,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).rawValue) else {
return nil
}
return context.makeImage()
}
private func createMaskBuffer(width: Int, height: Int) -> vImage_Buffer? {
let bytesPerPixel = 4
let bytesPerRow = width * bytesPerPixel
let byteCount = bytesPerRow * height
let maskData = UnsafeMutablePointer<UInt8>.allocate(capacity: byteCount)
var maskBuffer = vImage_Buffer(data: maskData, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow)
memset(maskBuffer.data, 0, byteCount)
return maskBuffer
}
}
extension UIColor {
func toRGBA() -> (UInt8, UInt8, UInt8, UInt8) {
var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return (UInt8(red * 255), UInt8(green * 255), UInt8(blue * 255), UInt8(alpha * 255))
}
}
I have tried several approaches to passing the replacement color. Am I trying to do something impossible here is the vImageFloodFill_ARGB8888 flood fill function not designed to be an actual flood fill but only to fill empty images?
April G is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.