GithubHelp home page GithubHelp logo

ditherpunk.jl's Introduction

💀 DitherPunk.jl 💀

Documentation Build Status

A dithering / digital halftoning package inspired by Lucas Pope's Obra Dinn and Surma's blogpost of the same name. Check out the gallery for an overview of all currently implemented algorithms.

Installation

To install this package and its dependencies, open the Julia REPL and run

julia> ]add DitherPunk

Examples

using DitherPunk
using Images
using TestImages
img = testimage("fabio_gray_256")

d = dither(img)                   # apply default algorithm: FloydSteinberg()
d = dither(img, Bayer())          # apply algorithm of choice

dither!(img)                      # or in-place modify image
dither!(img, Bayer())             # with the algorithm of your choice

If no color palette is provided, DitherPunk will apply binary dithering to each color channel of the input:

Error diffusion Ordered dithering Digital halftoning

Any of the 29 implemented algorithms can be used.

Color dithering

All error diffusion, ordered dithering and halftoning methods support custom color palettes. Define your own palette or use those from ColorSchemes.jl:

using ColorSchemes

cs = ColorSchemes.flag_us
dither(img, cs) 
flag_us PuOr_6 websafe

DitherPunk also lets you generate optimized color palettes for each input image:

ncolors = 8
dither(img, ncolors)
2 colors 8 colors 32 colors

Dithering in custom colors is supported by all error diffusion, ordered dithering and halftoning methods:

dither(img, Atkinson(), cs)
dither(img, Atkinson(), ncolors)

Braille pattern images

Images can also be printed using Unicode Braille Patterns

