From e2b6baf1ca41ec0e95faf118dace8257d101ed0a Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 13 Jul 2021 23:58:10 -0700 Subject: [PATCH] dithering ppm files using all 256 colors Not quite working yet, but yields an interesting 'sketching-like' effect. --- img.mu | 207 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 205 insertions(+), 2 deletions(-) diff --git a/img.mu b/img.mu index 4f244220..49c05581 100644 --- a/img.mu +++ b/img.mu @@ -114,7 +114,10 @@ fn render-image screen: (addr screen), _img: (addr image), xmin: int, ymin: int, { compare *type-a, 3/ppm break-if-!= - render-ppm-image screen, img, xmin, ymin, width, height + var img2-storage: image + var img2/edi: (addr image) <- address img2-storage + dither-ppm-unordered img, img2 + render-raw-image screen, img2, xmin, ymin, width, height return } abort "render-image: unrecognized image type" @@ -740,8 +743,10 @@ fn initialize-image-from-ppm _self: (addr image), in: (addr stream byte) { break-if-= abort "initialize-image-from-ppm: supports exactly 255 levels per rgb channel" } + var dest/edi: (addr int) <- get self, max + copy-to *dest, tmp # save width, height - var dest/edi: (addr int) <- get self, width + dest <- get self, width copy-to *dest, width dest <- get self, height copy-to *dest, height @@ -869,6 +874,204 @@ fn render-ppm-image screen: (addr screen), _img: (addr image), xmin: int, ymin: } } +fn dither-ppm-unordered _src: (addr image), _dest: (addr image) { + var src/esi: (addr image) <- copy _src + var dest/edi: (addr image) <- copy _dest + # copy 'width' + var src-width-a/eax: (addr int) <- get src, width + var tmp/eax: int <- copy *src-width-a + var src-width: int + copy-to src-width, tmp + { + var dest-width-a/edx: (addr int) <- get dest, width + copy-to *dest-width-a, tmp + } + # copy 'height' + var src-height-a/eax: (addr int) <- get src, height + var tmp/eax: int <- copy *src-height-a + var src-height: int + copy-to src-height, tmp + { + var dest-height-a/ecx: (addr int) <- get dest, height + copy-to *dest-height-a, tmp + } + # compute scaling factor 255/max + var target-scale/eax: int <- copy 0xff + var scale-f/xmm7: float <- convert target-scale + var src-max-a/eax: (addr int) <- get src, max + var tmp-f/xmm0: float <- convert *src-max-a + scale-f <- divide tmp-f + # allocate 'data' + var capacity/ebx: int <- copy src-width + capacity <- multiply src-height + var dest/edi: (addr image) <- copy _dest + var dest-data-ah/eax: (addr handle array byte) <- get dest, data + populate dest-data-ah, capacity + var _dest-data/eax: (addr array byte) <- lookup *dest-data-ah + var dest-data/edi: (addr array byte) <- copy _dest-data + # error buffers per r/g/b channel + var red-errors-storage: (array int 0xc0000) + var tmp/eax: (addr array int) <- address red-errors-storage + var red-errors: (addr array int) + copy-to red-errors, tmp + var green-errors-storage: (array int 0xc0000) + var tmp/eax: (addr array int) <- address green-errors-storage + var green-errors: (addr array int) + copy-to green-errors, tmp + var blue-errors-storage: (array int 0xc0000) + var tmp/eax: (addr array int) <- address blue-errors-storage + var blue-errors: (addr array int) + copy-to blue-errors, tmp + # transform 'data' + var src-data-ah/eax: (addr handle array byte) <- get src, data + var _src-data/eax: (addr array byte) <- lookup *src-data-ah + var src-data/esi: (addr array byte) <- copy _src-data + var y/edx: int <- copy 0 + { + compare y, src-height + break-if->= + var x/ecx: int <- copy 0 + { + compare x, src-width + break-if->= + # - update errors and compute color levels for current pixel in each channel + # update red-error with current image pixel + var red-error: int + { + var tmp/esi: int <- _read-dithering-error red-errors, x, y, src-width + copy-to red-error, tmp + } + { + var tmp/eax: int <- _ppm-error src-data, x, y, src-width, 0/red, scale-f + add-to red-error, tmp + } + # recompute red channel for current pixel + var red-level: int + { + var tmp/eax: int <- _error-to-ppm-channel red-error + copy-to red-level, tmp + } + # update green-error with current image pixel + var green-error: int + { + var tmp/esi: int <- _read-dithering-error green-errors, x, y, src-width + copy-to green-error, tmp + } + { + var tmp/eax: int <- _ppm-error src-data, x, y, src-width, 1/green, scale-f + add-to green-error, tmp + } + # recompute green channel for current pixel + var green-level: int + { + var tmp/eax: int <- _error-to-ppm-channel green-error + copy-to green-level, tmp + } + # update blue-error with current image pixel + var blue-error: int + { + var tmp/esi: int <- _read-dithering-error blue-errors, x, y, src-width + copy-to blue-error, tmp + } + { + var tmp/eax: int <- _ppm-error src-data, x, y, src-width, 2/blue, scale-f + add-to blue-error, tmp + } + # recompute blue channel for current pixel + var blue-level: int + { + var tmp/eax: int <- _error-to-ppm-channel blue-error + copy-to blue-level, tmp + } + # - figure out the nearest color + var nearest-color-index/eax: int <- nearest-color-euclidean red-level, green-level, blue-level + { + var nearest-color-index-byte/eax: byte <- copy-byte nearest-color-index + _write-raw-buffer dest-data, x, y, src-width, nearest-color-index-byte + } + # - diffuse errors + var red-level: int + var green-level: int + var blue-level: int + { + var tmp-red-level/ecx: int <- copy 0 + var tmp-green-level/edx: int <- copy 0 + var tmp-blue-level/ebx: int <- copy 0 + tmp-red-level, tmp-green-level, tmp-blue-level <- color-rgb nearest-color-index + copy-to red-level, tmp-red-level + copy-to green-level, tmp-green-level + copy-to blue-level, tmp-blue-level + } + # update red-error + var red-level-error/eax: int <- copy red-level + red-level-error <- shift-left 0x10 + subtract-from red-error, red-level-error + _diffuse-dithering-error-floyd-steinberg red-errors, x, y, src-width, src-height, red-error + # update green-error + var green-level-error/eax: int <- copy green-level + green-level-error <- shift-left 0x10 + subtract-from green-error, green-level-error + _diffuse-dithering-error-floyd-steinberg green-errors, x, y, src-width, src-height, green-error + # update blue-error + var blue-level-error/eax: int <- copy blue-level + blue-level-error <- shift-left 0x10 + subtract-from blue-error, blue-level-error + _diffuse-dithering-error-floyd-steinberg blue-errors, x, y, src-width, src-height, blue-error + # + x <- increment + loop + } + y <- increment + loop + } +} + +# convert a single channel for a single image pixel to error space +fn _ppm-error buf: (addr array byte), x: int, y: int, width: int, channel: int, _scale-f: float -> _/eax: int { + # current image pixel + var initial-level/eax: byte <- _read-ppm-buffer buf, x, y, width, 0/red + # scale to 255 levels + var initial-level-int/eax: int <- copy initial-level + var initial-level-f/xmm0: float <- convert initial-level-int + var scale-f/xmm1: float <- copy _scale-f + initial-level-f <- multiply scale-f + initial-level-int <- convert initial-level-f + # switch to fixed-point with 16 bits of precision + initial-level-int <- shift-left 0x10 + return initial-level-int +} + +fn _error-to-ppm-channel error: int -> _/eax: int { + # clamp(error >> 16) + var result/esi: int <- copy error + result <- shift-right-signed 0x10 + { + compare result, 0 + break-if->= + result <- copy 0 + } + { + compare result, 0xff + break-if-<= + result <- copy 0xff + } + return result +} + +# read from a buffer containing alternating bytes from r/g/b channels +fn _read-ppm-buffer _buf: (addr array byte), x: int, y: int, width: int, channel: int -> _/eax: byte { + var buf/esi: (addr array byte) <- copy _buf + var idx/ecx: int <- copy y + idx <- multiply width + idx <- add x + var byte-idx/edx: int <- copy 3 + byte-idx <- multiply idx + byte-idx <- add channel + var result-a/eax: (addr byte) <- index buf, byte-idx + var result/eax: byte <- copy-byte *result-a + return result +} + fn render-raw-image screen: (addr screen), _img: (addr image), xmin: int, ymin: int, width: int, height: int { var img/esi: (addr image) <- copy _img # yratio = height/img->height