Ditherpunk 2 – beyond 1-bit
makeworld.gq
makeworld.gq
Each color value (e.g. red) is represented by a value from zero to full intensity. It's easiest to think of it as a number between 0 and 1 in a linear space. You could use a floating point number for that, or a quantized/fixed point value. For example the 10-bit quantized value round(r_linear*1023) in the range 0 to 0x3ff.
8-bit RGB color components are "encoded" from their linear version with a transfer curve (aka gamma compression). For sRGB, the curve is a piecewise linear and exponential combination. A good overview is [1]. There are many different encodings, including sRGB, BT.601, BT.709, etc. Then there's "full range" vs. "video range"... it can get complex pretty quickly.
Because of gamma encoding, an 8-bit R_sRGB red value is not equal to round(r_linear*255). You have to first compress r_linear via the gamma curve, then quantize that 0..1 value to 8-bits. When going in reverse (expanding an 8-bit sRGB value to linear), you generally take R_sRGB/255 to produce a value in the 0..1 range and then use the inverse gamma curve to get the linear value. These computations can be done in floating point, fixed point, or using lookup tables.
The takeaway is that you can't represent 8-bit sRGB color components in linear with just 8-bits, without losing precision. You need at least 12-bits for linear sRGB and many implementations just go straight for 32-bit float values for simplicity.
These conversions are required whenever you combine (blend) pixels encoded into sRGB: so for each pixel operation X, you decode sRGB to linear, perform X, then encode back to sRGB. It's expensive! That's why GPUs offer texture formats that specify a gamma encoding like sRGB, so a pixel shader can blissfully work in linear color, with the conversions done for it in hardware as a pre- and post-shader operation. On the CPU? You have to do it all yourself...
Because of that, many software libraries don't bother with the proper gamma conversion and just compute everything in the logarithmic (gamma encoded) domain. And most of the time, it looks OK! But it really is just a "cheap" approximation -- sometimes it can look quite bad compared to the (proper) linear computation...
As far as I can tell, none of the Go standard library does linear blending; and all of the image formats are assumed to be sRGB encoded. There are some 3rd party packages like [3] that can do some of color management on a 16-bit linear image format (RGBA64 == 16bits/component RGBA).
The other thing the author might consider is revising the "Why?" footnote to the "Random Noise (grayscale)" section. What the author is actually doing there is just using a cheap approximation to a rounding function: round(x) ~= floor(x + 0.5). In general, doing a round like that introduces a bias [2]. That section can be summarized as: after every pixel operation, round and clamp back to the valid range.
[1] https://blog.johnnovak.net/2016/09/21/what-every-coder-shoul... [2] http://www.cplusplus.com/articles/1UCRko23/ [3] https://github.com/mandykoh/prism
The sRGB transfer function is piecewise linear + exponential but can be closely approximated by a simple exponential with \gamma ~= 2.2 [1]. Either way, the encoding between linear and non-linear is generally referred to as "gamma correction", even when using a transfer function that is not a simple exponential.
[1] https://en.wikipedia.org/wiki/SRGB#The_forward_transformatio...
I would understand a C++ library from the 1990s getting this wrong, or some toy project not bothering to implement colour management properly.
But to develop a new programming language for the 2010s to 2020s and blithely assume that images are always 8-bit sRGB is lazy beyond belief...
To put things in perspective, this would be roughly the same as making an application around the same time that simply assumes that the screen resolution is a fixed 1024x768 pixels.
You can see this visually in Surma's excellent blog post [1]: look for the gradient strips in the "Gamma" section.
I will update the library to use 16-bit color everywhere (0-65535), and update the blog post to note this.
As for the rounding, that's another great point, and thanks for the link. I will change the library and blog post to round to the even number on ties.
Edit: I've updated the blog post, I'd appreciate if you could check it out and let me know if I made any mistakes with the update.
[0]: https://github.com/lukevp/ESC-POS-.NET
[1]: https://user-images.githubusercontent.com/59696671/106924665...
That is a pretty sweet printer. Is it the Adafruit one?
[0]: https://www.amazon.com/Epson-TM-T20II-Direct-Thermal-Printer...
Will definitely try this library.
Also, it’s mentioned in the monochrome article, but Lucas Pope’s post on dithering in Obra Dinn is one of my favorite tech explorations [1].
[0] https://twitter.com/zackmichener/status/1359989840360525827?...
[1] https://forums.tigsource.com/index.php?PHPSESSID=dreqnu2pikn...
I've done some graphics programming but never specifically for halftone patterns, a quick search turns up this SO post with some examples:
https://stackoverflow.com/questions/1258047/algorithm-to-mak...
Thanks for the cool article! Coming from print and early graphics, I know dithering is all around us even if we don't notice it.
Thanks for the link, although the best I could find was this[1] old Java file that's completely uncommented. I was hoping for some textual documentation.
1: https://github.com/gabrielarchanjo/marvin-framework/blob/f50...
https://github.com/ClayFlannigan/halftone
https://github.com/philgyford/python-halftone
I think grandparent post is right, that you’re looking for the terms like halftone, halftoning, digital halftoning, binary halftoning, and the like. These are the terms that show up in papers:
https://www.sciencedirect.com/science/article/abs/pii/S10473...
Text discussion at Wikipedia: https://en.wikipedia.org/wiki/Halftone
Having worked in early digital press, I recall the term of art for a higher quality technique that came after “halftone screening” or “digital halftone” was “stochastic screening”:
https://en.wikipedia.org/wiki/Stochastic_screening
This prevented the tell-tale circle effect (moiré or rosette patterns) of traditional screening. We used it to print high quality magazine photography. It also worked on Mac Plus for rendering photography as a better dither for black and white dots of irregular or non-geometric photography — which brings us full circle to dithering.
Using terms from Photoshop of the day, Pattern Dither uses a uniform pattern to represent levels of gray. Diffusion Dither uses a random pattern to represent levels of gray. Halftone Screen uses preset patterns (round, diamond, ellipse, line, square or cross) at frequencies and angles that can be varied as well.
That brought another term to mind, “stippling”:
https://github.com/joeedh/BlueNoiseStippling
Some more terms show up in this discussion on difference between halftoning and dithering:
https://photo.stackexchange.com/questions/5779/what-is-the-d...
---
ADDED:
See also (PDF links may take a very long time to load):
Grayscale Digital Halftoning using Optimization Techniques: http://ethesis.nitrkl.ac.in/7814/1/2015_Grayscale_Lalitha.pd...
Evaluation Tool for Halftoning Algorithms: https://www.idc.ac.il/en/schools/cs/research/documents/evalu...
I would tend to disagree from a theoretical perspective, as you are trying to conserve energy from the point of view of the observer.
Our perception is not linear. The dithered images in the article appear too bright to me, probably due to the Helmholtz-Kohlrausch effect. Likewise, green is a lot brighter than other colors to us, and we have a greater depth of perception, so it might be more important to propagate errors there?
Hmm, as far as I understand the math, your palette being pure colors shouldn’t really matter if the display calibration were perfect. Which I guess is an unrealistic expectation. :)
However, pure red will always appear brighter than non-pure red, even at the same luminance (~energy), which is what Helmholtz-Kohlrausch is about. IIRC pure red appears brighter than other pure colors as well at the same luminance level, but I am not sure how computer monitors interact will all of the above...
"...the cheap, and therefore indecorous, articles of daily consumption in modern industrial communities are commonly machine products; and the generic feature of the physiognomy of machine-made goods as compared with the hand-wrought article is their greater perfection in workmanship and greater accuracy in the detail execution of the design. Hence it comes about that the visible imperfections of the hand-wrought goods, being honorific, are accounted marks of superiority in point of beauty, or serviceability, or both. Hence has arisen that exaltation of the defective, of which John Ruskin and William Morris were such eager spokesmen in their time; and on this ground their propaganda of crudity and wasted effort has been taken up and carried forward since their time. And hence also the propaganda for a return to handicraft and household industry. So much of the work and speculations of this group of men as fairly comes under the characterisation here given would have been impossible at a time when the visibly more perfect goods were not the cheaper." – Thorstein Veblen, The Theory of the Leisure Class, 1899, p162
I got it from some else's personal site that's offline now, and definitely keeps the bots away. Some humans too.
And the "missing yellow" constraint is important, without it, the problem would be separable and therefore uninteresting.
https://dl.acm.org/doi/pdf/10.1145/127719.122727?casa_token=...
> (ordered dither): This algorithm is generally identified as a dispersed-dot technique [Limb 69], but if the intensity threshold levels are spatially concentrated it results in a clustered-dot dithering.
You want to know something cool? Here's a tech report from HP that describes a stenography approach based on halftoning: