Skip to content

Instantly share code, notes, and snippets.

@0racle
Last active January 9, 2024 23:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0racle/f48586b6b2c05d13e3abbcb046e91dfb to your computer and use it in GitHub Desktop.
Save 0racle/f48586b6b2c05d13e3abbcb046e91dfb to your computer and use it in GitHub Desktop.
Dithering with Error Diffusion in J
require 'media/imagekit/color_space'
require 'graphics/png'
require 'graphics/pplatimg'
coinsert 'pplatimg'
ParseDiff =: {{
'p d' =. (}: ; {:) ];._2 y
t =. ". '/%' rplc~ d
c =. (+./@(' ' ~: ]) {{ (1, 2 </\ x) <;.1&(x&#) y }}"1 ]) p
a =. {. ($ #: I.@,) > 'X' +./@e.~L:0 c
i =. ($ #: I.@,) > '\d+' (rxin)L:0 c
e =. t * ".@> (< i) { c
o =. i -"1 a
o ; e
}}
floyd =: {{)n
X 7
3 5 1
(1/16)
}}
jarvis =: {{)n
X 7 5
3 5 7 5 3
1 3 5 3 1
(1/48)
}}
stucki =: {{)n
X 8 4
2 4 8 4 2
1 2 4 2 1
(1/42)
}}
atkinson =: {{)n
X 1 1
1 1 1
1
(1/8)
}}
burkes =: {{)n
X 8 4
2 4 8 4 2
(1/32)
}}
sierra =: {{)n
X 5 3
2 4 5 4 2
2 3 2
(1/32)
}}
sierraTwo =: {{)n
X 4 3
1 2 3 2 1
(1/16)
}}
sierraLite =: {{)n
X 2
1 1
(1/4)
}}
Pad =: ({: ,~ {. , ])"1@({: ,~ {. , ])
Dither =: {{
'o d' =. ParseDiff x NB. offset and diffusion
e =. >./ | , o NB. get max offset
p =. (Pad^:e) y NB. pad edges for offset
'h w' =. $ y NB. rows/cols of unpadded image
for_j. e + i. h do. NB. each row (skip padding)
for_k. e + i. w do. NB. each col (skip padding)
a =. (<j,k) { p NB. active pixel
c =. <.@+&0.5 a NB. closest color
q =. d * a - c NB. quantize
i =. (j,k) <@:+"1 o NB. calculate offset
p =. (c, (q + i { p)) ((j,k);i)} p NB. update pixes
end.
end.
(< (e&+)@i.&.> $ y) { p NB. trim pading
}}
Grayscale =: ((0&{)"1)@:RGB_to_YUV
Greyscale =: ((0&{)"1)@:to255@:RGB_to_yiq
Luminance =: 0.299 0.587 0.114 <.@+/@(*"1) ]
img =. (3 # 256) #: readimg 'Pictures/david.jpg'
mat =. to01 Greyscale (con_exp^:0) img
res =. sierra Dither mat
viewrgb rgb_to_i"1 [ 255 * 3 #"0 res
NB. Save png
NB. 'dither-test.png' writepng~ rgb_to_i"1 [ 255 * 3 #"0 res
@0racle
Copy link
Author

0racle commented Jan 5, 2024

Notes

  • Try different error diffusion patterns.
  • Try different greyscaling functions (or just grab a single color channel from an RGB image, eg. 1 { img)
  • Try with and without con_exp (contrast expansion)
  • You can pass res straight to viewmat but it doesn't size 100% by default.
  • Generate 2 outputs using different error diffusions, render side-by-side (a ,. b) and compare

The error diffusion patterns came from here

@gitonthescene
Copy link

Thinking about colored dithering, I think if your palette is made up of uniform segments of each RGB channel you should probably be able to do dithering just the same as grey scale by treating each channel equally. I.e. rather than extract a channel, simply run the algorithm across all channels. However, if picking the closest color is not simply picking the closest value in each channel, I think it gets more complicated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment