https://github.com/akkartik/mu/blob/main/apps/colors.mu
  1 # Return colors 'near' a given r/g/b value (expressed in hex)
  2 # If we did this rigorously we'd need to implement cosines. So we won't.
  3 #
  4 # To build:
  5 #   $ ./translate apps/colors.mu
  6 #
  7 # Example session:
  8 #   $ qemu-system-i386 code.img
  9 #   Enter 3 hex bytes for r, g, b (lowercase; no 0x prefix) separated by a single space> aa 0 aa
 10 #   5
 11 # This means only color 5 in the default palette is similar to #aa00aa.
 12 
 13 fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
 14   var in-storage: (stream byte 0x10)
 15   var in/esi: (addr stream byte) <- address in-storage
 16   {
 17     # print prompt
 18     var x/eax: int <- draw-text-rightward screen, "Enter 3 hex bytes for r, g, b (lowercase; no 0x prefix) separated by a single space> ", 0x10/x, 0x80/xmax, 0x28/y, 3/fg/cyan, 0/bg
 19     # read line from keyboard
 20     clear-stream in
 21     {
 22       draw-cursor screen, 0x20/space
 23       var key/eax: byte <- read-key keyboard
 24       compare key, 0xa/newline
 25       break-if-=
 26       compare key, 0
 27       loop-if-=
 28       var key2/eax: int <- copy key
 29       append-byte in, key2
 30       var g/eax: grapheme <- copy key2
 31       draw-grapheme-at-cursor screen, g, 0xf/fg, 0/bg
 32       move-cursor-right 0
 33       loop
 34     }
 35     clear-screen screen
 36     # parse
 37     var a/ecx: int <- copy 0
 38     var b/edx: int <- copy 0
 39     var c/ebx: int <- copy 0
 40     # a, b, c = r, g, b
 41     a, b, c <- parse in
 42 #?     set-cursor-position screen, 0x10/x, 0x1a/y
 43 #?     draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, a, 7/fg, 0/bg
 44 #?     draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " ", 7/fg, 0/bg
 45 #?     draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, b, 7/fg, 0/bg
 46 #?     draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " ", 7/fg, 0/bg
 47 #?     draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, c, 7/fg, 0/bg
 48     a, b, c <- hsl a, b, c
 49     # return all colors in the same quadrant in h, s and l
 50     print-nearby-colors screen, a, b, c
 51     # another metric
 52     var color/eax: int <- nearest-color-euclidean-hsl a, b, c
 53     set-cursor-position screen, 0x10/x, 0x26/y
 54     draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "nearest (euclidean, h/s/l): ", 0xf/fg, 0/bg
 55     draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen screen, color, 7/fg, 0/bg
 56     draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " ", 0xf/fg, 0/bg
 57     draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "               ", 0/fg, color
 58     #
 59     loop
 60   }
 61 }
 62 
 63 # read exactly 3 words in a single line
 64 # Each word consists of exactly 1 or 2 hex bytes. No hex prefix.
 65 fn parse in: (addr stream byte) -> _/ecx: int, _/edx: int, _/ebx: int {
 66   # read first byte of r
 67   var tmp/eax: byte <- read-byte in
 68   {
 69     var valid?/eax: boolean <- hex-digit? tmp
 70     compare valid?, 0/false
 71     break-if-!=
 72     abort "invalid byte 0 of r"
 73   }
 74   tmp <- fast-hex-digit-value tmp
 75   var r/ecx: int <- copy tmp
 76 #?   set-cursor-position 0/screen, 0x10/x, 0x10/y
 77 #?   draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, r, 7/fg, 0/bg
 78   # read second byte of r
 79   tmp <- read-byte in
 80   {
 81     {
 82       var valid?/eax: boolean <- hex-digit? tmp
 83       compare valid?, 0/false
 84     }
 85     break-if-=
 86     r <- shift-left 4
 87     tmp <- fast-hex-digit-value tmp
 88 #?     {
 89 #?       var foo/eax: int <- copy tmp
 90 #?       set-cursor-position 0/screen, 0x10/x, 0x11/y
 91 #?       draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, foo, 7/fg, 0/bg
 92 #?     }
 93     r <- add tmp
 94 #?     {
 95 #?       set-cursor-position 0/screen, 0x10/x, 0x12/y
 96 #?       draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, r, 7/fg, 0/bg
 97 #?     }
 98     tmp <- read-byte in  # skip space
 99   }
100   # read first byte of g
101   var tmp/eax: byte <- read-byte in
102   {
103     var valid?/eax: boolean <- hex-digit? tmp
104     compare valid?, 0/false
105     break-if-!=
106     abort "invalid byte 0 of g"
107   }
108   tmp <- fast-hex-digit-value tmp
109   var g/edx: int <- copy tmp
110 #?   set-cursor-position 0/screen, 0x10/x, 0x13/y
111 #?   draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, g, 7/fg, 0/bg
112   # read second byte of g
113   tmp <- read-byte in
114   {
115     {
116       var valid?/eax: boolean <- hex-digit? tmp
117       compare valid?, 0/false
118     }
119     break-if-=
120     g <- shift-left 4
121     tmp <- fast-hex-digit-value tmp
122 #?     {
123 #?       var foo/eax: int <- copy tmp
124 #?       set-cursor-position 0/screen, 0x10/x, 0x14/y
125 #?       draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, foo, 7/fg, 0/bg
126 #?     }
127     g <- add tmp
128 #?     {
129 #?       set-cursor-position 0/screen, 0x10/x, 0x15/y
130 #?       draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, g, 7/fg, 0/bg
131 #?     }
132     tmp <- read-byte in  # skip space
133   }
134   # read first byte of b
135   var tmp/eax: byte <- read-byte in
136   {
137     var valid?/eax: boolean <- hex-digit? tmp
138     compare valid?, 0/false
139     break-if-!=
140     abort "invalid byte 0 of b"
141   }
142   tmp <- fast-hex-digit-value tmp
143   var b/ebx: int <- copy tmp
144 #?   set-cursor-position 0/screen, 0x10/x, 0x16/y
145 #?   draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, b, 7/fg, 0/bg
146   # read second byte of b
147   {
148     {
149       var done?/eax: boolean <- stream-empty? in
150       compare done?, 0/false
151     }
152     break-if-!=
153     tmp <- read-byte in
154     {
155       var valid?/eax: boolean <- hex-digit? tmp
156       compare valid?, 0/false
157     }
158     break-if-=
159     b <- shift-left 4
160     tmp <- fast-hex-digit-value tmp
161 #?     {
162 #?       var foo/eax: int <- copy tmp
163 #?       set-cursor-position 0/screen, 0x10/x, 0x17/y
164 #?       draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, foo, 7/fg, 0/bg
165 #?     }
166     b <- add tmp
167 #?     {
168 #?       set-cursor-position 0/screen, 0x10/x, 0x18/y
169 #?       draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, b, 7/fg, 0/bg
170 #?     }
171   }
172   return r, g, b
173 }
174 
175 # no error checking
176 fn fast-hex-digit-value in: byte -> _/eax: byte {
177   var result/eax: byte <- copy in
178   compare result, 0x39
179   {
180     break-if->
181     result <- subtract 0x30/0
182     return result
183   }
184   result <- subtract 0x61/a
185   result <- add 0xa/10
186   return result
187 }
188 
189 fn print-nearby-colors screen: (addr screen), h: int, s: int, l: int {
190 #?   set-cursor-position screen, 0x10/x, 0x1c/y
191 #?   draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, h, 7/fg, 0/bg
192 #?   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " ", 7/fg, 0/bg
193 #?   draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, s, 7/fg, 0/bg
194 #?   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " ", 7/fg, 0/bg
195 #?   draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, l, 7/fg, 0/bg
196   # save just top 2 bits of each, so that we narrow down to 1/64th of the volume
197   shift-right h, 6
198   shift-right s, 6
199   shift-right l, 6
200 #?   set-cursor-position screen, 0x10/x, 0x1/y
201 #?   draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, h, 7/fg, 0/bg
202 #?   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " ", 7/fg, 0/bg
203 #?   draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, s, 7/fg, 0/bg
204 #?   draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " ", 7/fg, 0/bg
205 #?   draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, l, 7/fg, 0/bg
206   var a/ecx: int <- copy 0
207   var b/edx: int <- copy 0
208   var c/ebx: int <- copy 0
209   var color/eax: int <- copy 0
210   var y/esi: int <- copy 2
211   {
212     compare color, 0x100
213     break-if->=
214     a, b, c <- color-rgb color
215     a, b, c <- hsl a, b, c
216     a <- shift-right 6
217     b <- shift-right 6
218     c <- shift-right 6
219     {
220       compare a, h
221       break-if-!=
222       compare b, s
223       break-if-!=
224       compare c, l
225       break-if-!=
226       set-cursor-position screen, 0x10/x, y
227       draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen screen, color, 7/fg, 0/bg
228       set-cursor-position screen, 0x14/x, y
229       draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " ", 7/fg, 0/bg
230       draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, "               ", 0/fg, color
231 #?       draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " ", 7/fg, 0/bg
232 #?       draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, a, 7/fg, 0/bg
233 #?       draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " ", 7/fg, 0/bg
234 #?       draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, b, 7/fg, 0/bg
235 #?       draw-text-wrapping-right-then-down-from-cursor-over-full-screen screen, " ", 7/fg, 0/bg
236 #?       draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen screen, c, 7/fg, 0/bg
237       y <- increment
238     }
239     color <- increment
240     loop
241   }
242 }