How to create a Magic Wand selection tool like photoshop

I’ve tried everything including ChatGPT and i just cannot get my magic wand selection tool written in swift to work. The only information I’ve found on StackOverflow has not been helpful at all. And there does not seem to be a library or code snippet out there to get me moving in the right direction.

i’m just going to post the whole class I’m working on.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>struct Coordinate: Hashable {
let x: Int
let y: Int
}
class MagicWandSelection: NSObject {
override init() {
super.init()
}
func floodFill(x: Int, y: Int, width: Int, height: Int, maskData: [UInt8], checked: inout [Bool]) -> [Coordinate] {
var stack = [Coordinate(x: x, y: y)]
var foundPixels = [Coordinate]()
while let point = stack.popLast() {
let index = point.y * width + point.x
if point.x < 0 || point.x >= width || point.y < 0 || point.y >= height || checked[index] || maskData[index] != 255 {
continue
}
checked[index] = true
foundPixels.append(point)
let directions = [Coordinate(x: 0, y: -1), Coordinate(x: 1, y: 0), Coordinate(x: 0, y: 1), Coordinate(x: -1, y: 0)]
for direction in directions {
let nextPoint = Coordinate(x: point.x + direction.x, y: point.y + direction.y)
stack.append(nextPoint)
}
}
return foundPixels
}
func newMaskWithFloodFill(from point: CGPoint, in image: UIImage, selectedColor: UIColor, tolerance: Float) -> UIImage? {
guard let cgImage = image.cgImage else { return nil }
let width = cgImage.width
let height = cgImage.height
let bytesPerPixel = 4
let bytesPerRow = width * bytesPerPixel
let bitsPerComponent = 8
var pixelData = [UInt8](repeating: 0, count: width * height * bytesPerPixel)
guard let context = CGContext(data: &pixelData,
width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil }
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
let startPixelIndex = (Int(point.y) * width + Int(point.x)) * bytesPerPixel
let startPixel = Array(pixelData[startPixelIndex..<(startPixelIndex + bytesPerPixel)])
var maskData = [UInt8](repeating: 0, count: width * height)
floodFill(&pixelData, &maskData, width: width, height: height, x: Int(point.x), y: Int(point.y), startPixel: startPixel, tolerance: tolerance)
return createMaskImage(from: maskData, width: width, height: height)
}
func floodFill(_ pixelData: inout [UInt8], _ maskData: inout [UInt8], width: Int, height: Int, x: Int, y: Int, startPixel: [UInt8], tolerance: Float) {
var stack = [(Int, Int)]()
stack.append((x, y))
while !stack.isEmpty {
let (currentX, currentY) = stack.removeLast()
let index = (currentY * width + currentX) * 4
let maskIndex = currentY * width + currentX
if maskData[maskIndex] > 0 { continue }
let currentPixel = Array(pixelData[index..<(index + 4)])
if !colorsMatch(pixel1: currentPixel, pixel2: startPixel, tolerance: tolerance) {
continue
}
maskData[maskIndex] = 255
if currentX > 0 {
stack.append((currentX - 1, currentY))
}
if currentX < width - 1 {
stack.append((currentX + 1, currentY))
}
if currentY > 0 {
stack.append((currentX, currentY - 1))
}
if currentY < height - 1 {
stack.append((currentX, currentY + 1))
}
}
}
func colorsMatch(pixel1: [UInt8], pixel2: [UInt8], tolerance: Float) -> Bool {
let r1 = Float(pixel1[0]) / 255.0, g1 = Float(pixel1[1]) / 255.0, b1 = Float(pixel1[2]) / 255.0, a1 = Float(pixel1[3]) / 255.0
let r2 = Float(pixel2[0]) / 255.0, g2 = Float(pixel2[1]) / 255.0, b2 = Float(pixel2[2]) / 255.0, a2 = Float(pixel2[3]) / 255.0
let dr = r1 - r2, dg = g1 - g2, db = b1 - b2, da = a1 - a2
let distance = sqrt(dr * dr + dg * dg + db * db + da * da)
return distance < tolerance
}
func createMaskImage(from maskData: [UInt8], width: Int, height: Int) -> UIImage? {
let bytesPerPixel = 1
let bytesPerRow = width * bytesPerPixel
let bitsPerComponent = 8
var maskDataModified = maskData.map { $0 > 0 ? UInt8(255) : UInt8(0) }
guard let context = CGContext(data: &maskDataModified,
width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceGray(),
bitmapInfo: CGImageAlphaInfo.none.rawValue) else { return nil }
guard let maskCgImage = context.makeImage() else { return nil }
return UIImage(cgImage: maskCgImage)
}
func createBezierPath(from maskImage: UIImage, seed: CGPoint) -> UIBezierPath? {
guard let cgImage = maskImage.cgImage else { return nil }
let width = Int(maskImage.size.width)
let height = Int(maskImage.size.height)
let bytesPerPixel = 1
let bytesPerRow = width * bytesPerPixel
let bitsPerComponent = 8
var maskData = [UInt8](repeating: 0, count: width * height)
guard let context = CGContext(data: &maskData,
width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceGray(),
bitmapInfo: CGImageAlphaInfo.none.rawValue) else { return nil }
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
let path = UIBezierPath()
let seedPoint = Coordinate(x: Int(seed.x), y: Int(seed.y))
// Ensure the seed point is within bounds and on a white pixel
guard seedPoint.x >= 0 && seedPoint.x < width && seedPoint.y >= 0 && seedPoint.y < height else { return nil }
guard maskData[seedPoint.y * width + seedPoint.x] == 255 else { return nil }
var currentPoint = seedPoint
let directions = [
Coordinate(x: 0, y: -1), // Up
Coordinate(x: 1, y: 0), // Right
Coordinate(x: 0, y: 1), // Down
Coordinate(x: -1, y: 0) // Left
]
// Find the starting edge pixel by moving up and left from the seed pixel
var startPoint: Coordinate? = nil
for direction in [Coordinate(x: 0, y: -1), Coordinate(x: -1, y: 0)] {
var testPoint = seedPoint
while testPoint.x >= 0 && testPoint.y >= 0 {
if maskData[testPoint.y * width + testPoint.x] == 255 && isEdgePixel(x: testPoint.x, y: testPoint.y, width: width, height: height, maskData: maskData) {
startPoint = testPoint
break
}
testPoint = Coordinate(x: testPoint.x + direction.x, y: testPoint.y + direction.y)
}
if startPoint != nil {
break
}
}
guard let firstPoint = startPoint else { return nil }
currentPoint = firstPoint
path.move(to: CGPoint(x: firstPoint.x, y: firstPoint.y))
var visited: Set<Coordinate> = [firstPoint]
repeat {
var foundNext = false
for direction in directions {
let nextPoint = Coordinate(x: currentPoint.x + direction.x, y: currentPoint.y + direction.y)
let nextIndex = nextPoint.y * width + nextPoint.x
if nextPoint.x >= 0 && nextPoint.x < width && nextPoint.y >= 0 && nextPoint.y < height &&
maskData[nextIndex] == 255 &&
!visited.contains(nextPoint) &&
isEdgePixel(x: nextPoint.x, y: nextPoint.y, width: width, height: height, maskData: maskData) {
path.addLine(to: CGPoint(x: nextPoint.x, y: nextPoint.y))
visited.insert(nextPoint)
currentPoint = nextPoint
foundNext = true
break
}
}
if !foundNext {
break
}
} while currentPoint != firstPoint
path.close()
return path
}
}
extension MagicWandSelection{
func isEdgePixel(x: Int, y: Int, width: Int, height: Int, maskData: [UInt8]) -> Bool {
if x == 0 || y == 0 || x == width - 1 || y == height - 1 {
return true
}
let directions = [
Coordinate(x: 0, y: -1), // Up
Coordinate(x: 1, y: 0), // Right
Coordinate(x: 0, y: 1), // Down
Coordinate(x: -1, y: 0) // Left
]
for direction in directions {
let neighborX = x + direction.x
let neighborY = y + direction.y
if neighborX >= 0 && neighborX < width && neighborY >= 0 && neighborY < height {
let neighborIndex = neighborY * width + neighborX
if maskData[neighborIndex] == 0 {
return true
}
}
}
return false
}
func createMagicWandPath(from maskImage: UIImage, seed: CGPoint) -> UIBezierPath? {
guard let cgImage = maskImage.cgImage else { return nil }
let width = Int(maskImage.size.width)
let height = Int(maskImage.size.height)
let bytesPerPixel = 1
let bytesPerRow = width * bytesPerPixel
let bitsPerComponent = 8
var maskData = [UInt8](repeating: 0, count: width * height)
guard let context = CGContext(data: &maskData,
width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceGray(),
bitmapInfo: CGImageAlphaInfo.none.rawValue) else { return nil }
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
let seedPoint = Coordinate(x: Int(seed.x), y: Int(seed.y))
// Ensure the seed point is within bounds and on a white pixel
guard seedPoint.x >= 0 && seedPoint.x < width && seedPoint.y >= 0 && seedPoint.y < height else { return nil }
guard maskData[seedPoint.y * width + seedPoint.x] == 255 else { return nil }
// Find the starting edge pixel
guard let startPixel = findStartingEdgePixel(from: seedPoint, width: width, height: height, maskData: maskData) else {
return nil
}
// Find edge pixels and construct the path
if let edgePath = traceEdgePath(from: startPixel, width: width, height: height, maskData: maskData) {
return edgePath
}
return nil
}
func findStartingEdgePixel(from seedPoint: Coordinate, width: Int, height: Int, maskData: [UInt8]) -> Coordinate? {
for y in 0..<height {
for x in 0..<width {
let index = y * width + x
if maskData[index] == 255 && isEdgePixel(x: x, y: y, width: width, height: height, maskData: maskData) {
return Coordinate(x: x, y: y)
}
}
}
return nil
}
func traceEdgePath(from startPoint: Coordinate, width: Int, height: Int, maskData: [UInt8]) -> UIBezierPath? {
let directions = [
Coordinate(x: 0, y: -1), // Up
Coordinate(x: 1, y: 0), // Right
Coordinate(x: 0, y: 1), // Down
Coordinate(x: -1, y: 0), // Left
Coordinate(x: -1, y: -1), // Up-Left
Coordinate(x: 1, y: -1), // Up-Right
Coordinate(x: -1, y: 1), // Down-Left
Coordinate(x: 1, y: 1) // Down-Right
]
let path = UIBezierPath()
var visited = Set<Coordinate>()
var currentPoint = startPoint
var firstPoint = true
repeat {
visited.insert(currentPoint)
if firstPoint {
path.move(to: CGPoint(x: currentPoint.x, y: currentPoint.y))
firstPoint = false
} else {
path.addLine(to: CGPoint(x: currentPoint.x, y: currentPoint.y))
}
var foundNext = false
for direction in directions {
let nextPoint = Coordinate(x: currentPoint.x + direction.x, y: currentPoint.y + direction.y)
if nextPoint.x >= 0 && nextPoint.x < width && nextPoint.y >= 0 && nextPoint.y < height &&
maskData[nextPoint.y * width + nextPoint.x] == 255 && !visited.contains(nextPoint) {
currentPoint = nextPoint
foundNext = true
break
}
}
if !foundNext {
break
}
} while currentPoint != startPoint
path.close()
return path
}
}
</code>
<code>struct Coordinate: Hashable { let x: Int let y: Int } class MagicWandSelection: NSObject { override init() { super.init() } func floodFill(x: Int, y: Int, width: Int, height: Int, maskData: [UInt8], checked: inout [Bool]) -> [Coordinate] { var stack = [Coordinate(x: x, y: y)] var foundPixels = [Coordinate]() while let point = stack.popLast() { let index = point.y * width + point.x if point.x < 0 || point.x >= width || point.y < 0 || point.y >= height || checked[index] || maskData[index] != 255 { continue } checked[index] = true foundPixels.append(point) let directions = [Coordinate(x: 0, y: -1), Coordinate(x: 1, y: 0), Coordinate(x: 0, y: 1), Coordinate(x: -1, y: 0)] for direction in directions { let nextPoint = Coordinate(x: point.x + direction.x, y: point.y + direction.y) stack.append(nextPoint) } } return foundPixels } func newMaskWithFloodFill(from point: CGPoint, in image: UIImage, selectedColor: UIColor, tolerance: Float) -> UIImage? { guard let cgImage = image.cgImage else { return nil } let width = cgImage.width let height = cgImage.height let bytesPerPixel = 4 let bytesPerRow = width * bytesPerPixel let bitsPerComponent = 8 var pixelData = [UInt8](repeating: 0, count: width * height * bytesPerPixel) guard let context = CGContext(data: &pixelData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil } context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) let startPixelIndex = (Int(point.y) * width + Int(point.x)) * bytesPerPixel let startPixel = Array(pixelData[startPixelIndex..<(startPixelIndex + bytesPerPixel)]) var maskData = [UInt8](repeating: 0, count: width * height) floodFill(&pixelData, &maskData, width: width, height: height, x: Int(point.x), y: Int(point.y), startPixel: startPixel, tolerance: tolerance) return createMaskImage(from: maskData, width: width, height: height) } func floodFill(_ pixelData: inout [UInt8], _ maskData: inout [UInt8], width: Int, height: Int, x: Int, y: Int, startPixel: [UInt8], tolerance: Float) { var stack = [(Int, Int)]() stack.append((x, y)) while !stack.isEmpty { let (currentX, currentY) = stack.removeLast() let index = (currentY * width + currentX) * 4 let maskIndex = currentY * width + currentX if maskData[maskIndex] > 0 { continue } let currentPixel = Array(pixelData[index..<(index + 4)]) if !colorsMatch(pixel1: currentPixel, pixel2: startPixel, tolerance: tolerance) { continue } maskData[maskIndex] = 255 if currentX > 0 { stack.append((currentX - 1, currentY)) } if currentX < width - 1 { stack.append((currentX + 1, currentY)) } if currentY > 0 { stack.append((currentX, currentY - 1)) } if currentY < height - 1 { stack.append((currentX, currentY + 1)) } } } func colorsMatch(pixel1: [UInt8], pixel2: [UInt8], tolerance: Float) -> Bool { let r1 = Float(pixel1[0]) / 255.0, g1 = Float(pixel1[1]) / 255.0, b1 = Float(pixel1[2]) / 255.0, a1 = Float(pixel1[3]) / 255.0 let r2 = Float(pixel2[0]) / 255.0, g2 = Float(pixel2[1]) / 255.0, b2 = Float(pixel2[2]) / 255.0, a2 = Float(pixel2[3]) / 255.0 let dr = r1 - r2, dg = g1 - g2, db = b1 - b2, da = a1 - a2 let distance = sqrt(dr * dr + dg * dg + db * db + da * da) return distance < tolerance } func createMaskImage(from maskData: [UInt8], width: Int, height: Int) -> UIImage? { let bytesPerPixel = 1 let bytesPerRow = width * bytesPerPixel let bitsPerComponent = 8 var maskDataModified = maskData.map { $0 > 0 ? UInt8(255) : UInt8(0) } guard let context = CGContext(data: &maskDataModified, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue) else { return nil } guard let maskCgImage = context.makeImage() else { return nil } return UIImage(cgImage: maskCgImage) } func createBezierPath(from maskImage: UIImage, seed: CGPoint) -> UIBezierPath? { guard let cgImage = maskImage.cgImage else { return nil } let width = Int(maskImage.size.width) let height = Int(maskImage.size.height) let bytesPerPixel = 1 let bytesPerRow = width * bytesPerPixel let bitsPerComponent = 8 var maskData = [UInt8](repeating: 0, count: width * height) guard let context = CGContext(data: &maskData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue) else { return nil } context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) let path = UIBezierPath() let seedPoint = Coordinate(x: Int(seed.x), y: Int(seed.y)) // Ensure the seed point is within bounds and on a white pixel guard seedPoint.x >= 0 && seedPoint.x < width && seedPoint.y >= 0 && seedPoint.y < height else { return nil } guard maskData[seedPoint.y * width + seedPoint.x] == 255 else { return nil } var currentPoint = seedPoint let directions = [ Coordinate(x: 0, y: -1), // Up Coordinate(x: 1, y: 0), // Right Coordinate(x: 0, y: 1), // Down Coordinate(x: -1, y: 0) // Left ] // Find the starting edge pixel by moving up and left from the seed pixel var startPoint: Coordinate? = nil for direction in [Coordinate(x: 0, y: -1), Coordinate(x: -1, y: 0)] { var testPoint = seedPoint while testPoint.x >= 0 && testPoint.y >= 0 { if maskData[testPoint.y * width + testPoint.x] == 255 && isEdgePixel(x: testPoint.x, y: testPoint.y, width: width, height: height, maskData: maskData) { startPoint = testPoint break } testPoint = Coordinate(x: testPoint.x + direction.x, y: testPoint.y + direction.y) } if startPoint != nil { break } } guard let firstPoint = startPoint else { return nil } currentPoint = firstPoint path.move(to: CGPoint(x: firstPoint.x, y: firstPoint.y)) var visited: Set<Coordinate> = [firstPoint] repeat { var foundNext = false for direction in directions { let nextPoint = Coordinate(x: currentPoint.x + direction.x, y: currentPoint.y + direction.y) let nextIndex = nextPoint.y * width + nextPoint.x if nextPoint.x >= 0 && nextPoint.x < width && nextPoint.y >= 0 && nextPoint.y < height && maskData[nextIndex] == 255 && !visited.contains(nextPoint) && isEdgePixel(x: nextPoint.x, y: nextPoint.y, width: width, height: height, maskData: maskData) { path.addLine(to: CGPoint(x: nextPoint.x, y: nextPoint.y)) visited.insert(nextPoint) currentPoint = nextPoint foundNext = true break } } if !foundNext { break } } while currentPoint != firstPoint path.close() return path } } extension MagicWandSelection{ func isEdgePixel(x: Int, y: Int, width: Int, height: Int, maskData: [UInt8]) -> Bool { if x == 0 || y == 0 || x == width - 1 || y == height - 1 { return true } let directions = [ Coordinate(x: 0, y: -1), // Up Coordinate(x: 1, y: 0), // Right Coordinate(x: 0, y: 1), // Down Coordinate(x: -1, y: 0) // Left ] for direction in directions { let neighborX = x + direction.x let neighborY = y + direction.y if neighborX >= 0 && neighborX < width && neighborY >= 0 && neighborY < height { let neighborIndex = neighborY * width + neighborX if maskData[neighborIndex] == 0 { return true } } } return false } func createMagicWandPath(from maskImage: UIImage, seed: CGPoint) -> UIBezierPath? { guard let cgImage = maskImage.cgImage else { return nil } let width = Int(maskImage.size.width) let height = Int(maskImage.size.height) let bytesPerPixel = 1 let bytesPerRow = width * bytesPerPixel let bitsPerComponent = 8 var maskData = [UInt8](repeating: 0, count: width * height) guard let context = CGContext(data: &maskData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue) else { return nil } context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) let seedPoint = Coordinate(x: Int(seed.x), y: Int(seed.y)) // Ensure the seed point is within bounds and on a white pixel guard seedPoint.x >= 0 && seedPoint.x < width && seedPoint.y >= 0 && seedPoint.y < height else { return nil } guard maskData[seedPoint.y * width + seedPoint.x] == 255 else { return nil } // Find the starting edge pixel guard let startPixel = findStartingEdgePixel(from: seedPoint, width: width, height: height, maskData: maskData) else { return nil } // Find edge pixels and construct the path if let edgePath = traceEdgePath(from: startPixel, width: width, height: height, maskData: maskData) { return edgePath } return nil } func findStartingEdgePixel(from seedPoint: Coordinate, width: Int, height: Int, maskData: [UInt8]) -> Coordinate? { for y in 0..<height { for x in 0..<width { let index = y * width + x if maskData[index] == 255 && isEdgePixel(x: x, y: y, width: width, height: height, maskData: maskData) { return Coordinate(x: x, y: y) } } } return nil } func traceEdgePath(from startPoint: Coordinate, width: Int, height: Int, maskData: [UInt8]) -> UIBezierPath? { let directions = [ Coordinate(x: 0, y: -1), // Up Coordinate(x: 1, y: 0), // Right Coordinate(x: 0, y: 1), // Down Coordinate(x: -1, y: 0), // Left Coordinate(x: -1, y: -1), // Up-Left Coordinate(x: 1, y: -1), // Up-Right Coordinate(x: -1, y: 1), // Down-Left Coordinate(x: 1, y: 1) // Down-Right ] let path = UIBezierPath() var visited = Set<Coordinate>() var currentPoint = startPoint var firstPoint = true repeat { visited.insert(currentPoint) if firstPoint { path.move(to: CGPoint(x: currentPoint.x, y: currentPoint.y)) firstPoint = false } else { path.addLine(to: CGPoint(x: currentPoint.x, y: currentPoint.y)) } var foundNext = false for direction in directions { let nextPoint = Coordinate(x: currentPoint.x + direction.x, y: currentPoint.y + direction.y) if nextPoint.x >= 0 && nextPoint.x < width && nextPoint.y >= 0 && nextPoint.y < height && maskData[nextPoint.y * width + nextPoint.x] == 255 && !visited.contains(nextPoint) { currentPoint = nextPoint foundNext = true break } } if !foundNext { break } } while currentPoint != startPoint path.close() return path } } </code>
struct Coordinate: Hashable {
    let x: Int
    let y: Int
}

