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 }