Floodfill Scanline pixel in iOS Swift
I think you'll have a much better chance of translating that code to Swift by keeping it closer to the original.
func floodFillScanlineStackV4(x: Int, y: Int,
color_Grid: [Int],
newColor: Int, oldColor: Int,
h: Int, w: Int) -> [Int] {
var screenBuffer = color_Grid
assert(x < w, "p.x \(x) out of range, must be < \(w)")
assert(y < h, "p.y \(y) out of range, must be < \(h)")
if oldColor == newColor { return [] }
var x1: Int = 0
var stack : [(Int, Int)] = [(x, y)] // 0 is X, 1 is Y
while let pp = stack.popLast() {
x1 = pp.0
let y = pp.1
while(x1 >= 0 && screenBuffer[y * w + x1] == oldColor) {
x1 -= 1
}
x1 += 1
var spanAbove = false
var spanBelow = false
while(x1 < w && screenBuffer[y * w + x1] == oldColor) {
screenBuffer[y * w + x1] = newColor;
if(!spanAbove && y > 0 && screenBuffer[(y - 1) * w + x1] == oldColor) {
stack.append((x1, y - 1))
spanAbove = true
}
else if(spanAbove && y > 0 && screenBuffer[(y - 1) * w + x1] != oldColor) {
spanAbove = false
}
if(!spanBelow && y < h - 1 && screenBuffer[(y + 1) * w + x1] == oldColor) {
stack.append((x1, y + 1))
spanBelow = true
}
else if(spanBelow && y < h - 1 && screenBuffer[(y + 1) * w + x1] != oldColor) {
spanBelow = false
}
x1 += 1
}
}
return screenBuffer
}
func compareColor(_ v1: Int, _ v2: Int) -> Bool {
return v1 == v2
}
To do that, instead of using a two-dimensional array of Int, use a one-dimensional array.
Here's some example code. Note that this is Example Code only, with minimal error checking, and is not intended to be considered "Production Ready":
class FloodVC: UIViewController {
let gridWidth: Int = 12
let gridHeight: Int = 12
var bufLength: Int = 0
let gridSpacing: CGFloat = 1
let colors: [UIColor] = [
UIColor(red: 1.00, green: 0.60, blue: 0.60, alpha: 1.0),
UIColor(red: 0.60, green: 1.00, blue: 0.60, alpha: 1.0),
UIColor(red: 0.20, green: 0.85, blue: 1.00, alpha: 1.0),
UIColor(red: 1.00, green: 1.00, blue: 0.60, alpha: 1.0),
UIColor(red: 0.60, green: 1.00, blue: 1.00, alpha: 1.0),
UIColor(red: 1.00, green: 0.60, blue: 1.00, alpha: 1.0),
]
var grid: [Int] = []
var newColor: Int = 2
lazy var gridStack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.distribution = .fillEqually
v.spacing = gridSpacing
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let colorStack: UIStackView = {
let v = UIStackView()
v.spacing = 8
v.distribution = .fillEqually
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let infoLabel: UILabel = {
let v = UILabel()
v.textAlignment = .center
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
bufLength = gridWidth * gridHeight
// init array to all Zeroes
grid = Array(repeating: 0, count: bufLength)
for _ in 0..<gridHeight {
let rs = UIStackView()
rs.distribution = .fillEqually
rs.spacing = gridSpacing
for _ in 0..<gridWidth {
let v = UILabel()
v.font = .systemFont(ofSize: 10, weight: .light)
v.textAlignment = .center
v.isUserInteractionEnabled = true
let g = UITapGestureRecognizer(target: self, action: #selector(cellTap(_:)))
v.addGestureRecognizer(g)
rs.addArrangedSubview(v)
}
gridStack.addArrangedSubview(rs)
}
view.addSubview(gridStack)
for i in 0..<colors.count {
let v = UILabel()
v.font = .systemFont(ofSize: 10, weight: .light)
v.textAlignment = .center
v.text = "\(i)"
v.backgroundColor = colors[i]
v.isUserInteractionEnabled = true
v.heightAnchor.constraint(equalToConstant: 32).isActive = true
v.layer.borderColor = UIColor.red.cgColor
if i == newColor {
v.layer.borderWidth = 1
}
let g = UITapGestureRecognizer(target: self, action: #selector(newColorTap(_:)))
v.addGestureRecognizer(g)
colorStack.addArrangedSubview(v)
}
let shapeStack: UIStackView = {
let v = UIStackView()
v.spacing = 8
v.distribution = .fillEqually
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
["Square", "Cross", "Triangle", "Random"].forEach { str in
let v = UIButton()
v.titleLabel?.font = .systemFont(ofSize: 13, weight: .light)
v.setTitle(str, for: [])
v.backgroundColor = .systemBlue
v.setTitleColor(.white, for: .normal)
v.setTitleColor(.lightGray, for: .highlighted)
v.layer.borderColor = UIColor.blue.cgColor
v.layer.borderWidth = 1
v.layer.cornerRadius = 6
v.addTarget(self, action: #selector(setupShape(_:)), for: .touchUpInside)
shapeStack.addArrangedSubview(v)
}
let optionsStack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 8
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
optionsStack.addArrangedSubview(shapeStack)
optionsStack.addArrangedSubview(colorStack)
view.addSubview(optionsStack)
view.addSubview(infoLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
shapeStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
shapeStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16),
shapeStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16),
gridStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8),
gridStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8),
gridStack.heightAnchor.constraint(equalTo: gridStack.widthAnchor),
gridStack.centerYAnchor.constraint(equalTo: g.centerYAnchor),
infoLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16),
infoLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16),
infoLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
setupSquare()
}
@objc func newColorTap(_ g: UITapGestureRecognizer) {
guard let v = g.view as? UILabel,
let t = v.text,
let idx = Int(t)
else { return }
for i in 0..<colorStack.arrangedSubviews.count {
colorStack.arrangedSubviews[i].layer.borderWidth = i == idx ? 1 : 0
}
newColor = idx
}
@objc func cellTap(_ g: UITapGestureRecognizer) {
guard let v = g.view as? UILabel,
let t = v.text,
let oldColor = Int(t),
let rowStack = v.superview as? UIStackView,
let c = rowStack.arrangedSubviews.firstIndex(of: v),
let r = gridStack.arrangedSubviews.firstIndex(of: rowStack)
else { return }
let st = CFAbsoluteTimeGetCurrent()
let newGrid = floodFillScanlineStackV4(x: c, y: r, color_Grid: grid, newColor: newColor, oldColor: oldColor, h: gridHeight, w: gridWidth)
let elapsed = CFAbsoluteTimeGetCurrent() - st
let nf = NumberFormatter()
nf.maximumFractionDigits = 8
infoLabel.text = "Elapsed Time: " + nf.string(from: NSNumber(value: elapsed))! + " seconds"
// if new color equals tapped cell color,
// newGrid will be empty
if newGrid.count != 0 {
grid = newGrid
refreshGrid(grid)
}
}
func refreshGrid(_ g: [Int]) {
for i in 0..<bufLength {
let r = i / gridWidth
guard let rs = gridStack.arrangedSubviews[r] as? UIStackView
else { fatalError("bad setup") }
for c in 0..<gridWidth {
guard let v = rs.arrangedSubviews[c] as? UILabel
else { fatalError("bad setup") }
let p = r * gridWidth + c
v.text = "\(g[p])"
v.backgroundColor = colors[g[p]]
}
}
}
func floodFillScanlineStackV4(x: Int, y: Int,
color_Grid: [Int],
newColor: Int, oldColor: Int,
h: Int, w: Int) -> [Int] {
var screenBuffer = color_Grid
assert(x < w, "p.x \(x) out of range, must be < \(w)")
assert(y < h, "p.y \(y) out of range, must be < \(h)")
if oldColor == newColor { return [] }
var x1: Int = 0
var stack : [(Int, Int)] = [(x, y)] // 0 is X, 1 is Y
while let pp = stack.popLast() {
x1 = pp.0
let y = pp.1
while(x1 >= 0 && screenBuffer[y * w + x1] == oldColor) {
x1 -= 1
}
x1 += 1
var spanAbove = false
var spanBelow = false
while(x1 < w && screenBuffer[y * w + x1] == oldColor) {
screenBuffer[y * w + x1] = newColor;
if(!spanAbove && y > 0 && screenBuffer[(y - 1) * w + x1] == oldColor) {
stack.append((x1, y - 1))
spanAbove = true
}
else if(spanAbove && y > 0 && screenBuffer[(y - 1) * w + x1] != oldColor) {
spanAbove = false
}
if(!spanBelow && y < h - 1 && screenBuffer[(y + 1) * w + x1] == oldColor) {
stack.append((x1, y + 1))
spanBelow = true
}
else if(spanBelow && y < h - 1 && screenBuffer[(y + 1) * w + x1] != oldColor) {
spanBelow = false
}
x1 += 1
}
}
return screenBuffer
}
func compareColor(_ v1: Int, _ v2: Int) -> Bool {
return v1 == v2
}
// MARK: grid setups
@objc func setupShape(_ sender: Any?) {
var t: String = "square"
if let btn = sender as? UIButton {
t = btn.currentTitle ?? "square"
}
switch t {
case "Square":
setupSquare()
()
case "Cross":
setupCross()
()
case "Triangle":
setupTriangle()
()
default:
setupRandom()
()
}
}
@objc func setupSquare() {
// init array to all Zeroes
grid = Array(repeating: 0, count: bufLength)
let row1: Int = 2
let row2: Int = gridHeight - (row1 + 1)
let col1: Int = 2
let col2: Int = gridWidth - (col1 + 1)
for r in row1...row2 {
for c in col1...col2 {
let p = r * gridWidth + c
grid[p] = 1
}
}
refreshGrid(grid)
}
@objc func setupCross() {
// init array to all Zeroes
grid = Array(repeating: 0, count: bufLength)
var row1: Int = 2
var row2: Int = gridHeight - (row1 + 1)
var col1: Int = gridWidth / 2 - 1
var col2: Int = col1 + 1
for r in row1...row2 {
for c in col1...col2 {
let p = r * gridWidth + c
grid[p] = 1
}
}
row1 = gridHeight / 2 - 1
row2 = row1 + 1
col1 = 2
col2 = gridWidth - (col1 + 1)
for r in row1...row2 {
for c in col1...col2 {
let p = r * gridWidth + c
grid[p] = 1
}
}
refreshGrid(grid)
}
@objc func setupTriangle() {
// init array to all Zeroes
grid = Array(repeating: 0, count: bufLength)
var row: Int = 1
var col1: Int = gridWidth / 2 - 1
var col2: Int = col1 + 1
let p: Int = row * gridWidth + col1
grid[p] = 1
grid[p + 1] = 1
row += 1
col1 -= 1
col2 += 1
while col1 > 0 {
var p1: Int = row * gridWidth + col1
var p2: Int = row * gridWidth + col2
grid[p1] = 1
grid[p2] = 1
row += 1
p1 = row * gridWidth + col1
p2 = row * gridWidth + col2
grid[p1] = 1
grid[p2] = 1
row += 1
col1 -= 1
col2 += 1
}
for c in col1...col2 {
let p: Int = row * gridWidth + c
grid[p] = 1
}
refreshGrid(grid)
}
@objc func setupRandom() {
// init array to all Zeroes
grid = Array(repeating: 0, count: bufLength)
// we'll fill grid with random excluding
// the first color, to make it easier to see
// the changes
for r in 0..<gridHeight {
for c in 0..<gridWidth {
let p = r * gridWidth + c
grid[p] = Int.random(in: 1..<colors.count)
}
}
let idx: Int = 0
for i in 0..<colorStack.arrangedSubviews.count {
colorStack.arrangedSubviews[i].layer.borderWidth = i == idx ? 1 : 0
}
newColor = idx
refreshGrid(grid)
}
}
It looks like this when running:
Tapping a color-box at the top will select the "replacement color".
Tapping a box inside the grid will perform the Flood Fill.
Since this would ideally be used directly on a buffer of image data, the "Elapsed Time" value is for the Int Array process only -- it does not include refreshing the UI (the grid of colors/numbers).
Tapping on the labeled buttons sets up an initial pattern:
Use Random and then select various contiguous squares to try out a winding path:
iOS - How to fill color in Prescribed area
I have solved the problem, this is a Demo
This solution requires fixing two seed points on the leaves。
When a seed point is triggered for coloring, The other one is also coloring.
If there's a better solution, let me know please.
Related Topics
Pure Swiftui Login, Signup, Register Flow, Is It Possible
How to Make Uiscrollview Zoom in Only One Direction When Using Auto Layout
iOS Sound Not Playing in Swift
How to Set the Size of an Uiviewrepresentable
How to Set Navigationview Background Colour in Swiftui
How to Read References Given by Ptr_Refs in iOS
Load Offline Cached JSON Using Afnetworking
How to Change Uitextfield Color in Searchcontroller
Screen Recording When My iOS App Is in Background with Replaykit
Nsexceptionallowsinsecurehttploads Not Working for Ip Addresses
Swift - Uitableview Didselectrowatindexpath & Diddeselectrowatindexpath Add & Remove Indexpath Ids
How to Set Response Data into Todayextenstion Widget
Swift - Nsdate and Last Week of Year
How to Change the Number of Decimal Places iOS
Swiftui Card Flip Animation with Two Views, One of Which Is Embedded Within a Stack