class MagicWandSelection: NSObject {
    
    override init() {
        super.init()
    }


    func floodFill(x: Int, y: Int, width: Int, height: Int, maskData: [UInt8], checked: inout [Bool]) -> [Coordinate] {
        var stack = [Coordinate(x: x, y: y)]
        var foundPixels = [Coordinate]()

        while let point = stack.popLast() {
            let index = point.y * width + point.x
            if point.x < 0 || point.x >= width || point.y < 0 || point.y >= height || checked[index] || maskData[index] != 255 {
                continue
            }

            checked[index] = true
            foundPixels.append(point)

            let directions = [Coordinate(x: 0, y: -1), Coordinate(x: 1, y: 0), Coordinate(x: 0, y: 1), Coordinate(x: -1, y: 0)]
            for direction in directions {
                let nextPoint = Coordinate(x: point.x + direction.x, y: point.y + direction.y)
                stack.append(nextPoint)
            }
        }

        return foundPixels
    }

    
    func newMaskWithFloodFill(from point: CGPoint, in image: UIImage, selectedColor: UIColor, tolerance: Float) -> UIImage? {
        guard let cgImage = image.cgImage else { return nil }
        let width = cgImage.width
        let height = cgImage.height
        let bytesPerPixel = 4
        let bytesPerRow = width * bytesPerPixel
        let bitsPerComponent = 8

        var pixelData = [UInt8](repeating: 0, count: width * height * bytesPerPixel)
        guard let context = CGContext(data: &pixelData,
                                      width: width,
                                      height: height,
                                      bitsPerComponent: bitsPerComponent,
                                      bytesPerRow: bytesPerRow,
                                      space: CGColorSpaceCreateDeviceRGB(),
                                      bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil }

        context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

        let startPixelIndex = (Int(point.y) * width + Int(point.x)) * bytesPerPixel
        let startPixel = Array(pixelData[startPixelIndex..<(startPixelIndex + bytesPerPixel)])

        var maskData = [UInt8](repeating: 0, count: width * height)
        floodFill(&pixelData, &maskData, width: width, height: height, x: Int(point.x), y: Int(point.y), startPixel: startPixel, tolerance: tolerance)

        return createMaskImage(from: maskData, width: width, height: height)
    }

