dithering greyscale using black/white

Starting to look right.
This commit is contained in:
Kartik K. Agaram 2021-07-10 10:51:39 -07:00
parent 2a0f6cbb6f
commit 5f8a8c11a2
5 changed files with 293 additions and 24 deletions

7
7f.pgm Normal file
View File

@ -0,0 +1,7 @@
P2
4 4
255
127 127 127 127
127 127 127 127
127 127 127 127
127 127 127 127

7
80.pgm Normal file
View File

@ -0,0 +1,7 @@
P2
4 4
255
128 128 128 128
128 128 128 128
128 128 128 128
128 128 128 128

7
c0.pgm Normal file
View File

@ -0,0 +1,7 @@
P2
4 4
255
192 192 192 192
192 192 192 192
192 192 192 192
192 192 192 192

289
img.mu
View File

@ -27,9 +27,18 @@ fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk)
var img-storage: image
var img/esi: (addr image) <- address img-storage
load-image img, data-disk
render-image screen, img, 0x20/x 0x80/y, 0x100/width, 0x100/height
render-image screen, img, 0x120/x 0x180/y, 0x12c/width=300, 0xc8/height=200
render-image screen, img, 0x320/x 0x280/y, 0x60/width=96, 0x1c/height=28
#? render-image screen, img, 0x20/x, 0x80/y, 0x100/width, 0x100/height
#? render-image screen, img, 0x120/x, 0x180/y, 0x12c/width=300, 0xc8/height=200
#? render-image screen, img, 0x320/x, 0x280/y, 0x60/width=96, 0x1c/height=28
#? render-pgm-image screen, img, 0x1c0/x, 0x100/y, 0x12c/width=300, 0xc8/height=200
#? draw-box-on-real-screen 0x1bf/x, 0x102/y, 0x1c4/x, 0x104/y, 4/fg
#? render-image screen, img, 0x80/x, 0x100/y, 0x12c/width=300, 0xc8/height=200
set-cursor-position 0/screen, 0/x 2/y
render-pgm-image screen, img, 0x200/x, 0x100/y, 0xc0/width, 0xc0/height
render-image screen, img, 0x100/x, 0x100/y, 0xc0/width, 0xc0/height
}
fn load-image self: (addr image), data-disk: (addr disk) {
@ -97,7 +106,10 @@ fn render-image screen: (addr screen), _img: (addr image), xmin: int, ymin: int,
{
compare *type-a, 2/pgm
break-if-!=
render-pgm-image screen, img, xmin, ymin, width, height
var img2-storage: image
var img2/edi: (addr image) <- address img2-storage
dither-pgm-with-monochrome img, img2
render-pbm-image screen, img2, xmin, ymin, width, height
return
}
{
@ -177,7 +189,6 @@ fn render-pbm-image screen: (addr screen), _img: (addr image), xmin: int, ymin:
var zero/eax: int <- copy 0
var zero-f/xmm0: float <- convert zero
var y/xmm6: float <- copy zero-f
set-cursor-position 0/screen, 0x20/x 0x20/y
{
compare y, height-f
break-if-float>=
@ -192,12 +203,7 @@ fn render-pbm-image screen: (addr screen), _img: (addr image), xmin: int, ymin:
imgx-f <- divide xratio
var imgx/ecx: int <- truncate imgx-f
var idx/eax: int <- copy imgy
{
compare idx, 0
break-if-<=
idx <- decrement
idx <- multiply img-width
}
idx <- multiply img-width
idx <- add imgx
# error info in case we rounded wrong and 'index' will fail bounds-check
compare idx, len
@ -305,7 +311,6 @@ fn render-pgm-image screen: (addr screen), _img: (addr image), xmin: int, ymin:
var zero/eax: int <- copy 0
var zero-f/xmm0: float <- convert zero
var y/xmm6: float <- copy zero-f
set-cursor-position 0/screen, 0x20/x 0x20/y
{
compare y, height-f
break-if-float>=
@ -320,12 +325,7 @@ fn render-pgm-image screen: (addr screen), _img: (addr image), xmin: int, ymin:
imgx-f <- divide xratio
var imgx/ecx: int <- truncate imgx-f
var idx/eax: int <- copy imgy
{
compare idx, 0
break-if-<=
idx <- decrement
idx <- multiply img-width
}
idx <- multiply img-width
idx <- add imgx
# error info in case we rounded wrong and 'index' will fail bounds-check
compare idx, len
@ -359,6 +359,252 @@ fn nearest-grey level-255: byte -> _/eax: int {
return result
}
fn dither-pgm-with-monochrome _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/ecx: (addr int) <- get src, width
var src-width/ecx: int <- copy *src-width-a
{
var dest-width-a/edx: (addr int) <- get dest, width
copy-to *dest-width-a, src-width
}
# copy 'height'
var src-height-a/edx: (addr int) <- get src, height
var src-height/edx: int <- copy *src-height-a
{
var dest-height-a/ecx: (addr int) <- get dest, height
copy-to *dest-height-a, src-height
}
# transform '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
# needs a buffer to temporarily hold more than 256 levels of precision
var buffer-storage: (array int 0xc0000)
var buffer/ebx: (addr array int) <- address buffer-storage
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
_unordered-monochrome-dither src-data, src-width, src-height, buffer, dest-data
}
fn _unordered-monochrome-dither src: (addr array byte), width: int, height: int, buf: (addr array int), dest: (addr array byte) {
var y/edx: int <- copy 0
{
compare y, height
break-if->=
#? psd "y", y, 9/fg, 0/x, y
var x/ecx: int <- copy 0
{
compare x, width
break-if->=
#? psd "x", x, 3/fg, x, y
var error/ebx: int <- _read-buffer buf, x, y, width
$_unordered-monochrome-dither:update-error: {
var curr/eax: byte <- _read-byte-buffer src, x, y, width
error <- add curr
#? psd "e", error, 5/fg, x, y
#? draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, error, 7/fg 0/bg
compare error, 0x80
{
break-if->=
#? psd "p", 0, 0x14/fg, x, y
_write-byte-buffer dest, x, y, width, 0/black
break $_unordered-monochrome-dither:update-error
}
#? psd "p", 1, 0xf/fg, x, y
_write-byte-buffer dest, x, y, width, 1/white
error <- subtract 0xff
}
_diffuse-monochrome-dithering-errors buf, x, y, width, height, error
#? {
#? compare y, 3
#? break-if-!=
#? compare x, 4
#? break-if->=
#? {
#? var b/eax: byte <- read-key 0/keyboard
#? compare b, 0
#? loop-if-=
#? }
#? }
x <- increment
loop
}
move-cursor-to-left-margin-of-next-line 0/screen
y <- increment
loop
}
}
fn show-errors buf: (addr array int), width: int, height: int {
var y/edx: int <- copy 0
{
compare y, height
break-if->=
var x/ecx: int <- copy 0
{
compare x, width
break-if->=
var error/ebx: int <- _read-buffer buf, x, y, width
psd "e", error, 5/fg, x, y
x <- increment
loop
}
move-cursor-to-left-margin-of-next-line 0/screen
y <- increment
loop
}
}
fn psd s: (addr array byte), d: int, fg: int, x: int, y: int {
#? {
#? compare y, 3
#? break-if-=
#? return
#? }
#? {
#? compare x, 4
#? break-if-<
#? return
#? }
draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, s, 7/fg 0/bg
draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, d, fg 0/bg
}
# Use Floyd-Steinberg algorithm for turning an image of greyscale pixels into
# one of pure black or white pixels.
#
# https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html
fn _diffuse-monochrome-dithering-errors buf: (addr array int), x: int, y: int, width: int, height: int, error: int {
{
compare error, 0
break-if-!=
return
}
decrement width
decrement height
# delta = error/16
#? show-errors buf, width, height
#? draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, error, 3/fg 0/bg
#? move-cursor-to-left-margin-of-next-line 0/screen
var delta/ecx: int <- copy error
delta <- shift-right-signed 4
#? draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, delta, 2/fg 0/bg
#? move-cursor-to-left-margin-of-next-line 0/screen
# In Floyd-Steinberg, each pixel X transmits its errors to surrounding
# pixels in the following proportion:
# X 7/16
# 3/16 5/16 1/16
var x/esi: int <- copy x
{
compare x, width
break-if->=
var tmp/eax: int <- copy delta
var seven/edx: int <- copy 7
tmp <- multiply seven
var xright/edx: int <- copy x
xright <- increment
_accumulate-error buf, xright, y, width, tmp
}
#? show-errors buf, width, height
var y/edi: int <- copy y
{
compare y, height
break-if-<
return
}
var ybelow/edi: int <- copy y
ybelow <- increment
{
compare x, 0
break-if-<=
var tmp/eax: int <- copy delta
var three/edx: int <- copy 3
tmp <- multiply three
var xleft/ebx: int <- copy x
xleft <- decrement
_accumulate-error buf, xleft, ybelow, width, tmp
}
#? show-errors buf, width, height
{
var tmp/eax: int <- copy delta
var five/edx: int <- copy 5
tmp <- multiply five
_accumulate-error buf, x, ybelow, width, tmp
}
#? show-errors buf, width, height
{
compare x, width
break-if->=
var xright/edx: int <- copy x
xright <- increment
_accumulate-error buf, xright, ybelow, width, delta
}
#? show-errors buf, width, height
}
fn _accumulate-error buf: (addr array int), x: int, y: int, width: int, error: int {
var curr/ebx: int <- _read-buffer buf, x, y, width
curr <- add error
#? draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "{", 7/fg 0/bg
#? move-cursor-to-left-margin-of-next-line 0/screen
#? show-errors buf, width, 3/height
_write-buffer buf, x, y, width, curr
#? draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "===", 7/fg 0/bg
#? move-cursor-to-left-margin-of-next-line 0/screen
#? show-errors buf, width, 3/height
#? draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "}", 7/fg 0/bg
#? move-cursor-to-left-margin-of-next-line 0/screen
}
fn _read-buffer _buf: (addr array int), x: int, y: int, width: int -> _/ebx: int {
var buf/esi: (addr array int) <- copy _buf
var idx/ecx: int <- copy y
idx <- multiply width
idx <- add x
#? psd "i", idx, 5/fg, x, y
var result-a/eax: (addr int) <- index buf, idx
return *result-a
}
fn _write-buffer _buf: (addr array int), x: int, y: int, width: int, val: int {
var buf/esi: (addr array int) <- copy _buf
var idx/ecx: int <- copy y
idx <- multiply width
idx <- add x
#? draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, idx, 7/fg 0/bg
#? move-cursor-to-left-margin-of-next-line 0/screen
var src/eax: int <- copy val
var dest-a/edi: (addr int) <- index buf, idx
copy-to *dest-a, src
}
fn _read-byte-buffer _buf: (addr array byte), x: int, y: int, width: int -> _/eax: byte {
var buf/esi: (addr array byte) <- copy _buf
var idx/ecx: int <- copy y
idx <- multiply width
idx <- add x
var result-a/eax: (addr byte) <- index buf, idx
var result/eax: byte <- copy-byte *result-a
return result
}
fn _write-byte-buffer _buf: (addr array byte), x: int, y: int, width: int, val: byte {
var buf/esi: (addr array byte) <- copy _buf
var idx/ecx: int <- copy y
idx <- multiply width
idx <- add x
var src/eax: byte <- copy val
var dest-a/edi: (addr byte) <- index buf, idx
copy-byte-to *dest-a, src
}
# import a color ascii "pixmap" (each pixel consists of 3 shades of r/g/b from 0 to 255)
fn initialize-image-from-ppm _self: (addr image), in: (addr stream byte) {
var self/esi: (addr image) <- copy _self
@ -455,12 +701,7 @@ fn render-ppm-image screen: (addr screen), _img: (addr image), xmin: int, ymin:
imgx-f <- divide xratio
var imgx/ecx: int <- truncate imgx-f
var idx/eax: int <- copy imgy
{
compare idx, 0
break-if-<=
idx <- decrement
idx <- multiply img-width
}
idx <- multiply img-width
idx <- add imgx
# . multiply by 3 for the r/g/b channels
{

7
white.pgm Normal file
View File

@ -0,0 +1,7 @@
P2
4 4
255
255 255 255 255
255 255 255 255
255 255 255 255
255 255 255 255