braille(img, Bayer())
braille(img, Bayer(); invert=true)
⠕⠅⠅⠅⠕⠅⠕⠅⠅⠅⠅⠅⠅⠅⠅⠅⠅⠅⠕⠅⠕⠅⠕⢅⠕⠅⠕⢅⠕⠅⠕⠅⠅⠅⠅⠅⠅⠅⠕⠅⠁⠅⠁⠅⠁⠅⠁⠅⠁⠅⠁⠅⣪⣺⣺⣺⣪⣺⣪⣺⣺⣺⣺⣺⣺⣺⣺⣺⣺⣺⣪⣺⣪⣺⣪⡺⣪⣺⣪⡺⣪⣺⣪⣺⣺⣺⣺⣺⣺⣺⣪⣺⣾⣺⣾⣺⣾⣺⣾⣺⣾⣺⣾⡂
⠕⢅⠅⠅⠕⠅⠕⠅⠅⠅⠁⠅⠁⠅⠁⠅⠁⠅⠕⠅⠕⢅⢕⢕⢕⠅⠕⠅⢕⢅⠕⢅⠅⠅⠅⠅⠅⠅⠅⠅⠅⠅⠁⠄⠁⠄⠁⠄⠁⠄⠁⠄⣪⡺⣺⣺⣪⣺⣪⣺⣺⣺⣾⣺⣾⣺⣾⣺⣾⣺⣪⣺⣪⡺⡪⡪⡪⣺⣪⣺⡪⡺⣪⡺⣺⣺⣺⣺⣺⣺⣺⣺⣺⣺⣾⣻⣾⣻⣾⣻⣾⣻⣾⡃
⠕⢅⠅⠅⠕⠅⠕⠅⠅⠅⠁⠅⠁⠅⠁⠅⠅⠅⠅⢅⢕⢅⢕⢥⠕⢕⢕⢅⢕⢅⠕⠅⠅⠅⠅⠅⠁⠅⠁⠅⠁⠅⠁⠄⠁⠄⠁⠄⠁⠄⠁⠄⣪⡺⣺⣺⣪⣺⣪⣺⣺⣺⣾⣺⣾⣺⣾⣺⣺⣺⣺⡺⡪⡺⡪⡚⣪⡪⡪⡺⡪⡺⣪⣺⣺⣺⣺⣺⣾⣺⣾⣺⣾⣺⣾⣻⣾⣻⣾⣻⣾⣻⣾⡃
⠕⠅⠅⠅⠕⠅⠕⠅⠅⠅⠅⠅⠅⠅⠁⠅⠅⢵⢝⢵⢽⢽⢝⢵⢽⢽⢝⢕⠕⢕⠕⠕⠕⢅⠑⠄⠁⠅⠁⠅⠁⠅⠁⠄⠁⠄⠁⠄⠁⠄⠁⠄⣪⣺⣺⣺⣪⣺⣪⣺⣺⣺⣺⣺⣺⣺⣾⣺⣺⡊⡢⡊⡂⡂⡢⡊⡂⡂⡢⡪⣪⡪⣪⣪⣪⡺⣮⣻⣾⣺⣾⣺⣾⣺⣾⣻⣾⣻⣾⣻⣾⣻⣾⡃
⠕⠅⠅⠅⠕⠅⠕⠅⠅⠅⠅⠅⠅⠅⠅⢕⢝⢵⢝⢝⢕⢅⢅⢅⠕⠅⠅⠕⢕⢅⠅⠅⠅⢅⠕⠅⠅⠕⠁⠅⠁⠅⠁⠄⠁⠄⠁⠄⠁⠄⠁⠄⣪⣺⣺⣺⣪⣺⣪⣺⣺⣺⣺⣺⣺⣺⣺⡪⡢⡊⡢⡢⡪⡺⡺⡺⣪⣺⣺⣪⡪⡺⣺⣺⣺⡺⣪⣺⣺⣪⣾⣺⣾⣺⣾⣻⣾⣻⣾⣻⣾⣻⣾⡃
⠕⢅⠅⠅⠁⠅⠁⠅⠁⠅⠅⠅⠅⢅⢕⢕⠝⠅⠕⠅⠅⠅⠅⢕⠅⢕⠅⠄⠁⠕⠁⠅⠁⠅⠁⠅⠁⠅⢕⢅⠅⠅⠁⠅⠁⠄⠁⠄⠁⠄⠁⠄⣪⡺⣺⣺⣾⣺⣾⣺⣾⣺⣺⣺⣺⡺⡪⡪⣢⣺⣪⣺⣺⣺⣺⡪⣺⡪⣺⣻⣾⣪⣾⣺⣾⣺⣾⣺⣾⣺⡪⡺⣺⣺⣾⣺⣾⣻⣾⣻⣾⣻⣾⡃
⠕⢅⠅⠅⠁⠅⠕⠅⠅⠅⠅⠅⠅⢕⠕⢅⠕⠅⠁⠅⠅⠅⠁⠅⠅⠅⠁⠅⠅⠅⠁⠅⠁⠄⠅⢅⠅⠅⠁⠅⠑⠅⠁⠅⠁⠄⠁⠄⠁⠄⠁⠄⣪⡺⣺⣺⣾⣺⣪⣺⣺⣺⣺⣺⣺⡪⣪⡺⣪⣺⣾⣺⣺⣺⣾⣺⣺⣺⣾⣺⣺⣺⣾⣺⣾⣻⣺⡺⣺⣺⣾⣺⣮⣺⣾⣺⣾⣻⣾⣻⣾⣻⣾⡃
⠕⢅⠅⠅⠁⠅⠕⠅⠅⠅⠅⠅⢕⢅⠅⢅⠅⠅⠁⢕⢽⣵⢵⢵⢵⢵⢕⢵⢕⢅⢅⢅⢵⢵⢕⢕⢕⢕⠕⠄⠁⠅⠅⠅⠁⠄⠁⠄⠁⠄⠁⠄⣪⡺⣺⣺⣾⣺⣪⣺⣺⣺⣺⣺⡪⡺⣺⡺⣺⣺⣾⡪⡂⠊⡊⡊⡊⡊⡪⡊⡪⡺⡺⡺⡊⡊⡪⡪⡪⡪⣪⣻⣾⣺⣺⣺⣾⣻⣾⣻⣾⣻⣾⡃
⠕⢅⠕⠅⠁⠅⠕⠅⠅⠅⠕⢵⢕⠅⠅⠅⠅⠄⠕⢕⢽⢽⢿⣽⢿⢽⢿⣽⢿⢽⢿⢽⢿⢽⢝⢵⢕⢕⠕⠅⠅⠅⠁⠅⠅⠅⠁⠅⠁⠅⠁⠄⣪⡺⣪⣺⣾⣺⣪⣺⣺⣺⣪⡊⡪⣺⣺⣺⣺⣻⣪⡪⡂⡂⡀⠂⡀⡂⡀⠂⡀⡂⡀⡂⡀⡂⡢⡊⡪⡪⣪⣺⣺⣺⣾⣺⣺⣺⣾⣺⣾⣺⣾⡃
⠕⠅⠕⠅⠁⠅⠕⢅⠅⢅⢕⢕⠕⢅⠅⠅⠅⠅⠅⠅⢝⢽⢿⢽⢿⢽⢿⢽⢿⢽⢿⣽⢿⢽⢝⢵⢝⢕⠅⠄⠁⠅⠕⢅⢅⠅⠅⠅⠁⠅⠁⠄⣪⣺⣪⣺⣾⣺⣪⡺⣺⡺⡪⡪⣪⡺⣺⣺⣺⣺⣺⣺⡢⡂⡀⡂⡀⡂⡀⡂⡀⡂⡀⠂⡀⡂⡢⡊⡢⡪⣺⣻⣾⣺⣪⡺⡺⣺⣺⣺⣾⣺⣾⡃
⠕⠅⠕⠅⠁⢅⢕⢕⢝⢽⢝⢅⢕⢅⠅⠄⠕⠅⠁⢵⢝⢽⢽⢽⢿⣽⢿⢽⢿⣽⢿⣽⢿⢽⢝⢵⢝⢕⠕⠅⠁⠅⠕⢕⠕⠅⠅⠅⠁⠄⠁⠄⣪⣺⣪⣺⣾⡺⡪⡪⡢⡂⡢⡺⡪⡺⣺⣻⣪⣺⣾⡊⡢⡂⡂⡂⡀⠂⡀⡂⡀⠂⡀⠂⡀⡂⡢⡊⡢⡪⣪⣺⣾⣺⣪⡪⣪⣺⣺⣺⣾⣻⣾⡃
⠕⠅⠕⠅⠑⢅⠕⢅⢝⢵⢕⢵⢕⠅⠁⠅⠁⠄⠁⢵⢝⢝⠝⠝⠝⠝⠟⠝⢿⢽⢽⢽⠝⠝⠝⠅⠁⢅⢕⠅⠁⠄⠁⠅⠑⠅⠁⠅⠅⠄⠁⠄⣪⣺⣪⣺⣮⡺⣪⡺⡢⡊⡪⡊⡪⣺⣾⣺⣾⣻⣾⡊⡢⡢⣢⣢⣢⣢⣠⣢⡀⡂⡂⡂⣢⣢⣢⣺⣾⡺⡪⣺⣾⣻⣾⣺⣮⣺⣾⣺⣺⣻⣾⡃
⠕⠅⠕⠅⠕⢅⢝⢵⢝⢵⢽⢕⢝⠅⠅⠄⠁⠄⢑⢽⢽⢵⢕⢵⢕⢵⢕⢵⢝⢽⢿⢕⢕⢕⢕⢕⢕⢕⢕⢕⢕⠅⠁⠅⠁⠅⠅⠅⠅⠅⠁⠄⣪⣺⣪⣺⣪⡺⡢⡊⡢⡊⡂⡪⡢⣺⣺⣻⣾⣻⡮⡂⡂⡊⡪⡊⡪⡊⡪⡊⡢⡂⡀⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⣺⣾⣺⣾⣺⣺⣺⣺⣺⣾⡃
⠕⠅⠕⠅⠕⢕⢽⢕⢝⢽⢝⢕⢕⢅⠅⠄⠅⠄⠑⢽⢽⢽⢿⣿⢿⣽⢿⢽⢝⢽⢝⢕⢝⢽⢝⢽⢽⢽⢝⢅⢝⠕⠁⠄⠑⢅⠕⢅⠁⠅⠁⠄⣪⣺⣪⣺⣪⡪⡂⡪⡢⡂⡢⡪⡪⡺⣺⣻⣺⣻⣮⡂⡂⡂⡀⠀⡀⠂⡀⡂⡢⡂⡢⡪⡢⡂⡢⡂⡂⡂⡢⡺⡢⣪⣾⣻⣮⡺⣪⡺⣾⣺⣾⡃
⠕⠅⠕⠅⠕⢅⢝⢵⢽⢵⢝⢝⢝⢅⠕⢅⠕⠅⠁⢕⢝⢽⢿⣽⢿⣽⢿⢽⢝⢽⢟⢕⢕⢽⢝⢽⢝⢕⢕⢅⠝⠅⠁⠄⠑⢕⢕⠅⠕⠅⠁⠄⣪⣺⣪⣺⣪⡺⡢⡊⡂⡊⡢⡢⡢⡺⣪⡺⣪⣺⣾⡪⡢⡂⡀⠂⡀⠂⡀⡂⡢⡂⡠⡪⡪⡂⡢⡂⡢⡪⡪⡺⣢⣺⣾⣻⣮⡪⡪⣺⣪⣺⣾⡃
⠕⠅⠕⠕⠕⢅⢽⢵⢝⢽⠝⢕⢕⢕⢕⢅⠁⠀⠁⢕⢝⢽⢝⢽⢽⢽⢿⢽⢝⢽⢝⢕⢝⢵⢝⢕⢝⢕⠕⢅⠁⠀⠁⠄⠁⠕⠕⠅⠅⠅⠁⠄⣪⣺⣪⣪⣪⡺⡂⡊⡢⡂⣢⡪⡪⡪⡪⡺⣾⣿⣾⡪⡢⡂⡢⡂⡂⡂⡀⡂⡢⡂⡢⡪⡢⡊⡢⡪⡢⡪⣪⡺⣾⣿⣾⣻⣾⣪⣪⣺⣺⣺⣾⡃
⠕⠅⠅⠅⠕⢵⢟⢵⢽⢝⢕⢕⢕⢕⠕⠅⠁⠀⠁⢅⢕⢕⢝⢽⢿⢽⢽⢵⢝⢝⠝⢕⢝⢕⢝⢕⠝⢕⢕⢅⠁⠀⠁⠄⠁⠅⠕⠅⠅⠄⠁⠄⣪⣺⣺⣺⣪⡊⡠⡊⡂⡢⡪⡪⡪⡪⣪⣺⣾⣿⣾⡺⡪⡪⡢⡂⡀⡂⡂⡊⡢⡢⣢⡪⡢⡪⡢⡪⣢⡪⡪⡺⣾⣿⣾⣻⣾⣺⣪⣺⣺⣻⣾⡃
⠕⢅⠕⢕⢕⢝⢟⢽⢝⢵⢕⢵⢕⠕⠅⠅⠁⠀⠁⢅⢝⢕⢝⢽⢝⢽⢝⢽⢝⢕⢕⢕⢝⢕⢕⢕⠕⢕⠕⠅⠁⠀⠁⠀⠁⠅⠅⠅⠁⠅⠁⠄⣪⡺⣪⡪⡪⡢⡠⡂⡢⡊⡪⡊⡪⣪⣺⣺⣾⣿⣾⡺⡢⡪⡢⡂⡢⡂⡢⡂⡢⡪⡪⡪⡢⡪⡪⡪⣪⡪⣪⣺⣾⣿⣾⣿⣾⣺⣺⣺⣾⣺⣾⡃
⠕⢅⢕⢕⢝⢝⢝⢽⢽⢵⢝⢕⠅⠅⠅⠅⠁⠄⠁⠀⠝⢵⢝⢽⢝⢵⢵⢵⢵⢭⢝⢵⢕⢕⢕⢕⠕⢕⠕⢕⠅⠄⠁⠀⠁⠅⠅⠅⠅⠅⠕⠅⣪⡺⡪⡪⡢⡢⡢⡂⡂⡊⡢⡪⣺⣺⣺⣺⣾⣻⣾⣿⣢⡊⡢⡂⡢⡊⡊⡊⡊⡒⡢⡊⡪⡪⡪⡪⣪⡪⣪⡪⣺⣻⣾⣿⣾⣺⣺⣺⣺⣺⣪⡂
⠕⢝⢕⢕⢕⢽⢽⢽⢽⢽⢝⢕⢕⠅⠅⠅⠁⠀⠁⠀⠁⠑⠝⢕⢝⢽⢝⢵⢝⢕⢕⢕⢕⢕⢕⢕⠕⢅⢕⢅⠕⢕⢕⢄⠅⠀⠁⠅⠁⠅⠁⠅⣪⡢⡪⡪⡪⡂⡂⡂⡂⡂⡢⡪⡪⣺⣺⣺⣾⣿⣾⣿⣾⣮⣢⡪⡢⡂⡢⡊⡢⡪⡪⡪⡪⡪⡪⡪⣪⡺⡪⡺⣪⡪⡪⡻⣺⣿⣾⣺⣾⣺⣾⡂
⠕⢵⢕⢵⢝⢽⢽⣽⢝⢕⢕⢕⠝⠅⠅⠅⠁⠄⠁⠀⠁⠀⠑⢅⠝⢽⢝⢽⢿⢽⢿⢽⢝⢕⠕⢅⢕⢕⢕⢕⢕⢕⢝⢕⢕⢕⠅⠄⠁⠀⠁⠄⣪⡊⡪⡊⡢⡂⡂⠂⡢⡪⡪⡪⣢⣺⣺⣺⣾⣻⣾⣿⣾⣿⣮⡺⣢⡂⡢⡂⡀⡂⡀⡂⡢⡪⣪⡺⡪⡪⡪⡪⡪⡪⡢⡪⡪⡪⣺⣻⣾⣿⣾⡃
⢕⢵⢽⣽⢿⢽⢝⢝⢵⢕⠝⠕⠅⢅⠅⠅⠁⠅⠁⠄⠁⠀⠁⠅⢕⢕⠝⢕⠝⠝⠝⠕⠝⢅⢕⢕⢕⢕⢝⢕⢝⢕⢝⢕⢝⢕⢝⢅⠁⠄⠁⠄⡪⡊⡂⠂⡀⡂⡢⡢⡊⡪⣢⣪⣺⡺⣺⣺⣾⣺⣾⣻⣾⣿⣾⣺⡪⡪⣢⡪⣢⣢⣢⣪⣢⡺⡪⡪⡪⡪⡢⡪⡢⡪⡢⡪⡢⡪⡢⡺⣾⣻⣾⡃
⢽⢽⢟⢝⢝⢵⢽⢝⢕⠅⠕⠅⠁⠅⠁⠄⠁⠄⠁⠄⠁⠄⠁⢄⠕⢕⢕⢵⢕⢵⢕⢵⢕⢵⢝⢵⢝⢵⢝⢵⢝⢕⢝⢕⢝⢕⢕⢕⢕⠄⠁⠄⡂⡂⡠⡢⡢⡊⡂⡢⡪⣺⣪⣺⣾⣺⣾⣻⣾⣻⣾⣻⣾⣻⣾⡻⣪⡪⡪⡊⡪⡊⡪⡊⡪⡊⡢⡊⡢⡊⡢⡊⡢⡪⡢⡪⡢⡪⡪⡪⡪⣻⣾⡃
⢝⢽⢝⢽⢟⢽⢝⢵⢕⠅⠁⠄⠅⠄⠁⠄⠁⠄⠁⠄⠁⠀⠁⠅⠕⢕⢝⢕⢝⢽⢝⢵⢝⢵⢽⢽⢽⢽⢝⢵⢝⢵⢝⢵⢝⢵⢝⢕⢝⢕⠅⠄⡢⡂⡢⡂⡠⡂⡢⡊⡪⣺⣾⣻⣺⣻⣾⣻⣾⣻⣾⣻⣾⣿⣾⣺⣪⡪⡢⡪⡢⡂⡢⡊⡢⡊⡂⡂⡂⡂⡢⡊⡢⡊⡢⡊⡢⡊⡢⡪⡢⡪⣺⡃
⠝⢽⢝⢽⢝⢽⠝⠕⠅⠅⠅⠅⠁⠄⠁⠄⠁⠅⠁⠄⠁⠀⠁⠄⢕⢵⢝⢵⢝⢽⢝⢽⢽⢽⢽⢽⢽⢵⢕⢽⢝⢽⢝⢵⢝⢵⢝⢵⢝⢕⢕⠅⣢⡂⡢⡂⡢⡂⣢⣪⣺⣺⣺⣺⣾⣻⣾⣻⣾⣺⣾⣻⣾⣿⣾⣻⡪⡊⡢⡊⡢⡂⡢⡂⡂⡂⡂⡂⡂⡊⡪⡂⡢⡂⡢⡊⡢⡊⡢⡊⡢⡪⡪⡂
⠝⠽⠝⠵⠝⠕⠕⠅⠕⠅⠁⠄⠁⠄⠁⠄⠁⠄⠁⠄⠁⠄⠁⠅⠕⠽⠝⠽⠝⠵⠝⠽⠽⠽⠿⠽⠽⠵⠝⠵⠝⠵⠝⠵⠝⠵⠝⠵⠝⠵⠝⠅⠢⠂⠢⠊⠢⠪⠪⠺⠪⠺⠾⠻⠾⠻⠾⠻⠾⠻⠾⠻⠾⠻⠾⠺⠪⠂⠢⠂⠢⠊⠢⠂⠂⠂⠀⠂⠂⠊⠢⠊⠢⠊⠢⠊⠢⠊⠢⠊⠢⠊⠢⠂

For a more in-depth introduction, take a look at the docs.

Demonstration

Check out our talk at JuliaCon 2022 for a demonstration of the package:

List of implemented algorithms

  • Error diffusion:
    • FloydSteinberg (default)
    • JarvisJudice
    • Atkinson
    • Stucki
    • Burkes
    • Sierra
    • TwoRowSierra
    • SierraLite
    • Fan93
    • ShiauFan
    • ShiauFan2
    • SimpleErrorDiffusion
  • Ordered dithering:
    • Bayer
  • Halftoning:
    • ClusteredDots
    • CentralWhitePoint
    • BalancedCenteredPoint
    • Rhombus
    • Threshold maps from ImageMagick:
      • IM_checks
      • IM_h4x4a
      • IM_h6x6a
      • IM_h8x8a
      • IM_h4x4o
      • IM_h6x6o
      • IM_h8x8o
      • IM_c5x5
      • IM_c6x6
      • IM_c7x7
  • Other:
    • ClosestColor
    • ConstantThreshold
    • WhiteNoiseThreshold

Share your creations in the discussions tab and leave a GitHub Issue if you know of any cool algorithms you'd like to see implemented! 🔬🔧

ditherpunk.jl's People

Contributors

adrhill avatar github-actions[bot] avatar johnnychen94 avatar t-bltg avatar tinkerer-zlosk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