    func floodFill(_ pixelData: inout [UInt8], _ maskData: inout [UInt8], width: Int, height: Int, x: Int, y: Int, startPixel: [UInt8], tolerance: Float) {
        var stack = [(Int, Int)]()
        stack.append((x, y))

        while !stack.isEmpty {
            let (currentX, currentY) = stack.removeLast()
            let index = (currentY * width + currentX) * 4
            let maskIndex = currentY * width + currentX

            if maskData[maskIndex] > 0 { continue }

            let currentPixel = Array(pixelData[index..<(index + 4)])

            if !colorsMatch(pixel1: currentPixel, pixel2: startPixel, tolerance: tolerance) {
                continue
            }

            maskData[maskIndex] = 255

            if currentX > 0 {
                stack.append((currentX - 1, currentY))
            }
            if currentX < width - 1 {
                stack.append((currentX + 1, currentY))
            }
            if currentY > 0 {
                stack.append((currentX, currentY - 1))
            }
            if currentY < height - 1 {
                stack.append((currentX, currentY + 1))
            }
        }
    }

    func colorsMatch(pixel1: [UInt8], pixel2: [UInt8], tolerance: Float) -> Bool {
        let r1 = Float(pixel1[0]) / 255.0, g1 = Float(pixel1[1]) / 255.0, b1 = Float(pixel1[2]) / 255.0, a1 = Float(pixel1[3]) / 255.0
        let r2 = Float(pixel2[0]) / 255.0, g2 = Float(pixel2[1]) / 255.0, b2 = Float(pixel2[2]) / 255.0, a2 = Float(pixel2[3]) / 255.0

        let dr = r1 - r2, dg = g1 - g2, db = b1 - b2, da = a1 - a2
        let distance = sqrt(dr * dr + dg * dg + db * db + da * da)

        return distance < tolerance
    }

    func createMaskImage(from maskData: [UInt8], width: Int, height: Int) -> UIImage? {
        let bytesPerPixel = 1
        let bytesPerRow = width * bytesPerPixel
        let bitsPerComponent = 8

        var maskDataModified = maskData.map { $0 > 0 ? UInt8(255) : UInt8(0) }
        guard let context = CGContext(data: &maskDataModified,
                                      width: width,
                                      height: height,
                                      bitsPerComponent: bitsPerComponent,
                                      bytesPerRow: bytesPerRow,
                                      space: CGColorSpaceCreateDeviceGray(),
                                      bitmapInfo: CGImageAlphaInfo.none.rawValue) else { return nil }

        guard let maskCgImage = context.makeImage() else { return nil }
        return UIImage(cgImage: maskCgImage)
    }

    func createBezierPath(from maskImage: UIImage, seed: CGPoint) -> UIBezierPath? {
        guard let cgImage = maskImage.cgImage else { return nil }
        let width = Int(maskImage.size.width)
        let height = Int(maskImage.size.height)
        let bytesPerPixel = 1
        let bytesPerRow = width * bytesPerPixel
        let bitsPerComponent = 8

        var maskData = [UInt8](repeating: 0, count: width * height)
        guard let context = CGContext(data: &maskData,
                                      width: width,
                                      height: height,
                                      bitsPerComponent: bitsPerComponent,
                                      bytesPerRow: bytesPerRow,
                                      space: CGColorSpaceCreateDeviceGray(),
                                      bitmapInfo: CGImageAlphaInfo.none.rawValue) else { return nil }

        context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

        let path = UIBezierPath()
        let seedPoint = Coordinate(x: Int(seed.x), y: Int(seed.y))

        // Ensure the seed point is within bounds and on a white pixel
        guard seedPoint.x >= 0 && seedPoint.x < width && seedPoint.y >= 0 && seedPoint.y < height else { return nil }
        guard maskData[seedPoint.y * width + seedPoint.x] == 255 else { return nil }

        var currentPoint = seedPoint
        let directions = [
            Coordinate(x: 0, y: -1), // Up
            Coordinate(x: 1, y: 0),  // Right
            Coordinate(x: 0, y: 1),  // Down
            Coordinate(x: -1, y: 0)  // Left
        ]

        // Find the starting edge pixel by moving up and left from the seed pixel
        var startPoint: Coordinate? = nil
        for direction in [Coordinate(x: 0, y: -1), Coordinate(x: -1, y: 0)] {
            var testPoint = seedPoint
            while testPoint.x >= 0 && testPoint.y >= 0 {
                if maskData[testPoint.y * width + testPoint.x] == 255 && isEdgePixel(x: testPoint.x, y: testPoint.y, width: width, height: height, maskData: maskData) {
                    startPoint = testPoint
                    break
                }
                testPoint = Coordinate(x: testPoint.x + direction.x, y: testPoint.y + direction.y)
            }
            if startPoint != nil {
                break
            }
        }

        guard let firstPoint = startPoint else { return nil }

        currentPoint = firstPoint
        path.move(to: CGPoint(x: firstPoint.x, y: firstPoint.y))
        var visited: Set<Coordinate> = [firstPoint]

        repeat {
            var foundNext = false
            for direction in directions {
                let nextPoint = Coordinate(x: currentPoint.x + direction.x, y: currentPoint.y + direction.y)
                let nextIndex = nextPoint.y * width + nextPoint.x

                if nextPoint.x >= 0 && nextPoint.x < width && nextPoint.y >= 0 && nextPoint.y < height &&
                    maskData[nextIndex] == 255 &&
                    !visited.contains(nextPoint) &&
                    isEdgePixel(x: nextPoint.x, y: nextPoint.y, width: width, height: height, maskData: maskData) {
                    path.addLine(to: CGPoint(x: nextPoint.x, y: nextPoint.y))
                    visited.insert(nextPoint)
                    currentPoint = nextPoint
                    foundNext = true
                    break
                }
            }

            if !foundNext {
                break
            }
        } while currentPoint != firstPoint

        path.close()
        return path
    }
}


