|
| 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 | +} |
0 commit comments