Skip to content

Commit 91985a9

Browse files
authored
Merge pull request #271 from Naveen3Singh/improve_flood_fill
Improve Flood-fill algorithm
2 parents 8c6070a + e11ce9b commit 91985a9

File tree

8 files changed

+212
-303
lines changed

8 files changed

+212
-303
lines changed
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package com.simplemobiletools.draw.pro.extensions
22

33
import android.graphics.Bitmap
4-
import com.simplemobiletools.draw.pro.helpers.QueueLinearFloodFiller
4+
import com.simplemobiletools.draw.pro.helpers.VectorFloodFiller
5+
import com.simplemobiletools.draw.pro.models.MyPath
56

6-
fun Bitmap.floodFill(color: Int, x: Int, y: Int, tolerance: Int = 10): Bitmap {
7-
val floodFiller = QueueLinearFloodFiller(this).apply {
7+
fun Bitmap.vectorFloodFill(color: Int, x: Int, y: Int, tolerance: Int): MyPath {
8+
val floodFiller = VectorFloodFiller(this).apply {
89
fillColor = color
9-
setTolerance(tolerance)
10+
this.tolerance = tolerance
1011
}
1112

1213
floodFiller.floodFill(x, y)
13-
return floodFiller.image!!
14+
return floodFiller.path
1415
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.simplemobiletools.draw.pro.extensions
2+
3+
fun <K, V> LinkedHashMap<K, V>.removeFirst(): Pair<K, V> {
4+
val key = keys.first()
5+
val value = values.first()
6+
remove(key)
7+
return key to value
8+
}
9+
10+
fun <K, V> LinkedHashMap<K, V>.removeLast(): Pair<K, V> {
11+
val key = keys.last()
12+
val value = values.last()
13+
remove(key)
14+
return key to value
15+
}
16+
17+
fun <K, V> LinkedHashMap<K, V>.removeLastOrNull(): Pair<K?, V?> {
18+
val key = keys.lastOrNull()
19+
val value = values.lastOrNull()
20+
remove(key)
21+
return key to value
22+
}

app/src/main/kotlin/com/simplemobiletools/draw/pro/helpers/FloodFill.kt

Lines changed: 0 additions & 165 deletions
This file was deleted.
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package com.simplemobiletools.draw.pro.helpers
2+
3+
import android.graphics.Bitmap
4+
import android.graphics.Color
5+
import com.simplemobiletools.draw.pro.models.MyPath
6+
import java.util.*
7+
8+
// Original algorithm by J. Dunlap http:// www.codeproject.com/KB/GDI-plus/queuelinearflood-fill.aspx
9+
// Java port by Owen Kaluza
10+
// Android port by Darrin Smith (Standard Android)
11+
class VectorFloodFiller(image: Bitmap) {
12+
val path = MyPath()
13+
14+
private var width = 0
15+
private var height = 0
16+
private var pixels: IntArray? = null
17+
18+
private lateinit var pixelsChecked: BooleanArray
19+
private lateinit var ranges: Queue<FloodFillRange>
20+
21+
var fillColor = 0
22+
var tolerance = 0
23+
private var startColorRed = 0
24+
private var startColorGreen = 0
25+
private var startColorBlue = 0
26+
27+
init {
28+
width = image.width
29+
height = image.height
30+
pixels = IntArray(width * height)
31+
image.getPixels(pixels, 0, width, 0, 0, width, height)
32+
}
33+
34+
private fun prepare() {
35+
// Called before starting flood-fill
36+
pixelsChecked = BooleanArray(pixels!!.size)
37+
ranges = LinkedList()
38+
}
39+
40+
// Fills the specified point on the bitmap with the currently selected fill color.
41+
// int x, int y: The starting coordinates for the fill
42+
fun floodFill(x: Int, y: Int) {
43+
// Setup
44+
prepare()
45+
46+
// Get starting color.
47+
val startPixel = pixels!!.getOrNull(width * y + x) ?: return
48+
if (startPixel == fillColor) {
49+
// No-op.
50+
return
51+
}
52+
startColorRed = Color.red(startPixel)
53+
startColorGreen = Color.green(startPixel)
54+
startColorBlue = Color.blue(startPixel)
55+
56+
// Do first call to flood-fill.
57+
linearFill(x, y)
58+
59+
// Call flood-fill routine while flood-fill ranges still exist on the queue
60+
var range: FloodFillRange
61+
while (ranges.size > 0) {
62+
// Get Next Range Off the Queue
63+
range = ranges.remove()
64+
65+
// Check Above and Below Each Pixel in the flood-fill Range
66+
var downPxIdx = width * (range.Y + 1) + range.startX
67+
var upPxIdx = width * (range.Y - 1) + range.startX
68+
val upY = range.Y - 1 // so we can pass the y coordinate by ref
69+
val downY = range.Y + 1
70+
for (i in range.startX..range.endX) {
71+
// Start Fill Upwards
72+
// if we're not above the top of the bitmap and the pixel above this one is within the color tolerance
73+
if (range.Y > 0 && !pixelsChecked[upPxIdx] && isPixelColorWithinTolerance(upPxIdx)) {
74+
linearFill(i, upY)
75+
}
76+
77+
// Start Fill Downwards
78+
// if we're not below the bottom of the bitmap and the pixel below this one is within the color tolerance
79+
if (range.Y < height - 1 && !pixelsChecked[downPxIdx] && isPixelColorWithinTolerance(downPxIdx)) {
80+
linearFill(i, downY)
81+
}
82+
downPxIdx++
83+
upPxIdx++
84+
}
85+
}
86+
}
87+
88+
// Finds the furthermost left and right boundaries of the fill area
89+
// on a given y coordinate, starting from a given x coordinate, filling as it goes.
90+
// Adds the resulting horizontal range to the queue of flood-fill ranges,
91+
// to be processed in the main loop.
92+
//
93+
// int x, int y: The starting coordinates
94+
private fun linearFill(x: Int, y: Int) {
95+
// Find Left Edge of Color Area
96+
var lFillLoc = x // the location to check/fill on the left
97+
var pxIdx = width * y + x
98+
path.moveTo(x.toFloat(), y.toFloat())
99+
while (true) {
100+
pixelsChecked[pxIdx] = true
101+
lFillLoc--
102+
pxIdx--
103+
// exit loop if we're at edge of bitmap or color area
104+
if (lFillLoc < 0 || pixelsChecked[pxIdx] || !isPixelColorWithinTolerance(pxIdx)) {
105+
break
106+
}
107+
}
108+
vectorFill(pxIdx + 1)
109+
lFillLoc++
110+
111+
// Find Right Edge of Color Area
112+
var rFillLoc = x // the location to check/fill on the left
113+
pxIdx = width * y + x
114+
while (true) {
115+
pixelsChecked[pxIdx] = true
116+
rFillLoc++
117+
pxIdx++
118+
if (rFillLoc >= width || pixelsChecked[pxIdx] || !isPixelColorWithinTolerance(pxIdx)) {
119+
break
120+
}
121+
}
122+
vectorFill(pxIdx - 1)
123+
rFillLoc--
124+
125+
// add range to queue
126+
val r = FloodFillRange(lFillLoc, rFillLoc, y)
127+
ranges.offer(r)
128+
}
129+
130+
// vector fill pixels with color
131+
private fun vectorFill(pxIndex: Int) {
132+
val x = (pxIndex % width).toFloat()
133+
val y = (pxIndex - x) / width
134+
path.lineTo(x, y)
135+
}
136+
137+
// Sees if a pixel is within the color tolerance range.
138+
private fun isPixelColorWithinTolerance(px: Int): Boolean {
139+
val red = pixels!![px] ushr 16 and 0xff
140+
val green = pixels!![px] ushr 8 and 0xff
141+
val blue = pixels!![px] and 0xff
142+
return red >= startColorRed - tolerance && red <= startColorRed + tolerance && green >= startColorGreen - tolerance && green <= startColorGreen + tolerance && blue >= startColorBlue - tolerance && blue <= startColorBlue + tolerance
143+
}
144+
145+
// Represents a linear range to be filled and branched from.
146+
private inner class FloodFillRange(var startX: Int, var endX: Int, var Y: Int)
147+
}

app/src/main/kotlin/com/simplemobiletools/draw/pro/models/CanvasOp.kt

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)