extension MagicWandSelection{
    
    func isEdgePixel(x: Int, y: Int, width: Int, height: Int, maskData: [UInt8]) -> Bool {
         if x == 0 || y == 0 || x == width - 1 || y == height - 1 {
             return true
         }
         
         let directions = [
             Coordinate(x: 0, y: -1), // Up
             Coordinate(x: 1, y: 0),  // Right
             Coordinate(x: 0, y: 1),  // Down
             Coordinate(x: -1, y: 0)  // Left
         ]
         
         for direction in directions {
             let neighborX = x + direction.x
             let neighborY = y + direction.y
             if neighborX >= 0 && neighborX < width && neighborY >= 0 && neighborY < height {
                 let neighborIndex = neighborY * width + neighborX
                 if maskData[neighborIndex] == 0 {
                     return true
                 }
             }
         }
         return false
     }

     func createMagicWandPath(from maskImage: UIImage, seed: CGPoint) -> UIBezierPath? {
         guard let cgImage = maskImage.cgImage else { return nil }
         let width = Int(maskImage.size.width)
         let height = Int(maskImage.size.height)
         let bytesPerPixel = 1
         let bytesPerRow = width * bytesPerPixel
         let bitsPerComponent = 8

         var maskData = [UInt8](repeating: 0, count: width * height)
         guard let context = CGContext(data: &maskData,
                                       width: width,
                                       height: height,
                                       bitsPerComponent: bitsPerComponent,
                                       bytesPerRow: bytesPerRow,
                                       space: CGColorSpaceCreateDeviceGray(),
                                       bitmapInfo: CGImageAlphaInfo.none.rawValue) else { return nil }

         context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

         let seedPoint = Coordinate(x: Int(seed.x), y: Int(seed.y))

         // Ensure the seed point is within bounds and on a white pixel
         guard seedPoint.x >= 0 && seedPoint.x < width && seedPoint.y >= 0 && seedPoint.y < height else { return nil }
         guard maskData[seedPoint.y * width + seedPoint.x] == 255 else { return nil }

         // Find the starting edge pixel
         guard let startPixel = findStartingEdgePixel(from: seedPoint, width: width, height: height, maskData: maskData) else {
             return nil
         }

         // Find edge pixels and construct the path
         if let edgePath = traceEdgePath(from: startPixel, width: width, height: height, maskData: maskData) {
             return edgePath
         }
         
         return nil
     }

