Move SSD shrink mask accumulation to typed Kotlin

ober

c8f92e88075356d2cc83ee04a47b2540419863d7

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 5c0572e..778a78b 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -2759,7 +2759,7 @@
         (export grayAt edgeAt edgeMask sideCoverage darkSideCoverage contourCell toneCell
                 detectColorComponents detectToneComponents detectEdgeComponents
                 detectorComponentCells detectorToneCells detectorContourCells
-                shrinkMaskBounds)
+                shrinkMaskBounds shrinkInkMaskBounds shrinkBoxFillMaskBounds)
         (type Int32)
         (type IntArray)
         (type BooleanArray)
@@ -2821,6 +2821,76 @@
                       (float32 (clampLowerInt32 (- (+ originY minY) (int32 1)) (int32 0)))
                       (float32 (clampUpperInt32 (+ (+ originX maxX) (int32 2)) bitmapWidth))
                       (float32 (clampUpperInt32 (+ (+ originY maxY) (int32 2)) bitmapHeight))))))))
+        (def (pixelShrinkMaskAt (kind : Int32) (pixel : Int32)) : Bool
+          (if (= kind (int32 0))
+            (looksLikeInk pixel)
+            (looksLikeBoxFill pixel)))
+        (def (shrinkPixelMaskBounds (pixels : IntArray)
+                                    (width : Int32)
+                                    (height : Int32)
+                                    (originX : Int32)
+                                    (originY : Int32)
+                                    (bitmapWidth : Int32)
+                                    (bitmapHeight : Int32)
+                                    (kind : Int32)) : (Nullable FloatArray)
+          (let ((rows
+                  (int-array-build
+                    height
+                    (index (int32 0))))
+                (cols
+                  (int-array-build
+                    width
+                    (index (int32 0)))))
+            (begin
+              (for/fold ((ignored (int32 0)))
+                        ((index (in-range (int32 0) (* width height))))
+                (if (pixelShrinkMaskAt kind (int-array-ref pixels index))
+                  (let ((x (mod index width))
+                        (y (/ index width)))
+                    (begin
+                      (int-array-set! rows y (+ (int-array-ref rows y) (int32 1)))
+                      (int-array-set! cols x (+ (int-array-ref cols x) (int32 1)))
+                      ignored))
+                  ignored))
+              (shrinkMaskBounds
+                rows
+                cols
+                originX
+                originY
+                bitmapWidth
+                bitmapHeight))))
+        (def (shrinkInkMaskBounds (pixels : IntArray)
+                                  (width : Int32)
+                                  (height : Int32)
+                                  (originX : Int32)
+                                  (originY : Int32)
+                                  (bitmapWidth : Int32)
+                                  (bitmapHeight : Int32)) : (Nullable FloatArray)
+          (shrinkPixelMaskBounds
+            pixels
+            width
+            height
+            originX
+            originY
+            bitmapWidth
+            bitmapHeight
+            (int32 0)))
+        (def (shrinkBoxFillMaskBounds (pixels : IntArray)
+                                      (width : Int32)
+                                      (height : Int32)
+                                      (originX : Int32)
+                                      (originY : Int32)
+                                      (bitmapWidth : Int32)
+                                      (bitmapHeight : Int32)) : (Nullable FloatArray)
+          (shrinkPixelMaskBounds
+            pixels
+            width
+            height
+            originX
+            originY
+            bitmapWidth
+            bitmapHeight
+            (int32 1)))
         (def (coverage-count-at-least (coverage : FloatArray) (threshold : Float32)) : Int32
           (for/fold ((count (int32 0)))
                     ((index (in-range (int32 0) (float-array-length coverage))))
@@ -6025,17 +6095,17 @@
        "    }"
        ""
        "    fun shrinkRectToInk(bitmap: Bitmap, rect: FloatArray): FloatArray? {"
-       "        return shrinkRectByMask(bitmap, rect) { pixel -> looksLikeInk(pixel) }"
+       "        return shrinkRectByMaskKind(bitmap, rect, 0)"
        "    }"
        ""
        "    fun shrinkRectToBoxFill(bitmap: Bitmap, rect: FloatArray): FloatArray? {"
-       "        return shrinkRectByMask(bitmap, rect) { pixel -> looksLikeBoxFill(pixel) }"
+       "        return shrinkRectByMaskKind(bitmap, rect, 1)"
        "    }"
        ""
-       "    private fun shrinkRectByMask("
+       "    private fun shrinkRectByMaskKind("
        "        bitmap: Bitmap,"
        "        rect: FloatArray,"
-       "        masked: (Int) -> Boolean"
+       "        kind: Int"
        "    ): FloatArray? {"
        "        val x1 = rect[0].toInt().coerceIn(0, bitmap.width - 1)"
        "        val y1 = rect[1].toInt().coerceIn(0, bitmap.height - 1)"
@@ -6046,17 +6116,11 @@
        "        if (w < 4 || h < 4 || w * h > 1_200_000) return null"
        "        val pixels = IntArray(w * h)"
        "        bitmap.getPixels(pixels, 0, w, x1, y1, w, h)"
-       "        val rows = IntArray(h)"
-       "        val cols = IntArray(w)"
-       "        for (y in 0 until h) {"
-       "            for (x in 0 until w) {"
-       "                if (masked(pixels[y * w + x])) {"
-       "                    rows[y] += 1"
-       "                    cols[x] += 1"
-       "                }"
-       "            }"
+       "        return if (kind == 0) {"
+       "            shrinkInkMaskBounds(pixels, w, h, x1, y1, bitmap.width, bitmap.height)"
+       "        } else {"
+       "            shrinkBoxFillMaskBounds(pixels, w, h, x1, y1, bitmap.width, bitmap.height)"
        "        }"
-       "        return shrinkMaskBounds(rows, cols, x1, y1, bitmap.width, bitmap.height)"
        "    }"
        ""
        "    private fun contourCells(width: Int, height: Int, gray: IntArray): List<SsdCell> {"