much love
[mu.git] / 505colors.mu
blob45b13ab57f6e2e995ddbff28448022bceb52d183
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
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
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
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"
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"
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"
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
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
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
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
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
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
248 # cyan: 0x7f
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
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
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"
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
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
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