     func findStartingEdgePixel(from seedPoint: Coordinate, width: Int, height: Int, maskData: [UInt8]) -> Coordinate? {
         for y in 0..<height {
             for x in 0..<width {
                 let index = y * width + x
                 if maskData[index] == 255 && isEdgePixel(x: x, y: y, width: width, height: height, maskData: maskData) {
                     return Coordinate(x: x, y: y)
                 }
             }
         }
         return nil
     }

     func traceEdgePath(from startPoint: Coordinate, width: Int, height: Int, maskData: [UInt8]) -> UIBezierPath? {
         let directions = [
             Coordinate(x: 0, y: -1), // Up
             Coordinate(x: 1, y: 0),  // Right
             Coordinate(x: 0, y: 1),  // Down
             Coordinate(x: -1, y: 0), // Left
             Coordinate(x: -1, y: -1), // Up-Left
             Coordinate(x: 1, y: -1),  // Up-Right
             Coordinate(x: -1, y: 1),  // Down-Left
             Coordinate(x: 1, y: 1)    // Down-Right
         ]

         let path = UIBezierPath()
         var visited = Set<Coordinate>()
         var currentPoint = startPoint
         var firstPoint = true

         repeat {
             visited.insert(currentPoint)
             if firstPoint {
                 path.move(to: CGPoint(x: currentPoint.x, y: currentPoint.y))
                 firstPoint = false
             } else {
                 path.addLine(to: CGPoint(x: currentPoint.x, y: currentPoint.y))
             }
             
             var foundNext = false
             for direction in directions {
                 let nextPoint = Coordinate(x: currentPoint.x + direction.x, y: currentPoint.y + direction.y)
                 if nextPoint.x >= 0 && nextPoint.x < width && nextPoint.y >= 0 && nextPoint.y < height &&
                    maskData[nextPoint.y * width + nextPoint.x] == 255 && !visited.contains(nextPoint) {
                     currentPoint = nextPoint
                     foundNext = true
                     break
                 }
             }
             if !foundNext {
                 break
             }
         } while currentPoint != startPoint

         path.close()
         return path
     }
    
}

