https://github.com/akkartik/mu/blob/main/511image.mu
   1 # Loading images from disk, rendering images to screen.
   2 #
   3 # Currently supports ASCII Netpbm formats.
   4 #   https://en.wikipedia.org/wiki/Netpbm#File_formats
   5 
   6 type image {
   7   type: int  # supported types:
   8              #  1: portable bitmap (P1) - pixels 0 or 1
   9              #  2: portable greymap (P2) - pixels 1-byte greyscale values
  10              #  3: portable pixmap (P3) - pixels 3-byte rgb values
  11   max: int
  12   width: int
  13   height: int
  14   data: (handle array byte)
  15 }
  16 
  17 fn initialize-image _self: (addr image), in: (addr stream byte) {
  18   var self/esi: (addr image) <- copy _self
  19   var mode-storage: slice
  20   var mode/ecx: (addr slice) <- address mode-storage
  21   next-word in, mode
  22   {
  23     var P1?/eax: boolean <- slice-equal? mode, "P1"
  24     compare P1?, 0/false
  25     break-if-=
  26     var type-a/eax: (addr int) <- get self, type
  27     copy-to *type-a, 1/ppm
  28     initialize-image-from-pbm self, in
  29     return
  30   }
  31   {
  32     var P2?/eax: boolean <- slice-equal? mode, "P2"
  33     compare P2?, 0/false
  34     break-if-=
  35     var type-a/eax: (addr int) <- get self, type
  36     copy-to *type-a, 2/pgm
  37     initialize-image-from-pgm self, in
  38     return
  39   }
  40   {
  41     var P3?/eax: boolean <- slice-equal? mode, "P3"
  42     compare P3?, 0/false
  43     break-if-=
  44     var type-a/eax: (addr int) <- get self, type
  45     copy-to *type-a, 3/ppm
  46     initialize-image-from-ppm self, in
  47     return
  48   }
  49   abort "initialize-image: unrecognized image type"
  50 }
  51 
  52 # dispatch to a few variants with mostly identical boilerplate
  53 fn render-image screen: (addr screen), _img: (addr image), xmin: int, ymin: int, width: int, height: int {
  54   var img/esi: (addr image) <- copy _img
  55   var type-a/eax: (addr int) <- get img, type
  56   {
  57     compare *type-a, 1/pbm
  58     break-if-!=
  59     render-pbm-image screen, img, xmin, ymin, width, height
  60     return
  61   }
  62   {
  63     compare *type-a, 2/pgm
  64     break-if-!=
  65     var img2-storage: image
  66     var img2/edi: (addr image) <- address img2-storage
  67     dither-pgm-unordered img, img2
  68     render-raw-image screen, img2, xmin, ymin, width, height
  69     return
  70   }
  71   {
  72     compare *type-a, 3/ppm
  73     break-if-!=
  74     var img2-storage: image
  75     var img2/edi: (addr image) <- address img2-storage
  76     dither-ppm-unordered img, img2
  77     render-raw-image screen, img2, xmin, ymin, width, height
  78     return
  79   }
  80   abort "render-image: unrecognized image type"
  81 }
  82 
  83 ## helpers
  84 
  85 # import a black-and-white ascii bitmap (each pixel is 0 or 1)
  86 fn initialize-image-from-pbm _self: (addr image), in: (addr stream byte) {
  87   var self/esi: (addr image) <- copy _self
  88   var curr-word-storage: slice
  89   var curr-word/ecx: (addr slice) <- address curr-word-storage
  90   # load width, height
  91   next-word in, curr-word
  92   var tmp/eax: int <- parse-decimal-int-from-slice curr-word
  93   var width/edx: int <- copy tmp
  94   next-word in, curr-word
  95   tmp <- parse-decimal-int-from-slice curr-word
  96   var height/ebx: int <- copy tmp
  97   # save width, height
  98   var dest/eax: (addr int) <- get self, width
  99   copy-to *dest, width
 100   dest <- get self, height
 101   copy-to *dest, height
 102   # initialize data
 103   var capacity/edx: int <- copy width
 104   capacity <- multiply height
 105   var data-ah/edi: (addr handle array byte) <- get self, data
 106   populate data-ah, capacity
 107   var _data/eax: (addr array byte) <- lookup *data-ah
 108   var data/edi: (addr array byte) <- copy _data
 109   var i/ebx: int <- copy 0
 110   {
 111     compare i, capacity
 112     break-if->=
 113     next-word in, curr-word
 114     var src/eax: int <- parse-decimal-int-from-slice curr-word
 115     {
 116       var dest/ecx: (addr byte) <- index data, i
 117       copy-byte-to *dest, src
 118     }
 119     i <- increment
 120     loop
 121   }
 122 }
 123 
 124 # render a black-and-white ascii bitmap (each pixel is 0 or 1)
 125 fn render-pbm-image screen: (addr screen), _img: (addr image), xmin: int, ymin: int, width: int, height: int {
 126   var img/esi: (addr image) <- copy _img
 127   # yratio = height/img->height
 128   var img-height-a/eax: (addr int) <- get img, height
 129   var img-height/xmm0: float <- convert *img-height-a
 130   var yratio/xmm1: float <- convert height
 131   yratio <- divide img-height
 132   # xratio = width/img->width
 133   var img-width-a/eax: (addr int) <- get img, width
 134   var img-width/ebx: int <- copy *img-width-a
 135   var img-width-f/xmm0: float <- convert img-width
 136   var xratio/xmm2: float <- convert width
 137   xratio <- divide img-width-f
 138   # esi = img->data
 139   var img-data-ah/eax: (addr handle array byte) <- get img, data
 140   var _img-data/eax: (addr array byte) <- lookup *img-data-ah
 141   var img-data/esi: (addr array byte) <- copy _img-data
 142   var len/edi: int <- length img-data
 143   #
 144   var one/eax: int <- copy 1
 145   var one-f/xmm3: float <- convert one
 146   var width-f/xmm4: float <- convert width
 147   var height-f/xmm5: float <- convert height
 148   var zero/eax: int <- copy 0
 149   var zero-f/xmm0: float <- convert zero
 150   var y/xmm6: float <- copy zero-f
 151   {
 152     compare y, height-f
 153     break-if-float>=
 154     var imgy-f/xmm5: float <- copy y
 155     imgy-f <- divide yratio
 156     var imgy/edx: int <- truncate imgy-f
 157     var x/xmm7: float <- copy zero-f
 158     {
 159       compare x, width-f
 160       break-if-float>=
 161       var imgx-f/xmm5: float <- copy x
 162       imgx-f <- divide xratio
 163       var imgx/ecx: int <- truncate imgx-f
 164       var idx/eax: int <- copy imgy
 165       idx <- multiply img-width
 166       idx <- add imgx
 167       # error info in case we rounded wrong and 'index' will fail bounds-check
 168       compare idx, len
 169       {
 170         break-if-<
 171         set-cursor-position 0/screen, 0x20/x 0x20/y
 172         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, imgx, 3/fg 0/bg
 173         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, imgy, 4/fg 0/bg
 174         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, idx, 5/fg 0/bg
 175       }
 176       var src-a/eax: (addr byte) <- index img-data, idx
 177       var src/eax: byte <- copy-byte *src-a
 178       var color-int/eax: int <- copy src
 179       {
 180         compare color-int, 0/black
 181         break-if-=
 182         color-int <- copy 0xf/white
 183       }
 184       var screenx/ecx: int <- convert x
 185       screenx <- add xmin
 186       var screeny/edx: int <- convert y
 187       screeny <- add ymin
 188       pixel screen, screenx, screeny, color-int
 189       x <- add one-f
 190       loop
 191     }
 192     y <- add one-f
 193     loop
 194   }
 195 }
 196 
 197 # import a greyscale ascii "greymap" (each pixel is a shade of grey from 0 to 255)
 198 fn initialize-image-from-pgm _self: (addr image), in: (addr stream byte) {
 199   var self/esi: (addr image) <- copy _self
 200   var curr-word-storage: slice
 201   var curr-word/ecx: (addr slice) <- address curr-word-storage
 202   # load width, height
 203   next-word in, curr-word
 204   var tmp/eax: int <- parse-decimal-int-from-slice curr-word
 205   var width/edx: int <- copy tmp
 206   next-word in, curr-word
 207   tmp <- parse-decimal-int-from-slice curr-word
 208   var height/ebx: int <- copy tmp
 209   # check and save color levels
 210   next-word in, curr-word
 211   {
 212     tmp <- parse-decimal-int-from-slice curr-word
 213     compare tmp, 0xff
 214     break-if-=
 215     draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "levels of grey is not 255; continuing and hoping for the best", 0x2b/fg 0/bg
 216   }
 217   var dest/edi: (addr int) <- get self, max
 218   copy-to *dest, tmp
 219   # save width, height
 220   dest <- get self, width
 221   copy-to *dest, width
 222   dest <- get self, height
 223   copy-to *dest, height
 224   # initialize data
 225   var capacity/edx: int <- copy width
 226   capacity <- multiply height
 227   var data-ah/edi: (addr handle array byte) <- get self, data
 228   populate data-ah, capacity
 229   var _data/eax: (addr array byte) <- lookup *data-ah
 230   var data/edi: (addr array byte) <- copy _data
 231   var i/ebx: int <- copy 0
 232   {
 233     compare i, capacity
 234     break-if->=
 235     next-word in, curr-word
 236     var src/eax: int <- parse-decimal-int-from-slice curr-word
 237     {
 238       var dest/ecx: (addr byte) <- index data, i
 239       copy-byte-to *dest, src
 240     }
 241     i <- increment
 242     loop
 243   }
 244 }
 245 
 246 # render a greyscale ascii "greymap" (each pixel is a shade of grey from 0 to 255) by quantizing the shades
 247 fn render-pgm-image screen: (addr screen), _img: (addr image), xmin: int, ymin: int, width: int, height: int {
 248   var img/esi: (addr image) <- copy _img
 249   # yratio = height/img->height
 250   var img-height-a/eax: (addr int) <- get img, height
 251   var img-height/xmm0: float <- convert *img-height-a
 252   var yratio/xmm1: float <- convert height
 253   yratio <- divide img-height
 254   # xratio = width/img->width
 255   var img-width-a/eax: (addr int) <- get img, width
 256   var img-width/ebx: int <- copy *img-width-a
 257   var img-width-f/xmm0: float <- convert img-width
 258   var xratio/xmm2: float <- convert width
 259   xratio <- divide img-width-f
 260   # esi = img->data
 261   var img-data-ah/eax: (addr handle array byte) <- get img, data
 262   var _img-data/eax: (addr array byte) <- lookup *img-data-ah
 263   var img-data/esi: (addr array byte) <- copy _img-data
 264   var len/edi: int <- length img-data
 265   #
 266   var one/eax: int <- copy 1
 267   var one-f/xmm3: float <- convert one
 268   var width-f/xmm4: float <- convert width
 269   var height-f/xmm5: float <- convert height
 270   var zero/eax: int <- copy 0
 271   var zero-f/xmm0: float <- convert zero
 272   var y/xmm6: float <- copy zero-f
 273   {
 274     compare y, height-f
 275     break-if-float>=
 276     var imgy-f/xmm5: float <- copy y
 277     imgy-f <- divide yratio
 278     var imgy/edx: int <- truncate imgy-f
 279     var x/xmm7: float <- copy zero-f
 280     {
 281       compare x, width-f
 282       break-if-float>=
 283       var imgx-f/xmm5: float <- copy x
 284       imgx-f <- divide xratio
 285       var imgx/ecx: int <- truncate imgx-f
 286       var idx/eax: int <- copy imgy
 287       idx <- multiply img-width
 288       idx <- add imgx
 289       # error info in case we rounded wrong and 'index' will fail bounds-check
 290       compare idx, len
 291       {
 292         break-if-<
 293         set-cursor-position 0/screen, 0x20/x 0x20/y
 294         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, imgx, 3/fg 0/bg
 295         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, imgy, 4/fg 0/bg
 296         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, idx, 5/fg 0/bg
 297       }
 298       var src-a/eax: (addr byte) <- index img-data, idx
 299       var src/eax: byte <- copy-byte *src-a
 300       var color-int/eax: int <- nearest-grey src
 301       var screenx/ecx: int <- convert x
 302       screenx <- add xmin
 303       var screeny/edx: int <- convert y
 304       screeny <- add ymin
 305       pixel screen, screenx, screeny, color-int
 306       x <- add one-f
 307       loop
 308     }
 309     y <- add one-f
 310     loop
 311   }
 312 }
 313 
 314 fn nearest-grey level-255: byte -> _/eax: int {
 315   var result/eax: int <- copy level-255
 316   result <- shift-right 4
 317   result <- add 0x10
 318   return result
 319 }
 320 
 321 fn dither-pgm-unordered-monochrome _src: (addr image), _dest: (addr image) {
 322   var src/esi: (addr image) <- copy _src
 323   var dest/edi: (addr image) <- copy _dest
 324   # copy 'width'
 325   var src-width-a/eax: (addr int) <- get src, width
 326   var tmp/eax: int <- copy *src-width-a
 327   var src-width: int
 328   copy-to src-width, tmp
 329   {
 330     var dest-width-a/edx: (addr int) <- get dest, width
 331     copy-to *dest-width-a, tmp
 332   }
 333   # copy 'height'
 334   var src-height-a/eax: (addr int) <- get src, height
 335   var tmp/eax: int <- copy *src-height-a
 336   var src-height: int
 337   copy-to src-height, tmp
 338   {
 339     var dest-height-a/ecx: (addr int) <- get dest, height
 340     copy-to *dest-height-a, tmp
 341   }
 342   # transform 'data'
 343   var capacity/ebx: int <- copy src-width
 344   capacity <- multiply src-height
 345   var dest/edi: (addr image) <- copy _dest
 346   var dest-data-ah/eax: (addr handle array byte) <- get dest, data
 347   populate dest-data-ah, capacity
 348   var _dest-data/eax: (addr array byte) <- lookup *dest-data-ah
 349   var dest-data/edi: (addr array byte) <- copy _dest-data
 350   # needs a buffer to temporarily hold more than 256 levels of precision
 351   var errors-storage: (array int 0xc0000)
 352   var errors/ebx: (addr array int) <- address errors-storage
 353   var src-data-ah/eax: (addr handle array byte) <- get src, data
 354   var _src-data/eax: (addr array byte) <- lookup *src-data-ah
 355   var src-data/esi: (addr array byte) <- copy _src-data
 356   var y/edx: int <- copy 0
 357   {
 358     compare y, src-height
 359     break-if->=
 360     var x/ecx: int <- copy 0
 361     {
 362       compare x, src-width
 363       break-if->=
 364       var curr/eax: byte <- _read-pgm-buffer src-data, x, y, src-width
 365       var curr-int/eax: int <- copy curr
 366       curr-int <- shift-left 0x10  # we have 32 bits; we'll use 16 bits for the fraction and leave 8 for unanticipated overflow
 367       var error/esi: int <- _read-dithering-error errors, x, y, src-width
 368       error <- add curr-int
 369       $_dither-pgm-unordered-monochrome:update-error: {
 370         compare error, 0x800000
 371         {
 372           break-if->=
 373           _write-raw-buffer dest-data, x, y, src-width, 0/black
 374           break $_dither-pgm-unordered-monochrome:update-error
 375         }
 376         _write-raw-buffer dest-data, x, y, src-width, 1/white
 377         error <- subtract 0xff0000
 378       }
 379       _diffuse-dithering-error-floyd-steinberg errors, x, y, src-width, src-height, error
 380       x <- increment
 381       loop
 382     }
 383     move-cursor-to-left-margin-of-next-line 0/screen
 384     y <- increment
 385     loop
 386   }
 387 }
 388 
 389 fn dither-pgm-unordered _src: (addr image), _dest: (addr image) {
 390   var src/esi: (addr image) <- copy _src
 391   var dest/edi: (addr image) <- copy _dest
 392   # copy 'width'
 393   var src-width-a/eax: (addr int) <- get src, width
 394   var tmp/eax: int <- copy *src-width-a
 395   var src-width: int
 396   copy-to src-width, tmp
 397   {
 398     var dest-width-a/edx: (addr int) <- get dest, width
 399     copy-to *dest-width-a, tmp
 400   }
 401   # copy 'height'
 402   var src-height-a/eax: (addr int) <- get src, height
 403   var tmp/eax: int <- copy *src-height-a
 404   var src-height: int
 405   copy-to src-height, tmp
 406   {
 407     var dest-height-a/ecx: (addr int) <- get dest, height
 408     copy-to *dest-height-a, tmp
 409   }
 410   # compute scaling factor 255/max
 411   var target-scale/eax: int <- copy 0xff
 412   var scale-f/xmm7: float <- convert target-scale
 413   var src-max-a/eax: (addr int) <- get src, max
 414   var tmp-f/xmm0: float <- convert *src-max-a
 415   scale-f <- divide tmp-f
 416   # transform 'data'
 417   var capacity/ebx: int <- copy src-width
 418   capacity <- multiply src-height
 419   var dest/edi: (addr image) <- copy _dest
 420   var dest-data-ah/eax: (addr handle array byte) <- get dest, data
 421   populate dest-data-ah, capacity
 422   var _dest-data/eax: (addr array byte) <- lookup *dest-data-ah
 423   var dest-data/edi: (addr array byte) <- copy _dest-data
 424   # needs a buffer to temporarily hold more than 256 levels of precision
 425   var errors-storage: (array int 0xc0000)
 426   var errors/ebx: (addr array int) <- address errors-storage
 427   var src-data-ah/eax: (addr handle array byte) <- get src, data
 428   var _src-data/eax: (addr array byte) <- lookup *src-data-ah
 429   var src-data/esi: (addr array byte) <- copy _src-data
 430   var y/edx: int <- copy 0
 431   {
 432     compare y, src-height
 433     break-if->=
 434     var x/ecx: int <- copy 0
 435     {
 436       compare x, src-width
 437       break-if->=
 438       var initial-color/eax: byte <- _read-pgm-buffer src-data, x, y, src-width
 439       # . scale to 255 levels
 440       var initial-color-int/eax: int <- copy initial-color
 441       var initial-color-f/xmm0: float <- convert initial-color-int
 442       initial-color-f <- multiply scale-f
 443       initial-color-int <- convert initial-color-f
 444       var error/esi: int <- _read-dithering-error errors, x, y, src-width
 445       # error += (initial-color << 16)
 446       {
 447         var tmp/eax: int <- copy initial-color-int
 448         tmp <- shift-left 0x10  # we have 32 bits; we'll use 16 bits for the fraction and leave 8 for unanticipated overflow
 449         error <- add tmp
 450       }
 451       # nearest-color = nearest(error >> 16)
 452       var nearest-color/eax: int <- copy error
 453       nearest-color <- shift-right-signed 0x10
 454       {
 455         compare nearest-color, 0
 456         break-if->=
 457         nearest-color <- copy 0
 458       }
 459       {
 460         compare nearest-color, 0xf0
 461         break-if-<=
 462         nearest-color <- copy 0xf0
 463       }
 464       # . truncate last 4 bits
 465       nearest-color <- and 0xf0
 466       # error -= (nearest-color << 16)
 467       {
 468         var tmp/eax: int <- copy nearest-color
 469         tmp <- shift-left 0x10
 470         error <- subtract tmp
 471       }
 472       # color-index = (nearest-color >> 4 + 16)
 473       var color-index/eax: int <- copy nearest-color
 474       color-index <- shift-right 4
 475       color-index <- add 0x10
 476       var color-index-byte/eax: byte <- copy-byte color-index
 477       _write-raw-buffer dest-data, x, y, src-width, color-index-byte
 478       _diffuse-dithering-error-floyd-steinberg errors, x, y, src-width, src-height, error
 479       x <- increment
 480       loop
 481     }
 482     y <- increment
 483     loop
 484   }
 485 }
 486 
 487 # Use Floyd-Steinberg algorithm for diffusing error at x, y in a 2D grid of
 488 # dimensions (width, height)
 489 #
 490 # https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html
 491 #
 492 # Error is currently a fixed-point number with 16-bit fraction. But
 493 # interestingly this function doesn't care about that.
 494 fn _diffuse-dithering-error-floyd-steinberg errors: (addr array int), x: int, y: int, width: int, height: int, error: int {
 495   {
 496     compare error, 0
 497     break-if-!=
 498     return
 499   }
 500   var width-1/esi: int <- copy width
 501   width-1 <- decrement
 502   var height-1/edi: int <- copy height
 503   height-1 <- decrement
 504   # delta = error/16
 505 #?   show-errors errors, width, height, x, y
 506   var delta/ecx: int <- copy error
 507   delta <- shift-right-signed 4
 508   # In Floyd-Steinberg, each pixel X transmits its errors to surrounding
 509   # pixels in the following proportion:
 510   #           X     7/16
 511   #     3/16  5/16  1/16
 512   var x/edx: int <- copy x
 513   {
 514     compare x, width-1
 515     break-if->=
 516     var tmp/eax: int <- copy 7
 517     tmp <- multiply delta
 518     var xright/edx: int <- copy x
 519     xright <- increment
 520     _accumulate-dithering-error errors, xright, y, width, tmp
 521   }
 522   var y/ebx: int <- copy y
 523   {
 524     compare y, height-1
 525     break-if-<
 526     return
 527   }
 528   var ybelow: int
 529   copy-to ybelow, y
 530   increment ybelow
 531   {
 532     compare x, 0
 533     break-if-<=
 534     var tmp/eax: int <- copy 3
 535     tmp <- multiply delta
 536     var xleft/edx: int <- copy x
 537     xleft <- decrement
 538     _accumulate-dithering-error errors, xleft, ybelow, width, tmp
 539   }
 540   {
 541     var tmp/eax: int <- copy 5
 542     tmp <- multiply delta
 543     _accumulate-dithering-error errors, x, ybelow, width, tmp
 544   }
 545   {
 546     compare x, width-1
 547     break-if->=
 548     var xright/edx: int <- copy x
 549     xright <- increment
 550     _accumulate-dithering-error errors, xright, ybelow, width, delta
 551   }
 552 #?   show-errors errors, width, height, x, y
 553 }
 554 
 555 fn _accumulate-dithering-error errors: (addr array int), x: int, y: int, width: int, error: int {
 556   var curr/esi: int <- _read-dithering-error errors, x, y, width
 557   curr <- add error
 558   _write-dithering-error errors, x, y, width, curr
 559 }
 560 
 561 fn _read-dithering-error _errors: (addr array int), x: int, y: int, width: int -> _/esi: int {
 562   var errors/esi: (addr array int) <- copy _errors
 563   var idx/ecx: int <- copy y
 564   idx <- multiply width
 565   idx <- add x
 566   var result-a/eax: (addr int) <- index errors, idx
 567   return *result-a
 568 }
 569 
 570 fn _write-dithering-error _errors: (addr array int), x: int, y: int, width: int, val: int {
 571   var errors/esi: (addr array int) <- copy _errors
 572   var idx/ecx: int <- copy y
 573   idx <- multiply width
 574   idx <- add x
 575 #?   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, idx, 7/fg 0/bg
 576 #?   move-cursor-to-left-margin-of-next-line 0/screen
 577   var src/eax: int <- copy val
 578   var dest-a/edi: (addr int) <- index errors, idx
 579   copy-to *dest-a, src
 580 }
 581 
 582 fn _read-pgm-buffer _buf: (addr array byte), x: int, y: int, width: int -> _/eax: byte {
 583   var buf/esi: (addr array byte) <- copy _buf
 584   var idx/ecx: int <- copy y
 585   idx <- multiply width
 586   idx <- add x
 587   var result-a/eax: (addr byte) <- index buf, idx
 588   var result/eax: byte <- copy-byte *result-a
 589   return result
 590 }
 591 
 592 fn _write-raw-buffer _buf: (addr array byte), x: int, y: int, width: int, val: byte {
 593   var buf/esi: (addr array byte) <- copy _buf
 594   var idx/ecx: int <- copy y
 595   idx <- multiply width
 596   idx <- add x
 597   var src/eax: byte <- copy val
 598   var dest-a/edi: (addr byte) <- index buf, idx
 599   copy-byte-to *dest-a, src
 600 }
 601 
 602 # some debugging helpers
 603 fn show-errors errors: (addr array int), width: int, height: int, x: int, y: int {
 604   compare y, 1
 605   {
 606     break-if-=
 607     return
 608   }
 609   compare x, 0
 610   {
 611     break-if-=
 612     return
 613   }
 614   var y/edx: int <- copy 0
 615   {
 616     compare y, height
 617     break-if->=
 618     var x/ecx: int <- copy 0
 619     {
 620       compare x, width
 621       break-if->=
 622       var error/esi: int <- _read-dithering-error errors, x, y, width
 623       psd "e", error, 5/fg, x, y
 624       x <- increment
 625       loop
 626     }
 627     move-cursor-to-left-margin-of-next-line 0/screen
 628     y <- increment
 629     loop
 630   }
 631 }
 632 
 633 fn psd s: (addr array byte), d: int, fg: int, x: int, y: int {
 634   {
 635     compare y, 0x18
 636     break-if->=
 637     return
 638   }
 639   {
 640     compare y, 0x1c
 641     break-if-<=
 642     return
 643   }
 644   {
 645     compare x, 0x40
 646     break-if->=
 647     return
 648   }
 649 #?   {
 650 #?     compare x, 0x48
 651 #?     break-if-<=
 652 #?     return
 653 #?   }
 654   draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, s, 7/fg 0/bg
 655   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, d, fg 0/bg
 656 }
 657 
 658 fn psx s: (addr array byte), d: int, fg: int, x: int, y: int {
 659 #?   {
 660 #?     compare y, 0x60
 661 #?     break-if->=
 662 #?     return
 663 #?   }
 664 #?   {
 665 #?     compare y, 0x6c
 666 #?     break-if-<=
 667 #?     return
 668 #?   }
 669   {
 670     compare x, 0x20
 671     break-if->=
 672     return
 673   }
 674 #?   {
 675 #?     compare x, 0x6c
 676 #?     break-if-<=
 677 #?     return
 678 #?   }
 679   draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, s, 7/fg 0/bg
 680   draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, d, fg 0/bg
 681 }
 682 
 683 # import a color ascii "pixmap" (each pixel consists of 3 shades of r/g/b from 0 to 255)
 684 fn initialize-image-from-ppm _self: (addr image), in: (addr stream byte) {
 685   var self/esi: (addr image) <- copy _self
 686   var curr-word-storage: slice
 687   var curr-word/ecx: (addr slice) <- address curr-word-storage
 688   # load width, height
 689   next-word in, curr-word
 690   var tmp/eax: int <- parse-decimal-int-from-slice curr-word
 691   var width/edx: int <- copy tmp
 692   next-word in, curr-word
 693   tmp <- parse-decimal-int-from-slice curr-word
 694   var height/ebx: int <- copy tmp
 695   next-word in, curr-word
 696   # check color levels
 697   {
 698     tmp <- parse-decimal-int-from-slice curr-word
 699     compare tmp, 0xff
 700     break-if-=
 701     abort "initialize-image-from-ppm: supports exactly 255 levels per rgb channel"
 702   }
 703   var dest/edi: (addr int) <- get self, max
 704   copy-to *dest, tmp
 705   # save width, height
 706   dest <- get self, width
 707   copy-to *dest, width
 708   dest <- get self, height
 709   copy-to *dest, height
 710   # initialize data
 711   var capacity/edx: int <- copy width
 712   capacity <- multiply height
 713   # . multiply by 3 for the r/g/b channels
 714   var tmp/eax: int <- copy capacity
 715   tmp <- shift-left 1
 716   capacity <- add tmp
 717   #
 718   var data-ah/edi: (addr handle array byte) <- get self, data
 719   populate data-ah, capacity
 720   var _data/eax: (addr array byte) <- lookup *data-ah
 721   var data/edi: (addr array byte) <- copy _data
 722   var i/ebx: int <- copy 0
 723   {
 724     compare i, capacity
 725     break-if->=
 726     next-word in, curr-word
 727     var src/eax: int <- parse-decimal-int-from-slice curr-word
 728     {
 729       var dest/ecx: (addr byte) <- index data, i
 730       copy-byte-to *dest, src
 731     }
 732     i <- increment
 733     loop
 734   }
 735 }
 736 
 737 # import a color ascii "pixmap" (each pixel consists of 3 shades of r/g/b from 0 to 255)
 738 fn render-ppm-image screen: (addr screen), _img: (addr image), xmin: int, ymin: int, width: int, height: int {
 739   var img/esi: (addr image) <- copy _img
 740   # yratio = height/img->height
 741   var img-height-a/eax: (addr int) <- get img, height
 742   var img-height/xmm0: float <- convert *img-height-a
 743   var yratio/xmm1: float <- convert height
 744   yratio <- divide img-height
 745   # xratio = width/img->width
 746   var img-width-a/eax: (addr int) <- get img, width
 747   var img-width/ebx: int <- copy *img-width-a
 748   var img-width-f/xmm0: float <- convert img-width
 749   var xratio/xmm2: float <- convert width
 750   xratio <- divide img-width-f
 751   # esi = img->data
 752   var img-data-ah/eax: (addr handle array byte) <- get img, data
 753   var _img-data/eax: (addr array byte) <- lookup *img-data-ah
 754   var img-data/esi: (addr array byte) <- copy _img-data
 755   var len/edi: int <- length img-data
 756   #
 757   var one/eax: int <- copy 1
 758   var one-f/xmm3: float <- convert one
 759   var width-f/xmm4: float <- convert width
 760   var height-f/xmm5: float <- convert height
 761   var zero/eax: int <- copy 0
 762   var zero-f/xmm0: float <- convert zero
 763   var y/xmm6: float <- copy zero-f
 764   {
 765     compare y, height-f
 766     break-if-float>=
 767     var imgy-f/xmm5: float <- copy y
 768     imgy-f <- divide yratio
 769     var imgy/edx: int <- truncate imgy-f
 770     var x/xmm7: float <- copy zero-f
 771     {
 772       compare x, width-f
 773       break-if-float>=
 774       var imgx-f/xmm5: float <- copy x
 775       imgx-f <- divide xratio
 776       var imgx/ecx: int <- truncate imgx-f
 777       var idx/eax: int <- copy imgy
 778       idx <- multiply img-width
 779       idx <- add imgx
 780       # . multiply by 3 for the r/g/b channels
 781       {
 782         var tmp/ecx: int <- copy idx
 783         tmp <- shift-left 1
 784         idx <- add tmp
 785       }
 786       # error info in case we rounded wrong and 'index' will fail bounds-check
 787       compare idx, len
 788       {
 789         break-if-<
 790         set-cursor-position 0/screen, 0x20/x 0x20/y
 791         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, imgx, 3/fg 0/bg
 792         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, imgy, 4/fg 0/bg
 793         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, idx, 5/fg 0/bg
 794       }
 795       # r channel
 796       var r: int
 797       {
 798         var src-a/eax: (addr byte) <- index img-data, idx
 799         var src/eax: byte <- copy-byte *src-a
 800         copy-to r, src
 801       }
 802       idx <- increment
 803       # g channel
 804       var g: int
 805       {
 806         var src-a/eax: (addr byte) <- index img-data, idx
 807         var src/eax: byte <- copy-byte *src-a
 808         copy-to g, src
 809       }
 810       idx <- increment
 811       # b channel
 812       var b: int
 813       {
 814         var src-a/eax: (addr byte) <- index img-data, idx
 815         var src/eax: byte <- copy-byte *src-a
 816         copy-to b, src
 817       }
 818       idx <- increment
 819       # plot nearest color
 820       var color/eax: int <- nearest-color-euclidean r, g, b
 821       var screenx/ecx: int <- convert x
 822       screenx <- add xmin
 823       var screeny/edx: int <- convert y
 824       screeny <- add ymin
 825       pixel screen, screenx, screeny, color
 826       x <- add one-f
 827       loop
 828     }
 829     y <- add one-f
 830     loop
 831   }
 832 }
 833 
 834 fn dither-ppm-unordered _src: (addr image), _dest: (addr image) {
 835   var src/esi: (addr image) <- copy _src
 836   var dest/edi: (addr image) <- copy _dest
 837   # copy 'width'
 838   var src-width-a/eax: (addr int) <- get src, width
 839   var tmp/eax: int <- copy *src-width-a
 840   var src-width: int
 841   copy-to src-width, tmp
 842   {
 843     var dest-width-a/edx: (addr int) <- get dest, width
 844     copy-to *dest-width-a, tmp
 845   }
 846   # copy 'height'
 847   var src-height-a/eax: (addr int) <- get src, height
 848   var tmp/eax: int <- copy *src-height-a
 849   var src-height: int
 850   copy-to src-height, tmp
 851   {
 852     var dest-height-a/ecx: (addr int) <- get dest, height
 853     copy-to *dest-height-a, tmp
 854   }
 855   # compute scaling factor 255/max
 856   var target-scale/eax: int <- copy 0xff
 857   var scale-f/xmm7: float <- convert target-scale
 858   var src-max-a/eax: (addr int) <- get src, max
 859   var tmp-f/xmm0: float <- convert *src-max-a
 860   scale-f <- divide tmp-f
 861   # allocate 'data'
 862   var capacity/ebx: int <- copy src-width
 863   capacity <- multiply src-height
 864   var dest/edi: (addr image) <- copy _dest
 865   var dest-data-ah/eax: (addr handle array byte) <- get dest, data
 866   populate dest-data-ah, capacity
 867   var _dest-data/eax: (addr array byte) <- lookup *dest-data-ah
 868   var dest-data/edi: (addr array byte) <- copy _dest-data
 869   # error buffers per r/g/b channel
 870   var red-errors-storage: (array int 0xc0000)
 871   var tmp/eax: (addr array int) <- address red-errors-storage
 872   var red-errors: (addr array int)
 873   copy-to red-errors, tmp
 874   var green-errors-storage: (array int 0xc0000)
 875   var tmp/eax: (addr array int) <- address green-errors-storage
 876   var green-errors: (addr array int)
 877   copy-to green-errors, tmp
 878   var blue-errors-storage: (array int 0xc0000)
 879   var tmp/eax: (addr array int) <- address blue-errors-storage
 880   var blue-errors: (addr array int)
 881   copy-to blue-errors, tmp
 882   # transform 'data'
 883   var src-data-ah/eax: (addr handle array byte) <- get src, data
 884   var _src-data/eax: (addr array byte) <- lookup *src-data-ah
 885   var src-data/esi: (addr array byte) <- copy _src-data
 886   var y/edx: int <- copy 0
 887   {
 888     compare y, src-height
 889     break-if->=
 890     var x/ecx: int <- copy 0
 891     {
 892       compare x, src-width
 893       break-if->=
 894       # - update errors and compute color levels for current pixel in each channel
 895       # update red-error with current image pixel
 896       var red-error: int
 897       {
 898         var tmp/esi: int <- _read-dithering-error red-errors, x, y, src-width
 899         copy-to red-error, tmp
 900       }
 901       {
 902         var tmp/eax: int <- _ppm-error src-data, x, y, src-width, 0/red, scale-f
 903         add-to red-error, tmp
 904       }
 905       # recompute red channel for current pixel
 906       var red-level: int
 907       {
 908         var tmp/eax: int <- _error-to-ppm-channel red-error
 909         copy-to red-level, tmp
 910       }
 911       # update green-error with current image pixel
 912       var green-error: int
 913       {
 914         var tmp/esi: int <- _read-dithering-error green-errors, x, y, src-width
 915         copy-to green-error, tmp
 916       }
 917       {
 918         var tmp/eax: int <- _ppm-error src-data, x, y, src-width, 1/green, scale-f
 919         add-to green-error, tmp
 920       }
 921       # recompute green channel for current pixel
 922       var green-level: int
 923       {
 924         var tmp/eax: int <- _error-to-ppm-channel green-error
 925         copy-to green-level, tmp
 926       }
 927       # update blue-error with current image pixel
 928       var blue-error: int
 929       {
 930         var tmp/esi: int <- _read-dithering-error blue-errors, x, y, src-width
 931         copy-to blue-error, tmp
 932       }
 933       {
 934         var tmp/eax: int <- _ppm-error src-data, x, y, src-width, 2/blue, scale-f
 935         add-to blue-error, tmp
 936       }
 937       # recompute blue channel for current pixel
 938       var blue-level: int
 939       {
 940         var tmp/eax: int <- _error-to-ppm-channel blue-error
 941         copy-to blue-level, tmp
 942       }
 943       # - figure out the nearest color
 944       var nearest-color-index/eax: int <- nearest-color-euclidean red-level, green-level, blue-level
 945       {
 946         var nearest-color-index-byte/eax: byte <- copy-byte nearest-color-index
 947         _write-raw-buffer dest-data, x, y, src-width, nearest-color-index-byte
 948       }
 949       # - diffuse errors
 950       var red-level: int
 951       var green-level: int
 952       var blue-level: int
 953       {
 954         var tmp-red-level/ecx: int <- copy 0
 955         var tmp-green-level/edx: int <- copy 0
 956         var tmp-blue-level/ebx: int <- copy 0
 957         tmp-red-level, tmp-green-level, tmp-blue-level <- color-rgb nearest-color-index
 958         copy-to red-level, tmp-red-level
 959         copy-to green-level, tmp-green-level
 960         copy-to blue-level, tmp-blue-level
 961       }
 962       # update red-error
 963       var red-level-error/eax: int <- copy red-level
 964       red-level-error <- shift-left 0x10
 965       subtract-from red-error, red-level-error
 966       _diffuse-dithering-error-floyd-steinberg red-errors, x, y, src-width, src-height, red-error
 967       # update green-error
 968       var green-level-error/eax: int <- copy green-level
 969       green-level-error <- shift-left 0x10
 970       subtract-from green-error, green-level-error
 971       _diffuse-dithering-error-floyd-steinberg green-errors, x, y, src-width, src-height, green-error
 972       # update blue-error
 973       var blue-level-error/eax: int <- copy blue-level
 974       blue-level-error <- shift-left 0x10
 975       subtract-from blue-error, blue-level-error
 976       _diffuse-dithering-error-floyd-steinberg blue-errors, x, y, src-width, src-height, blue-error
 977       #
 978       x <- increment
 979       loop
 980     }
 981     y <- increment
 982     loop
 983   }
 984 }
 985 
 986 # convert a single channel for a single image pixel to error space
 987 fn _ppm-error buf: (addr array byte), x: int, y: int, width: int, channel: int, _scale-f: float -> _/eax: int {
 988   # current image pixel
 989   var initial-level/eax: byte <- _read-ppm-buffer buf, x, y, width, channel
 990   # scale to 255 levels
 991   var initial-level-int/eax: int <- copy initial-level
 992   var initial-level-f/xmm0: float <- convert initial-level-int
 993   var scale-f/xmm1: float <- copy _scale-f
 994   initial-level-f <- multiply scale-f
 995   initial-level-int <- convert initial-level-f
 996   # switch to fixed-point with 16 bits of precision
 997   initial-level-int <- shift-left 0x10
 998   return initial-level-int
 999 }
1000 
1001 fn _error-to-ppm-channel error: int -> _/eax: int {
1002   # clamp(error >> 16)
1003   var result/esi: int <- copy error
1004   result <- shift-right-signed 0x10
1005   {
1006     compare result, 0
1007     break-if->=
1008     result <- copy 0
1009   }
1010   {
1011     compare result, 0xff
1012     break-if-<=
1013     result <- copy 0xff
1014   }
1015   return result
1016 }
1017 
1018 # read from a buffer containing alternating bytes from r/g/b channels
1019 fn _read-ppm-buffer _buf: (addr array byte), x: int, y: int, width: int, channel: int -> _/eax: byte {
1020   var buf/esi: (addr array byte) <- copy _buf
1021   var idx/ecx: int <- copy y
1022   idx <- multiply width
1023   idx <- add x
1024   var byte-idx/edx: int <- copy 3
1025   byte-idx <- multiply idx
1026   byte-idx <- add channel
1027   var result-a/eax: (addr byte) <- index buf, byte-idx
1028   var result/eax: byte <- copy-byte *result-a
1029   return result
1030 }
1031 
1032 # each byte in the image data is a color of the current palette
1033 fn render-raw-image screen: (addr screen), _img: (addr image), xmin: int, ymin: int, width: int, height: int {
1034   var img/esi: (addr image) <- copy _img
1035   # yratio = height/img->height
1036   var img-height-a/eax: (addr int) <- get img, height
1037   var img-height/xmm0: float <- convert *img-height-a
1038   var yratio/xmm1: float <- convert height
1039   yratio <- divide img-height
1040   # xratio = width/img->width
1041   var img-width-a/eax: (addr int) <- get img, width
1042   var img-width/ebx: int <- copy *img-width-a
1043   var img-width-f/xmm0: float <- convert img-width
1044   var xratio/xmm2: float <- convert width
1045   xratio <- divide img-width-f
1046   # esi = img->data
1047   var img-data-ah/eax: (addr handle array byte) <- get img, data
1048   var _img-data/eax: (addr array byte) <- lookup *img-data-ah
1049   var img-data/esi: (addr array byte) <- copy _img-data
1050   var len/edi: int <- length img-data
1051   #
1052   var one/eax: int <- copy 1
1053   var one-f/xmm3: float <- convert one
1054   var width-f/xmm4: float <- convert width
1055   var height-f/xmm5: float <- convert height
1056   var zero/eax: int <- copy 0
1057   var zero-f/xmm0: float <- convert zero
1058   var y/xmm6: float <- copy zero-f
1059   {
1060     compare y, height-f
1061     break-if-float>=
1062     var imgy-f/xmm5: float <- copy y
1063     imgy-f <- divide yratio
1064     var imgy/edx: int <- truncate imgy-f
1065     var x/xmm7: float <- copy zero-f
1066     {
1067       compare x, width-f
1068       break-if-float>=
1069       var imgx-f/xmm5: float <- copy x
1070       imgx-f <- divide xratio
1071       var imgx/ecx: int <- truncate imgx-f
1072       var idx/eax: int <- copy imgy
1073       idx <- multiply img-width
1074       idx <- add imgx
1075       # error info in case we rounded wrong and 'index' will fail bounds-check
1076       compare idx, len
1077       {
1078         break-if-<
1079         set-cursor-position 0/screen, 0x20/x 0x20/y
1080         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, imgx, 3/fg 0/bg
1081         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, imgy, 4/fg 0/bg
1082         draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, idx, 5/fg 0/bg
1083       }
1084       var color-a/eax: (addr byte) <- index img-data, idx
1085       var color/eax: byte <- copy-byte *color-a
1086       var color-int/eax: int <- copy color
1087       var screenx/ecx: int <- convert x
1088       screenx <- add xmin
1089       var screeny/edx: int <- convert y
1090       screeny <- add ymin
1091       pixel screen, screenx, screeny, color-int
1092       x <- add one-f
1093       loop
1094     }
1095     y <- add one-f
1096     loop
1097   }
1098 }