smoofer t-bltg crest

ditherpunk.jl's Issues

Separate inner loop of error diffusion

As mentioned here by @johnnychen94:

I noticed this only recently; for the innermost for loop, a performance practice is to separate the loops into two parts: 1) border and 2) inner area. The benefits are 1) we can remove unnecessary bounds check for the inner area, and thus 2) we can enable @simd for such simple operations.

See https://github.com/JuliaImages/Images.jl/blob/9796acc8bf1d56d9574c555656fdba362f84a988/src/algorithms.jl#L505-L537 and JuliaImages/ImageBase.jl#22 (comment)

Decrease memory usage in tile_matrix

In the tile_matrix(h, w, mat) function in ordered.jl, the line return repeat(mat, repeat_rows, repeat_cols)[1:h, 1:w] # trim to image dims is using twice the memory actually required for the returned matrix -- 'repeat' is creating one image in memory, and [1:h, 1:w] appears to be creating a 2nd image in memory. When the [1:h, 1:w] at the end of the line is removed, memory usage drops to half, and mean run time drops by 60%.

A 2nd possible issue is the argument order for this function. If a bang version was created, then the arguments should be (mat, h, w) instead of (h, w, mat).

Below is a listing that covers both of these issues. Aside from using less memory, it has about 20% less median run time. I couldn't hit the 60% reduction; repeat's codebase is considerably faster than mine, but is also considerably more convoluted. I could not follow it.

`function tile_matrix(mat, h, w)
new = Vector{eltype(mat)}(undef, h*w)
new = reshape(new, h, w)
h_mat, w_mat = size(mat)

for i = 1:w_mat, j = 1:h_mat
	new[j:h_mat:h, i:w_mat:w] .= mat[j,i]
end
return new

end`

Bayer

In ordered.jl, there is in error in the bayer_matrix function, as the added integers are being included in the multiplication.

    bₙ₋₁ = bayer_matrix(n - 1)
    b = 4 * [(bₙ₋₁) (bₙ₋₁.+2); (bₙ₋₁.+3) (bₙ₋₁.+1)]

should be

    bₙ₋₁ = 4 * bayer_matrix(n - 1)
    b = [(bₙ₋₁) (bₙ₋₁.+2); (bₙ₋₁.+3) (bₙ₋₁.+1)]

bayer_matrix(1) # Current
4×4 Array{Int64,2}:
0 8 8 16
12 4 20 12
12 20 4 12
24 16 16 8

fixed_bayer_matrix(1) # Fixed
4×4 Array{Int64,2}:
0 8 2 10
12 4 14 6
3 11 1 9
15 7 13 5

Add direct binary search (DBS)

As described in [1] and Chapter 7 of Modern Digital Halftoning by Lau & Are.

[1] Analoui, M.; Allebach, J.P. (February 1992). "Model-based Halftoning by Direct Binary Search". Proceedings of the 1992 SPIE/IS&T Symposium on Electronic Imaging Science and Technology. 1666: 9–14.

Add blue noise dithering

As described in Chapter 8 of Digital Halftoning by Ulichney and Chapter 6 of Modern Digital Halftoning by Lau & Are.

Refactor dithering to braille

  • using UnicodePlots is overkill, a dictionary look-up would be enough to match Unicode braille characters to 4x2 tiles in the image
  • braille currently doesn't support the default dithering algorithm introduced in #75 and 477c98e.
  • a keyword argument that inverts the output would be useful since light & dark-modes require inverted braille characters.

Limit accumulated error in error diffusion

Currently, there is no limit to the error accumulated in img during error diffusion:

err = px - col
for dr in drs
for dc in dcs
if (r + dr > 0) && (r + dr <= h) && (c + dc > 0) && (c + dc <= w)
img[r + dr, c + dc] += err * filter[dr, dc]
end
end
end

When using color palettes that don't match the input image and therefore lead to high errors, these errors accumulate to the point where they add glitchy artifacts to the entire output.

This is shown in the following code, which applies FloydSteinberg on 8 different color palettes using 5 color distance measures:

using DitherPunk
using Images
using TestImages
using ColorSchemes

img = imresize(testimage("fabio_color_512"); ratio=1//2)

metrics = [DE_2000(), DE_94(), DE_JPC79(), DE_CMC(), DE_AB()]
cschemes = [
    ColorSchemes.rainbow,
    ColorSchemes.flag_ci,
    ColorSchemes.flag_it,
    ColorSchemes.flag_cf,
    ColorSchemes.jet,
    ColorSchemes.julia_colorscheme,
    ColorSchemes.PuOr_7,
    ColorSchemes.hokusai,
]

ds = [dither(img, FloydSteinberg(), cs.colors; metric=m) for cs in cschemes for m in metrics]
mosaicview(ds; ncol=length(cschemes))

dither_mosaic

It would be interesting to see how other error diffusion implementations deal with this. Maybe the error should be bound to the limits of the colorant, i.e. between 0 and 1 on each channel.

(The bug looks pretty cool though...)

default dither method?

I'm wondering if FloydSteinberg() a good default method so that we can expose the following two interfaces to the users without the need to remember the algorithm names.

dither(img)
dither(img, n)

Diffuse error in XYZ color space

The ColorVectorSpace.jl readme mentions:

Colorspaces such as RGB, unlike XYZ, are technically non-linear; perhaps the most "colorimetrically correct" approach when averaging two RGBs is to first convert each to XYZ, average them, and then convert back to RGB.

This sounds to me like more accurate error diffusion would be possible in XYZ color space.

FloydSteinberg performance issue for given colormap

function dither_bw(img)
    alg = DitherPunk.FloydSteinberg()
    img = Gray{N0f8}.(img)
    img_bw = DitherPunk.dither(img, alg, [Gray{N0f8}(0), Gray{N0f8}(1)])
    return Bool.(img_bw)
end

img = testimage("cameraman");
@btime dither_bw($img) # 109.763 ms (5453396 allocations: 86.75 MiB)

vs MATLAB's dither function

img = imresize(imread("cameraman.tif"), [512, 512]);
f = @() dither(img)
timeit(f) * 1000 % 2.8ms

It indicates that DitherPunk can be faster. The massive allocation count looks like type instability to me.

Add Knuth dot diffusion

As described in [1] and Chapter 5.2 of Modern Digital Halftoning by Lau & Are.

Knuth, Donald E. "Digital halftones by dot diffusion." ACM Transactions on Graphics (TOG) 6.4 (1987): 245-273.

Refactor internals to use lower-precision fixed point numbers

Addresses #70.

Currently, when passed an input image of eltype Colorant{T}, the dithered output image is computed using the floating point number type T provided by the user.

Since the goal of dithering is image quantization, it would be reasonable to exclusively use quantized N0f8 fixed-point numbers from FixedPointNumbers.jl internally.

This would also enable the optional use of a look-up table of closest colors. Currently, the performance bottleneck of most dithering algorithms is a large amount of calls to colordiff from Colors.jl. For color palettes of size $n$, this function is called at least $n$ times on each pixel. For an upfront cost of $256^3 \cdot n$ calls to colordiff, a N0f8 look-up table could be computed instead. This would open up DitherPunk for dithering of live-video.

Add serpentine scanning option to error diffusion algorithms

As described here under the section "The "false" Floyd-Steinberg filter":

Much better results would be obtained by using an alternating, or
serpentine, raster scan: processing the first line left-to-right, the next
line right-to-left, and so on (reversing the filter pattern appropriately).
Serpentine scanning –which can be used with any of the error-diffusion
filters detailed here– introduces an additional perturbation which
contributes more randomness to the resultant halftone.

(Apparently this is also known as Boustrophedon transform dithering.)

Add images to readme

The readme refers to the gallery in the docs but looks a bit dry.
Should I add an asset-folder to /docs or just upload some images as comments here and link to them @johnnychen94?

Ordered dithering threshold matrices should not start at zero

While looking at the ImageMagick matrices, I noticed that their Bayer matrices don't start at zero (they are shifted by .+ 1) . The purpose of this is to have an additional "fully" black pattern. Let me demonstrate:

  • Our current Bayer implementation:
    image
  • Shifting the matrix by one:
    bayer

The same applies to the other methods in ordered.jl:

  • Our current implementation of ClusteredDots:
    image
  • Shifting the matrix by one:
    clustereddots

Support dithering with ASCII characters

I've taken a brief look into how this is typically done. Usually people define a ramp of ASCII characters to approximate a gray-scale color ramp. In this sense, we can already dither with ASCII characters using the underlying IndirectArray:

using DitherPunk, TestImages, ImageCore, ImageTransformations

img = testimage("fabio_gray_256")
img = imresize(img, ratio=(1//6, 1//3))

ascii_ramp = split(" .:-=+*#%@", "")
cs = Gray.(range(0, 1, length=10)) # match length of ramp

d = dither(img, FloydSteinberg(), cs)
mat = ascii_ramp[d.index]

for r in eachrow(mat)
    println(join(r))
end

which prints the following (make sure your browser window is wide enough):

----::--------::-:-:-::-::-:--------:--=-=-=---:-----:::::-:-:--:-::::::.::.::.:::.::.
-=--::---------::::::::::::------------=--::-=-+=-----=--::-:-:-:-:::::.:.:.::.:.:.:.:
-=--::--------::::::::::::::----------=+++=+=+=-:---:-:-:--::--:-:::-:.:.:.:.:.:.:.:..
=---::-------:::::::::::::::-:-:--:=+==+=-=++++==+==+==--::::::-:-:::.:...:.:.:.:.:.:.
--=-::-:-------::::::::::::-:::--=====##*+======+=-----::-::.:::::::::..:.:.:.:.:.:.:.
-=--::--------:-::::::::-:-:-++=*+*%#*#*****++++++=-=-::-::.::.:-:::::.....:.:.:.:.:.:
-=--:::-----:--:-:-:--:::--=++***#*+++++++++*+=---=-:=:==:-:.:-:::.::.:.:.:.:.:.:.:.:.
=---::-:------:-::::::::-=+++*#*+=--:::-::::--=+==--=-:=-+:::-:::::.:::..:.:.:.:.:.:.:
----::::-:::.:::-:--:-:-=-***--:-:--=+====-::.-:++++-:.:::-:::--::.::::::.:.:.:.:.:..:
-=--::::::::::::::::::--=+=::::--+:.-----:-=-::.:::-::...::::::--=--:-:.:.:.::.:.:.:.:
--=::::::---:-::-:::-:=+=+=:=---::----::::=-:.+..::::.:::.:::::::=--:::::.::.:.:.:.:.:
-=---:::----:-::::-::=++=-=:::.:::==::----:-:-:::.::.::---------:..::::::::..:.:.:::.:
=-=-::::-:=--::-::::=+-=-::::.::-*#*#*+#**#*++**---:-=***+*++=+==:.:::-::::::.:.:..:..
-==-::::------:::---*++-::--:::-=*######%%##%%###########*+++=++=-::.::::::::.::.::.::
------::--=--:-:--=+*+=-=---:-:--+###%##%#%%#%%%%%##%#%#**+++=+=-:::::-:---::::.:.:.:.
-=---:::--==-----+*++-::-:::::::==*###%%#%##%##%#%%######***+++=-.:::-:-::::::-::.:.:.
=-----::--==--=+***+-:-=::.:-:-::=+*######%########%####***+++=+-:..:---==+--:::.:.:.:
--=:-:::---==*+#+#+=-++=:::-:...*+*########%%#%%#%#%%####***++===-.:.:---=----:.:.::..
=-----::--=++=#***-+++=:.:.....-+****#%%%%%#%#%#%#%##%#%#**++----=....:---=-::::::.::.
-=---:::-==+-*+**++*+=-::..:..:=**+++------==+=*#####+-::::::--===:.:..:..::-::::::.:.
-=---::---=+*****+*+*-:::.:....+****+=+#--:+-=-=*##*==++*:-*+--=+===+:..:::-::::-::.:.
=-=---::-=++*++*+**=+=-::::..::=*#%##%****#####+*##*++*+*#**+**+++-++=.:.:::--::-::..:
-=-:-:::-*+#+****#+*=+--::::..:=+*#%%%%%@%%%##*+*##+=++**###****+==#*+...:--:-==::::..
-----::---#*++****+++===-:--:..-+*##%%%%%#%#%#***##*==+**#*##**++=-**-..:::=--=+:-:::.
-=-:--::-=*++*****=++=++-=---:.:=+*##%#%%%#%##***##*+==******++++-=+:...::--+=+---::.:
----:::-=+*+**#**++=+*++-=-....:-=+**######%##***#**+==*****+++==-=...:.:.::=++--:::..
=----::===*+*****+=+*===+=-....::+=+**#*######***%%*+=+++*++++====-....:.:::==-==::::.
-=--:-:-=+#***#*++++-++=+=:.....-=++**#*###*%##=+**++=+*++*+++=====....::.::-----::::.
------:-*#*#*#+*++==*=++=::....::+++++**#**####**+==+++=+==++=====-......:::-:--:::.::
----===**+*+*#***++**+=:::::....-=*++*+*#*********=*+++==+=++==+==:......:::--:-::::.:
=----*+***++******++=---::::.....==*+*+*#*++=--=-=+----=++++==+==-=:.......::-:--:::-:
::=*++=*=++**#***+*==-=:-:::......=++*++***#*****++++++=+++=+===-===--......::-:-:::-:
-=*====++**##**#*+==-+-::::....... .=+++*#****++++==+==+==+===-++====++-=......::.:.::
--*==-=+**#**#**+=====-:-.:..........-=+=**########****+++===-+++====+++*++=..........
:=*+*+**%#####+=+=+++-:-::..:.........-=-=+***####%##**++==-++++==+++=+++++++=......:.
=+++**###*##++=#=+++-:---:.::.:.......:-+=+=+++**++++++=-=++++++++=++++++++++++....:..
**+*#%#***+**+*=*+=::.---.:.::.::......-==+====--------+==++++++++++++=+=++++=++:...::
+###***++*#**+*+---..:::..:::.::......::-+=++++==+****+++*++**+++++*++++++=+======:..:
#***+***##*+**==:-::::.::..:........:.---++++***#********+******++*+*+*++++=+++++++.:.
*****#***+**++-=-:-:::::...::.::.......--=+++++**********#####*+++*******+**+*+++===:.
*%*********+=*-::::::::.:.:.:......:..:::-+**+*+***#***#*#####*+=+#****+**+*++*+*++===
*=#**#***++---:::.:.:.:::....::.:....::.:+*****+*+***#*#*#*#*#++***+++++++*+**+*+*+**+
***#****++=--:=::::.:.:.::..:...:.....:--=********++*****######**+*+*++**+*++**+******

This might be worth adding to the docs.

However, when #60 is implemented, we can go one step further and use font rasterizers like FreeTypeAbstraction.jl to generate tiles from characters, average their color and return ASCII. This would also open up the possibility of supporting colored letters on colored background.

DitherPunk API planning

Per-channel dithering by default

Currently, to apply per-channel dithering, methods have to be wrapped in the SeparateSpace() meta-method.
Since this can be applied to any algorithm, the API could be made more elegant by applying channel-wise dithering by default:

dither(Gray.(img), FloydSteinberg())  # => binary dithering (black & white output)
dither(RGB.(img), FloydSteinberg())   # => per-channel binary dithering (2^3 color output)

For convenience, a function binary_dither could be provided that first calls Gray.(), then dither.

Conditional dependencies

I'm not sure how well Requires.jl works, but these would come in handy for methods that support custom color palettes. Some examples:

  1. Support for ColorSchemes.jl:
    dither(img, FloydSteinberg(), ColorSchemes.PuOr_7.colors)  # current API
    dither(img, FloydSteinberg(), :PuOr_7)                     # this would be much simpler!
  2. Braille plots via UnicodePlots could be re-introduced for binary dithering methods.
  3. Rough idea: support clustering methods (e.g. from Clustering.jl) for optimized color palettes. The user could then just pass their desired number of colors instead of having to specify a palette.

Support dithering with tiles

Related to a question that came up in the Julia Slack #image-processing channel a while ago, it would be nice to dither using smaller images as tiles.

A simple way to implement this would be to average colors in each tile to obtain a color scheme. We would then obtain an IndirectArray from which it would be possible to reconstruct the tiled image.

One open issue is how to deal with non-square tiles. It might be necessary to rescale the input image to compensate for stretching of the output image.

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Add interactive example

Host a Pluto notebook using DitherPunk on Binder using https://pluto-on-binder.glitch.me.
This can then be linked to from the readme and docs.

Feature checklist

  • option to select image from file or URL
  • sliders to rescale image
  • dropdown menu to select dithering algorithm

Add per-channel dithering

Add a meta-method that takes any gray-scale dithering algorithm and applies channel-wise binary dithering to color images using channelview.

An illustration of this is shown here, where it is called "separate-space dithering".

The type AbstractColorDither will need be extended by AbstractCustomColorDither to differentiate this method from other methods like Floyd-Steinberg that take custom user-defined color palettes.

The output image will look different depending on the concrete color type on which channelview is applied. As RGB is typically used, this should be the default.

Pseudocode example:

alg = SeparateSpaceDither(Bayer(); colorspace=RGB)
dither!(img, alg)

store the color result using IndirectArrays

Since the dithered colorful image only uses very few distinctive values, it's intuitive to just store and process them in index image format (IndirectArray). That further compresses the memory but I'm not sure yet how this affects the performance.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.