Как создать инструмент выделения «Волшебная палочка», как в фотошопеIOS

Программируем под IOS
Ответить
Anonymous
 Как создать инструмент выделения «Волшебная палочка», как в фотошопе

Сообщение Anonymous »

Я перепробовал все, включая ChatGPT, но никак не могу заставить свой инструмент выбора «волшебной палочки», написанный быстро, работать. Единственная информация, которую я нашел на StackOverflow, совсем не помогла. И, похоже, не существует библиотеки или фрагмента кода, которые помогли бы мне двигаться в правильном направлении.
Я просто опубликую весь класс, над которым работаю.

Код: Выделить всё

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.. 0 { continue }

let currentPixel = Array(pixelData[index.. 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 = [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..= 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
}

}

Я перепробовал практически все, что только мог придумать, в том числе пытался использоватьchatgpt для исправления моего кода. Я получаю беспорядочное выделение с UIBezierPath, зигзагообразным образом перемещающим изображение.
Я использовал ! здесь, чтобы произошел сбой, если ничего не будет возвращено, это будет исправлено в рабочем коде.
это пример использования приведенного выше кода:

Код: Выделить всё

            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)
маска FloodFill возвращает изображение черного фона с пикселями, которые должны быть обведены контуром выделения в виде белых пикселей. Это должно облегчить понимание, но я просто не могу пройти мимо того места, где нахожусь.

Подробнее здесь: https://stackoverflow.com/questions/785 ... -photoshop
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «IOS»