https://github.com/akkartik/mu/blob/main/505colors.mu

      1 fn nearest-color-euclidean r: int, g: int, b: int -> _/eax: int {
      2   var result/edi: int <- copy 0x100/invalid
      3   var max-distance/esi: int <- copy 0x30000/max  # 3 * 0x100*0x100
      4   var r2/ecx: int <- copy 0
      5   var g2/edx: int <- copy 0
      6   var b2/ebx: int <- copy 0
      7   var color/eax: int <- copy 0
      8   {
      9     compare color, 0x100
     10     break-if->=
     11     $nearest-color-euclidean:body: {
     12       r2, g2, b2 <- color-rgb color
     13       {
     14         var curr-distance/eax: int <- euclidean-distance-squared r, g, b, r2, g2, b2
     15         compare curr-distance, max-distance
     16         break-if->= $nearest-color-euclidean:body
     17         max-distance <- copy curr-distance
     18       }
     19       result <- copy color
     20     }
     21     color <- increment
     22     loop
     23   }
     24   return result
     25 }
     26
     27 fn euclidean-distance-squared r1: int, g1: int, b1: int, r2: int, g2: int, b2: int -> _/eax: int {
     28   var result/edi: int <- copy 0
     29   # red
     30   var tmp/eax: int <- copy r1
     31   tmp <- subtract r2
     32   tmp <- multiply tmp
     33   result <- add tmp
     34   # green
     35   tmp <- copy g1
     36   tmp <- subtract g2
     37   tmp <- multiply tmp
     38   result <- add tmp
     39   # blue
     40   tmp <- copy b1
     41   tmp <- subtract b2
     42   tmp <- multiply tmp
     43   result <- add tmp
     44   return result
     45 }
     46
     47 # Hue/saturation/luminance for an rgb triple.
     48 # rgb are in [0, 256)
     49 # hsl are also returned in [0, 256)
     50 # from https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl
     51 fn hsl r: int, g: int, b: int -> _/ecx: int, _/edx: int, _/ebx: int {
     52   var _max/eax: int <- maximum r, g
     53   _max <- maximum _max, b
     54   var max/ecx: int <- copy _max
     55   var _min/eax: int <- minimum r, g
     56   _min <- minimum _min, b
     57   var min/edx: int <- copy _min
     58   var luminance/ebx: int <- copy min
     59   luminance <- add max
     60   luminance <- shift-right 1  # TODO: round up instead of down
     61   # if rgb are all equal, it's a shade of grey
     62   compare min, max
     63   {
     64     break-if-!=
     65     return 0, 0, luminance
     66   }
     67   # saturation =
     68   #   luminance < 128 | 255*(max-min)/         (max+min)
     69   #   otherwise       | 255*(max-min)/(2*255 - (max+min))
     70   var nr/esi: int <- copy max
     71   nr <- subtract min
     72   var dr/eax: int <- copy 0
     73   compare luminance, 0x80
     74   {
     75     break-if->=
     76     dr <- copy max
     77     dr <- add min
     78   }
     79   {
     80     break-if-<
     81     dr <- copy 0xff
     82     dr <- shift-left 1
     83     dr <- subtract max
     84     dr <- subtract min
     85   }
     86   var q/xmm0: float <- convert nr
     87   var tmp/xmm1: float <- convert dr
     88   q <- divide tmp
     89   var int-255/eax: int <- copy 0xff
     90   tmp <- convert int-255
     91   q <- multiply tmp
     92   var saturation/esi: int <- convert q
     93   # hue =
     94   #   red is max   | 256.0/6*       (g-b)/(max-min)
     95   #   green is max | 256.0/6*(2.0 + (b-r)/(max-min))
     96   #   blue is max  | 256.0/6*(4.0 + (r-g)/(max-min))
     97   var zero/eax: int <- copy 0
     98   var hue-f/xmm0: float <- convert zero
     99   var dr/eax: int <- copy max
    100   dr <- subtract min
    101   var dr-f/xmm1: float <- convert dr
    102   $hsl:compute-hue-normalized: {
    103     compare r, max
    104     {
    105       break-if-!=
    106       var nr/eax: int <- copy g
    107       nr <- subtract b
    108       hue-f <- convert nr
    109       hue-f <- divide dr-f
    110       break $hsl:compute-hue-normalized
    111     }
    112     compare g, max
    113     {
    114       break-if-!=
    115       var nr/eax: int <- copy b
    116       nr <- subtract r
    117       var f/xmm2: float <- convert nr
    118       f <- divide dr-f
    119       var two/ecx: int <- copy 2
    120       hue-f <- convert two
    121       hue-f <- add f
    122       break $hsl:compute-hue-normalized
    123     }
    124     compare b, max
    125     {
    126       break-if-!=
    127       var nr/eax: int <- copy r
    128       nr <- subtract g
    129       var f/xmm2: float <- convert nr
    130       f <- divide dr-f
    131       var two/ecx: int <- copy 4
    132       hue-f <- convert two
    133       hue-f <- add f
    134       break $hsl:compute-hue-normalized
    135     }
    136   }
    137   var int-256/eax: int <- copy 0x100
    138   var scaling-factor/xmm1: float <- convert int-256
    139   var int-6/eax: int <- copy 6
    140   var six-f/xmm2: float <- convert int-6
    141   scaling-factor <- divide six-f
    142   hue-f <- multiply scaling-factor
    143   var hue/eax: int <- convert hue-f
    144   # if hue < 0, hue = 256 - hue
    145   compare hue, 0
    146   {
    147     break-if->=
    148     var tmp/ecx: int <- copy 0x100
    149     tmp <- subtract hue
    150     hue <- copy tmp
    151   }
    152   return hue, saturation, luminance
    153 }
    154
    155 fn test-hsl-black {
    156   var h/ecx: int <- copy 0
    157   var s/edx: int <- copy 0
    158   var l/ebx: int <- copy 0
    159   h, s, l <- hsl 0, 0, 0
    160   check-ints-equal h, 0, "F - test-hsl-black/hue"
    161   check-ints-equal s, 0, "F - test-hsl-black/saturation"
    162   check-ints-equal l, 0, "F - test-hsl-black/luminance"
    163 }
    164
    165 fn test-hsl-white {
    166   var h/ecx: int <- copy 0
    167   var s/edx: int <- copy 0
    168   var l/ebx: int <- copy 0
    169   h, s, l <- hsl 0xff, 0xff, 0xff
    170   check-ints-equal h, 0, "F - test-hsl-white/hue"
    171   check-ints-equal s, 0, "F - test-hsl-white/saturation"
    172   check-ints-equal l, 0xff, "F - test-hsl-white/luminance"
    173 }
    174
    175 fn test-hsl-grey {
    176   var h/ecx: int <- copy 0
    177   var s/edx: int <- copy 0
    178   var l/ebx: int <- copy 0
    179   h, s, l <- hsl 0x30, 0x30, 0x30
    180   check-ints-equal h, 0, "F - test-hsl-grey/hue"
    181   check-ints-equal s, 0, "F - test-hsl-grey/saturation"
    182   check-ints-equal l, 0x30, "F - test-hsl-grey/luminance"
    183 }
    184
    185 # red hues: 0-0x54
    186 fn test-hsl-slightly-red {
    187   var h/ecx: int <- copy 0
    188   var s/edx: int <- copy 0
    189   var l/ebx: int <- copy 0
    190   h, s, l <- hsl 0xff, 0xfe, 0xfe
    191   check-ints-equal h, 0, "F - test-hsl-slightly-red/hue"
    192   check-ints-equal s, 0xff, "F - test-hsl-slightly-red/saturation"
    193   check-ints-equal l, 0xfe, "F - test-hsl-slightly-red/luminance"  # TODO: should round up
    194 }
    195
    196 fn test-hsl-extremely-red {
    197   var h/ecx: int <- copy 0
    198   var s/edx: int <- copy 0
    199   var l/ebx: int <- copy 0
    200   h, s, l <- hsl 0xff, 0, 0
    201   check-ints-equal h, 0, "F - test-hsl-extremely-red/hue"
    202   check-ints-equal s, 0xff, "F - test-hsl-extremely-red/saturation"
    203   check-ints-equal l, 0x7f, "F - test-hsl-extremely-red/luminance"  # TODO: should round up
    204 }
    205
    206 # green hues: 0x55-0xaa
    207 fn test-hsl-slightly-green {
    208   var h/ecx: int <- copy 0
    209   var s/edx: int <- copy 0
    210   var l/ebx: int <- copy 0
    211   h, s, l <- hsl 0xfe, 0xff, 0xfe
    212   check-ints-equal h, 0x55, "F - test-hsl-slightly-green/hue"
    213   check-ints-equal s, 0xff, "F - test-hsl-slightly-green/saturation"
    214   check-ints-equal l, 0xfe, "F - test-hsl-slightly-green/luminance"  # TODO: should round up
    215 }
    216
    217 fn test-hsl-extremely-green {
    218   var h/ecx: int <- copy 0
    219   var s/edx: int <- copy 0
    220   var l/ebx: int <- copy 0
    221   h, s, l <- hsl 0, 0xff, 0
    222   check-ints-equal h, 0x55, "F - test-hsl-extremely-green/hue"
    223   check-ints-equal s, 0xff, "F - test-hsl-extremely-green/saturation"
    224   check-ints-equal l, 0x7f, "F - test-hsl-extremely-green/luminance"  # TODO: should round up
    225 }
    226
    227 # blue hues: 0xab-0xff
    228 fn test-hsl-slightly-blue {
    229   var h/ecx: int <- copy 0
    230   var s/edx: int <- copy 0
    231   var l/ebx: int <- copy 0
    232   h, s, l <- hsl 0xfe, 0xfe, 0xff
    233   check-ints-equal h, 0xab, "F - test-hsl-slightly-blue/hue"
    234   check-ints-equal s, 0xff, "F - test-hsl-slightly-blue/saturation"
    235   check-ints-equal l, 0xfe, "F - test-hsl-slightly-blue/luminance"  # TODO: should round up
    236 }
    237
    238 fn test-hsl-extremely-blue {
    239   var h/ecx: int <- copy 0
    240   var s/edx: int <- copy 0
    241   var l/ebx: int <- copy 0
    242   h, s, l <- hsl 0, 0, 0xff
    243   check-ints-equal h, 0xab, "F - test-hsl-extremely-blue/hue"
    244   check-ints-equal s, 0xff, "F - test-hsl-extremely-blue/saturation"
    245   check-ints-equal l, 0x7f, "F - test-hsl-extremely-blue/luminance"  # TODO: should round up
    246 }
    247
    248 # cyan: 0x7f
    249
    250 fn test-hsl-cyan {
    251   var h/ecx: int <- copy 0
    252   var s/edx: int <- copy 0
    253   var l/ebx: int <- copy 0
    254   h, s, l <- hsl 0, 0xff, 0xff
    255   check-ints-equal h, 0x80, "F - test-hsl-cyan/hue"
    256   check-ints-equal s, 0xff, "F - test-hsl-cyan/saturation"
    257   check-ints-equal l, 0x7f, "F - test-hsl-cyan/luminance"  # TODO: should round up
    258 }
    259
    260 fn nearest-color-euclidean-hsl h: int, s: int, l: int -> _/eax: int {
    261   var result/edi: int <- copy 0x100/invalid
    262   var max-distance/esi: int <- copy 0x30000/max  # 3 * 0x100*0x100
    263   var a/ecx: int <- copy 0
    264   var b/edx: int <- copy 0
    265   var c/ebx: int <- copy 0
    266   var color/eax: int <- copy 0
    267   {
    268     compare color, 0x100
    269     break-if->=
    270     $nearest-color-euclidean-hsl:body: {
    271       a, b, c <- color-rgb color
    272       a, b, c <- hsl a, b, c
    273       {
    274         var curr-distance/eax: int <- euclidean-hsl-squared a, b, c, h, s, l
    275         compare curr-distance, max-distance
    276         break-if->= $nearest-color-euclidean-hsl:body
    277         max-distance <- copy curr-distance
    278       }
    279       result <- copy color
    280     }
    281     color <- increment
    282     loop
    283   }
    284   return result
    285 }
    286
    287 fn test-nearest-color-euclidean-hsl {
    288   # red from lightest to darkest
    289   var red/eax: int <- nearest-color-euclidean-hsl 0, 0xff, 0xff
    290   check-ints-equal red, 0x58/88, "F - test-nearest-color-euclidean-hsl/full-red1"
    291   red <- nearest-color-euclidean-hsl 0, 0xff, 0xc0
    292   check-ints-equal red, 0x40/64, "F - test-nearest-color-euclidean-hsl/full-red2"
    293   red <- nearest-color-euclidean-hsl 0, 0xff, 0x80
    294   check-ints-equal red, 0x28/40, "F - test-nearest-color-euclidean-hsl/full-red3"
    295   red <- nearest-color-euclidean-hsl 0, 0xff, 0x40
    296   check-ints-equal red, 0x28/40, "F - test-nearest-color-euclidean-hsl/full-red4"
    297   red <- nearest-color-euclidean-hsl 0, 0xff, 0
    298   check-ints-equal red, 0x28/40, "F - test-nearest-color-euclidean-hsl/full-red5"
    299   # try a number really close to red but on the other side of the cylinder
    300   red <- nearest-color-euclidean-hsl 0xff, 0xff, 0xff
    301 #?   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, red, 7/fg 0/bg
    302   check-ints-equal red, 0x57/87, "F - test-nearest-color-euclidean-hsl/other-end-of-red"  # still looks red
    303   # half-saturation red from lightest to darkest
    304   red <- nearest-color-euclidean-hsl 0, 0x80, 0xff
    305   check-ints-equal red, 0xf/15, "F - test-nearest-color-euclidean-hsl/half-red1"   # ?? grey ??
    306   red <- nearest-color-euclidean-hsl 0, 0x80, 0xc0
    307   check-ints-equal red, 4, "F - test-nearest-color-euclidean-hsl/half-red2"
    308   red <- nearest-color-euclidean-hsl 0, 0x80, 0x80
    309   check-ints-equal red, 4, "F - test-nearest-color-euclidean-hsl/half-red3"
    310   red <- nearest-color-euclidean-hsl 0, 0x80, 0x40
    311   check-ints-equal red, 4, "F - test-nearest-color-euclidean-hsl/half-red4"
    312   red <- nearest-color-euclidean-hsl 0, 0x80, 0
    313   check-ints-equal red, 0x70/112, "F - test-nearest-color-euclidean-hsl/half-red5"
    314 }
    315
    316 fn euclidean-hsl-squared h1: int, s1: int, l1: int, h2: int, s2: int, l2: int -> _/eax: int {
    317   var result/edi: int <- copy 0
    318   # hue
    319   var tmp/eax: int <- copy h1
    320   tmp <- subtract h2
    321   tmp <- multiply tmp
    322   # TODO: should we do something to reflect that hue is a cylindrical space?
    323   # I can't come up with a failing test.
    324   result <- add tmp
    325   # saturation
    326   tmp <- copy s1
    327   tmp <- subtract s2
    328   tmp <- multiply tmp
    329   result <- add tmp
    330   # luminance
    331   tmp <- copy l1
    332   tmp <- subtract l2
    333   tmp <- multiply tmp
    334   result <- add tmp
    335   return result
    336 }
    337
    338 ###
    339
    340 fn maximum a: int, b: int -> _/eax: int {
    341   var a2/eax: int <- copy a
    342   compare a2, b
    343   {
    344     break-if-<
    345     return a
    346   }
    347   return b
    348 }
    349
    350 fn minimum a: int, b: int -> _/eax: int {
    351   var a2/eax: int <- copy a
    352   compare a2, b
    353   {
    354     break-if->
    355     return a
    356   }
    357   return b
    358 }