Move SSD detector shrink bounds to typed Kotlin

ober

8ec1c0561d6fec98a68daac1013711b34b419825

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index e940910..ede2d89 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -2130,7 +2130,8 @@
 
     (typed-kotlin-file "com/sfb/ssdreview/VisionArrays.kt"
       (typed-library (com sfb ssdreview)
-        (export grayAt edgeAt edgeMask sideCoverage darkSideCoverage contourCell toneCell)
+        (export grayAt edgeAt edgeMask sideCoverage darkSideCoverage contourCell toneCell
+                shrinkMaskBounds)
         (type Int32)
         (type IntArray)
         (type BooleanArray)
@@ -2144,6 +2145,54 @@
           (if (< value (int32 0))
             (- (int32 0) value)
             value))
+        (def (intArrayFirstIndexAtLeast (items : IntArray)
+                                      (threshold : Int32)) : Int32
+          (for/fold ((found (int32 -1)))
+                    ((index (in-range (int32 0) (int-array-length items))))
+            (if (and (< found (int32 0))
+                     (>= (int-array-ref items index) threshold))
+                index
+                found)))
+        (def (intArrayLastIndexAtLeast (items : IntArray)
+                                     (threshold : Int32)) : Int32
+          (for/fold ((found (int32 -1)))
+                    ((index (in-range (int32 0) (int-array-length items))))
+            (if (>= (int-array-ref items index) threshold)
+                index
+                found)))
+        (def (shrinkMaskThresholdForSpan (span : Int32)) : Int32
+          (array-max-int32
+            (int32 2)
+            (array-min-int32
+              (int32 10)
+              (/ (* span (int32 12)) (int32 1000)))))
+        (def (clampLowerInt32 (value : Int32) (floor : Int32)) : Int32
+          (if (< value floor) floor value))
+        (def (clampUpperInt32 (value : Int32) (ceiling : Int32)) : Int32
+          (if (> value ceiling) ceiling value))
+        (def (shrinkMaskBounds (rows : IntArray)
+                               (cols : IntArray)
+                               (originX : Int32)
+                               (originY : Int32)
+                               (bitmapWidth : Int32)
+                               (bitmapHeight : Int32)) : (Nullable FloatArray)
+          (let ((rowThreshold (shrinkMaskThresholdForSpan (int-array-length cols)))
+                (colThreshold (shrinkMaskThresholdForSpan (int-array-length rows))))
+            (let ((minX (intArrayFirstIndexAtLeast cols colThreshold))
+                  (maxX (intArrayLastIndexAtLeast cols colThreshold))
+                  (minY (intArrayFirstIndexAtLeast rows rowThreshold))
+                  (maxY (intArrayLastIndexAtLeast rows rowThreshold)))
+              (if (or (or (< minX (int32 0))
+                          (< minY (int32 0)))
+                      (or (<= maxX minX)
+                          (<= maxY minY)))
+                  (nullable-none FloatArray)
+                  (nullable-some
+                    (float-array
+                      (float32 (clampLowerInt32 (- (+ originX minX) (int32 1)) (int32 0)))
+                      (float32 (clampLowerInt32 (- (+ originY minY) (int32 1)) (int32 0)))
+                      (float32 (clampUpperInt32 (+ (+ originX maxX) (int32 2)) bitmapWidth))
+                      (float32 (clampUpperInt32 (+ (+ originY maxY) (int32 2)) bitmapHeight))))))))
         (def (coverage-count-at-least (coverage : FloatArray) (threshold : Float32)) : Int32
           (for/fold ((count (int32 0)))
                     ((index (in-range (int32 0) (float-array-length coverage))))
@@ -4807,19 +4856,7 @@
        "                }"
        "            }"
        "        }"
-       "        val rowThreshold = max(2, min(10, (w * 0.012f).toInt()))"
-       "        val colThreshold = max(2, min(10, (h * 0.012f).toInt()))"
-       "        val minX = cols.indexOfFirst { it >= colThreshold }"
-       "        val maxX = cols.indexOfLast { it >= colThreshold }"
-       "        val minY = rows.indexOfFirst { it >= rowThreshold }"
-       "        val maxY = rows.indexOfLast { it >= rowThreshold }"
-       "        if (minX < 0 || minY < 0 || maxX <= minX || maxY <= minY) return null"
-       "        return floatArrayOf("
-       "            (x1 + minX - 1).coerceAtLeast(0).toFloat(),"
-       "            (y1 + minY - 1).coerceAtLeast(0).toFloat(),"
-       "            (x1 + maxX + 2).coerceAtMost(bitmap.width).toFloat(),"
-       "            (y1 + maxY + 2).coerceAtMost(bitmap.height).toFloat()"
-       "        )"
+       "        return shrinkMaskBounds(rows, cols, x1, y1, bitmap.width, bitmap.height)"
        "    }"
        ""
        "    private fun detectMask("