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 }