I’ve tried pretty much everything I can think of including trying to use chatgpt to fix my code. What I get is a mess of a selection with the UIBezierPath zigzaging the image.
I used the ! here so that it would crash if nothing is returned this will be fixed in production code.

this is the usage example for the above code:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> let magicWand = MagicWandSelection()
let color = UIColor.white
let pixel = GPPixel(x: Int(pt.x), y: Int(pt.y), color: color)
let tolerance: Float = 0.1
let img = magicWand.newMaskWithFloodFill(from: pt, in: self.image, selectedColor: color, tolerance: tolerance)!
self.selectionPath = magicWand.createMagicWandPath(from: img, seed: pt)
</code>
<code> let magicWand = MagicWandSelection() let color = UIColor.white let pixel = GPPixel(x: Int(pt.x), y: Int(pt.y), color: color) let tolerance: Float = 0.1 let img = magicWand.newMaskWithFloodFill(from: pt, in: self.image, selectedColor: color, tolerance: tolerance)! self.selectionPath = magicWand.createMagicWandPath(from: img, seed: pt) </code>
            let magicWand = MagicWandSelection()
            let color = UIColor.white
            let pixel = GPPixel(x: Int(pt.x), y: Int(pt.y), color: color)
            let tolerance: Float = 0.1
            let img = magicWand.newMaskWithFloodFill(from: pt, in: self.image, selectedColor: color, tolerance: tolerance)!
            self.selectionPath = magicWand.createMagicWandPath(from: img, seed: pt)

the floodFill mask returns and image of a black background with the pixels that should be outlined with a selection path as white pixels. Which should make this easy to figure out but I just cannot get past where i am.

New contributor

April G is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật