Move SSD detector component scan to typed Kotlin

ober

a31117b5befa7379611914202b91661327315e26

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 7ba146a..5c0572e 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -2757,6 +2757,7 @@
     (typed-kotlin-file "com/sfb/ssdreview/VisionArrays.kt"
       (typed-library (com sfb ssdreview)
         (export grayAt edgeAt edgeMask sideCoverage darkSideCoverage contourCell toneCell
+                detectColorComponents detectToneComponents detectEdgeComponents
                 detectorComponentCells detectorToneCells detectorContourCells
                 shrinkMaskBounds)
         (type Int32)
@@ -2852,6 +2853,161 @@
                           (>= y height))))
             #f
             (boolean-array-ref edge (+ (* y width) x))))
+        (def (toneMaskAt (gray : IntArray) (index : Int32)) : Bool
+          (let ((value (int-array-ref gray index)))
+            (and (>= value (int32 70))
+                 (<= value (int32 245)))))
+        (def (componentMaskAt (kind : Int32)
+                              (pixels : IntArray)
+                              (gray : IntArray)
+                              (edge : BooleanArray)
+                              (index : Int32)) : Bool
+          (if (= kind (int32 0))
+            (isColorMask (int-array-ref pixels index))
+            (if (= kind (int32 1))
+              (toneMaskAt gray index)
+              (boolean-array-ref edge index))))
+        (def (markComponentNeighbor! (visited : BooleanArray)
+                                     (queue : (MutableList Int32))
+                                     (kind : Int32)
+                                     (pixels : IntArray)
+                                     (gray : IntArray)
+                                     (edge : BooleanArray)
+                                     (width : Int32)
+                                     (height : Int32)
+                                     (nx : Int32)
+                                     (ny : Int32)) : Unit
+          (if (or (or (< nx (int32 0))
+                      (< ny (int32 0)))
+                  (or (>= nx width)
+                      (>= ny height)))
+            (begin)
+            (let ((nidx (+ (* ny width) nx)))
+              (if (and (not (boolean-array-ref visited nidx))
+                       (componentMaskAt kind pixels gray edge nidx))
+                (begin
+                  (boolean-array-set! visited nidx #t)
+                  (mutable-list-add! queue nidx))
+                (begin)))))
+        (def (scanComponentNeighbors! (visited : BooleanArray)
+                                      (queue : (MutableList Int32))
+                                      (kind : Int32)
+                                      (pixels : IntArray)
+                                      (gray : IntArray)
+                                      (edge : BooleanArray)
+                                      (width : Int32)
+                                      (height : Int32)
+                                      (idx : Int32)) : Unit
+          (let ((cx (mod idx width))
+                (cy (/ idx width)))
+            (begin
+              (markComponentNeighbor! visited queue kind pixels gray edge width height (- cx (int32 1)) (- cy (int32 1)))
+              (markComponentNeighbor! visited queue kind pixels gray edge width height cx (- cy (int32 1)))
+              (markComponentNeighbor! visited queue kind pixels gray edge width height (+ cx (int32 1)) (- cy (int32 1)))
+              (markComponentNeighbor! visited queue kind pixels gray edge width height (- cx (int32 1)) cy)
+              (markComponentNeighbor! visited queue kind pixels gray edge width height cx cy)
+              (markComponentNeighbor! visited queue kind pixels gray edge width height (+ cx (int32 1)) cy)
+              (markComponentNeighbor! visited queue kind pixels gray edge width height (- cx (int32 1)) (+ cy (int32 1)))
+              (markComponentNeighbor! visited queue kind pixels gray edge width height cx (+ cy (int32 1)))
+              (markComponentNeighbor! visited queue kind pixels gray edge width height (+ cx (int32 1)) (+ cy (int32 1))))))
+        (def (scanComponentPixel! (component : Component)
+                                  (visited : BooleanArray)
+                                  (queue : (MutableList Int32))
+                                  (kind : Int32)
+                                  (pixels : IntArray)
+                                  (gray : IntArray)
+                                  (edge : BooleanArray)
+                                  (width : Int32)
+                                  (height : Int32)
+                                  (idx : Int32)) : Unit
+          (let ((cx (mod idx width))
+                (cy (/ idx width)))
+            (begin
+              (componentAdd component cx cy)
+              (scanComponentNeighbors! visited queue kind pixels gray edge width height idx))))
+        (def (detectMaskedComponents (width : Int32)
+                                     (height : Int32)
+                                     (pixels : IntArray)
+                                     (gray : IntArray)
+                                     (edge : BooleanArray)
+                                     (detector : String)
+                                     (kind : Int32)) : (MutableList Component)
+          (let ((visited
+                  (boolean-array-build
+                    (* width height)
+                    (index #f)))
+                (components (mutable-list-empty Component)))
+            (begin
+              (for/fold ((ignored (int32 0)))
+                        ((start (in-range (int32 0) (* width height))))
+                (if (or (boolean-array-ref visited start)
+                        (not (componentMaskAt kind pixels gray edge start)))
+                  (begin
+                    (boolean-array-set! visited start #t)
+                    ignored)
+                  (let ((queue (mutable-list-empty Int32))
+                        (x (mod start width))
+                        (y (/ start width)))
+                    (let ((component (make-Component x y x y (int32 0) detector)))
+                      (begin
+                        (boolean-array-set! visited start #t)
+                        (mutable-list-add! queue start)
+                        (for/fold ((cursor (int32 0)))
+                                  ((pass (in-range (int32 0) (* width height))))
+                          (if (< cursor (list-size queue))
+                            (begin
+                              (scanComponentPixel!
+                                component
+                                visited
+                                queue
+                                kind
+                                pixels
+                                gray
+                                edge
+                                width
+                                height
+                                (list-ref queue cursor))
+                              (+ cursor (int32 1)))
+                            cursor))
+                        (mutable-list-add! components component)
+                        ignored)))))
+              components)))
+        (def (detectColorComponents (width : Int32)
+                                    (height : Int32)
+                                    (pixels : IntArray)
+                                    (detector : String)) : (MutableList Component)
+          (detectMaskedComponents
+            width
+            height
+            pixels
+            pixels
+            (boolean-array-build (int32 0) (index #f))
+            detector
+            (int32 0)))
+        (def (detectToneComponents (width : Int32)
+                                   (height : Int32)
+                                   (gray : IntArray)
+                                   (detector : String)) : (MutableList Component)
+          (detectMaskedComponents
+            width
+            height
+            gray
+            gray
+            (boolean-array-build (int32 0) (index #f))
+            detector
+            (int32 1)))
+        (def (detectEdgeComponents (width : Int32)
+                                   (height : Int32)
+                                   (edge : BooleanArray)
+                                   (detector : String)) : (MutableList Component)
+          (detectMaskedComponents
+            width
+            height
+            (int-array-build (int32 0) (index (int32 0)))
+            (int-array-build (int32 0) (index (int32 0)))
+            edge
+            detector
+            (int32 2)))
         (def (sobelPixel (gray : IntArray)
                          (width : Int32)
                          (x : Int32)
@@ -5839,10 +5995,6 @@
        "package com.sfb.ssdreview"
        ""
        "import android.graphics.Bitmap"
-       "import java.util.ArrayDeque"
-       "import kotlin.math.max"
-       "import kotlin.math.min"
-       ""
        "object SsdDetector {"
        "    fun detectCells(bitmap: Bitmap): MutableList<SsdCell> {"
        "        val width = bitmap.width"
@@ -5853,16 +6005,12 @@
        "        val cells = mutableListOf<SsdCell>()"
        ""
        "        cells += detectorComponentCells("
-       "            detectMask(width, height, pixels, \"android-color\") { index ->"
-       "                isColorMask(pixels[index])"
-       "            },"
+       "            detectColorComponents(width, height, pixels, \"android-color\"),"
        "            \"android-color\""
        "        )"
        ""
        "        cells += detectorToneCells("
-       "            detectMask(width, height, pixels, \"android-tone\") { index ->"
-       "                gray[index] in 70..245"
-       "            },"
+       "            detectToneComponents(width, height, gray, \"android-tone\"),"
        "            gray,"
        "            width,"
        "            height"
@@ -5911,52 +6059,10 @@
        "        return shrinkMaskBounds(rows, cols, x1, y1, bitmap.width, bitmap.height)"
        "    }"
        ""
-       "    private fun detectMask("
-       "        width: Int,"
-       "        height: Int,"
-       "        pixels: IntArray,"
-       "        detector: String,"
-       "        masked: (Int) -> Boolean"
-       "    ): List<Component> {"
-       "        val visited = BooleanArray(width * height)"
-       "        val components = mutableListOf<Component>()"
-       "        val queue = ArrayDeque<Int>()"
-       "        for (y in 0 until height) {"
-       "            for (x in 0 until width) {"
-       "                val start = y * width + x"
-       "                if (visited[start] || !masked(start)) {"
-       "                    visited[start] = true"
-       "                    continue"
-       "                }"
-       "                queue.clear()"
-       "                queue.add(start)"
-       "                visited[start] = true"
-       "                val acc = Component(x, y, x, y, 0, detector)"
-       "                while (!queue.isEmpty()) {"
-       "                    val idx = queue.removeFirst()"
-       "                    val cx = idx % width"
-       "                    val cy = idx / width"
-       "                    componentAdd(acc, cx, cy)"
-       "                    for (ny in max(0, cy - 1)..min(height - 1, cy + 1)) {"
-       "                        for (nx in max(0, cx - 1)..min(width - 1, cx + 1)) {"
-       "                            val nidx = ny * width + nx"
-       "                            if (!visited[nidx] && masked(nidx)) {"
-       "                                visited[nidx] = true"
-       "                                queue.add(nidx)"
-       "                            }"
-       "                        }"
-       "                    }"
-       "                }"
-       "                components.add(acc)"
-       "            }"
-       "        }"
-       "        return components"
-       "    }"
-       ""
        "    private fun contourCells(width: Int, height: Int, gray: IntArray): List<SsdCell> {"
        "        val edge = edgeMask(gray, width, height)"
        "        return detectorContourCells("
-       "            detectMask(width, height, IntArray(width * height), \"android-contour\") { edge[it] },"
+       "            detectEdgeComponents(width, height, edge, \"android-contour\"),"
        "            edge,"
        "            width,"
        "            height"