Move SSD dark side coverage helper to typed Kotlin

ober

e895680349309e273f281d2a2e3085df8760ec0e

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 024db02..230f0d5 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -705,7 +705,7 @@
 
     (typed-kotlin-file "com/sfb/ssdreview/VisionArrays.kt"
       (typed-library (com sfb ssdreview)
-        (export grayAt edgeAt sideCoverage)
+        (export grayAt edgeAt sideCoverage darkSideCoverage)
         (type Int32)
         (type IntArray)
         (type BooleanArray)
@@ -815,6 +815,85 @@
                 (/ (float32 top) (float32 (array-max-int32 (int32 1) w)))
                 (/ (float32 bottom) (float32 (array-max-int32 (int32 1) w)))
                 (/ (float32 left) (float32 (array-max-int32 (int32 1) h)))
+                (/ (float32 right) (float32 (array-max-int32 (int32 1) h)))))))
+        (def (darkBandTop? (gray : IntArray)
+                           (width : Int32)
+                           (height : Int32)
+                           (px : Int32)
+                           (y : Int32)
+                           (band : Int32)) : Bool
+          (for/fold ((hit #f))
+                    ((offset (in-range (int32 0) band)))
+            (or hit (< (grayAt gray width height px (+ y offset)) (int32 80)))))
+        (def (darkBandBottom? (gray : IntArray)
+                              (width : Int32)
+                              (height : Int32)
+                              (px : Int32)
+                              (y : Int32)
+                              (h : Int32)
+                              (band : Int32)) : Bool
+          (for/fold ((hit #f))
+                    ((offset (in-range (int32 0) band)))
+            (or hit (< (grayAt gray width height px (- (+ y h) (+ (int32 1) offset))) (int32 80)))))
+        (def (darkBandLeft? (gray : IntArray)
+                            (width : Int32)
+                            (height : Int32)
+                            (x : Int32)
+                            (py : Int32)
+                            (band : Int32)) : Bool
+          (for/fold ((hit #f))
+                    ((offset (in-range (int32 0) band)))
+            (or hit (< (grayAt gray width height (+ x offset) py) (int32 80)))))
+        (def (darkBandRight? (gray : IntArray)
+                             (width : Int32)
+                             (height : Int32)
+                             (x : Int32)
+                             (py : Int32)
+                             (w : Int32)
+                             (band : Int32)) : Bool
+          (for/fold ((hit #f))
+                    ((offset (in-range (int32 0) band)))
+            (or hit (< (grayAt gray width height (- (+ x w) (+ (int32 1) offset)) py) (int32 80)))))
+        (def (darkSideCoverage (gray : IntArray)
+                               (width : Int32)
+                               (height : Int32)
+                               (x : Int32)
+                               (y : Int32)
+                               (w : Int32)
+                               (h : Int32)) : FloatArray
+          (let ((band (array-max-int32
+                        (int32 1)
+                        (array-min-int32
+                          (int32 2)
+                          (array-min-int32 (/ w (int32 2)) (/ h (int32 2)))))))
+            (let ((top
+                    (for/fold ((count (int32 0)))
+                              ((px (in-range x (+ x w))))
+                      (if (darkBandTop? gray width height px y band)
+                        (+ count (int32 1))
+                        count)))
+                  (bottom
+                    (for/fold ((count (int32 0)))
+                              ((px (in-range x (+ x w))))
+                      (if (darkBandBottom? gray width height px y h band)
+                        (+ count (int32 1))
+                        count)))
+                  (left
+                    (for/fold ((count (int32 0)))
+                              ((py (in-range y (+ y h))))
+                      (if (darkBandLeft? gray width height x py band)
+                        (+ count (int32 1))
+                        count)))
+                  (right
+                    (for/fold ((count (int32 0)))
+                              ((py (in-range y (+ y h))))
+                      (if (darkBandRight? gray width height x py w band)
+                        (+ count (int32 1))
+                        count))))
+              (float-array
+                (/ (float32 top) (float32 (array-max-int32 (int32 1) w)))
+                (/ (float32 bottom) (float32 (array-max-int32 (int32 1) w)))
+                (/ (float32 left) (float32 (array-max-int32 (int32 1) h)))
                 (/ (float32 right) (float32 (array-max-int32 (int32 1) h)))))))))
 
     (typed-kotlin-file "com/sfb/ssdreview/UiTextHelpers.kt"
@@ -3290,37 +3369,6 @@
        "        return merged.mapIndexed { index, cell -> cell.copy(id = \"c%04d\".format(index + 1)) }.toMutableList()"
        "    }"
        ""
-       "    private fun darkSideCoverage(gray: IntArray, width: Int, height: Int, x: Int, y: Int, w: Int, h: Int): FloatArray {"
-       "        val band = max(1, min(2, min(w / 2, h / 2)))"
-       "        val counts = IntArray(4)"
-       "        for (px in x until x + w) {"
-       "            var top = false"
-       "            var bottom = false"
-       "            for (offset in 0 until band) {"
-       "                top = top || grayAt(gray, width, height, px, y + offset) < 80"
-       "                bottom = bottom || grayAt(gray, width, height, px, y + h - 1 - offset) < 80"
-       "            }"
-       "            if (top) counts[0] += 1"
-       "            if (bottom) counts[1] += 1"
-       "        }"
-       "        for (py in y until y + h) {"
-       "            var left = false"
-       "            var right = false"
-       "            for (offset in 0 until band) {"
-       "                left = left || grayAt(gray, width, height, x + offset, py) < 80"
-       "                right = right || grayAt(gray, width, height, x + w - 1 - offset, py) < 80"
-       "            }"
-       "            if (left) counts[2] += 1"
-       "            if (right) counts[3] += 1"
-       "        }"
-       "        return floatArrayOf("
-       "            counts[0].toFloat() / max(1, w),"
-       "            counts[1].toFloat() / max(1, w),"
-       "            counts[2].toFloat() / max(1, h),"
-       "            counts[3].toFloat() / max(1, h)"
-       "        )"
-       "    }"
-       ""
        "    private fun edgeMask(gray: IntArray, width: Int, height: Int): BooleanArray {"
        "        val gradients = IntArray(width * height)"
        "        val samples = mutableListOf<Int>()"