6 ms·
Dithering in Colour
- Clamchop 2y agoThey may not want to imply that didder's linearized rabbit is wrong, but I'm comfortable saying so. It's not just a little dark, it's way dark, to the point of hiding detail. The linearized RGB palette is similarly awful. It clobbers a whole swath of colors, rendering them as nearly black. Purples are particularly brutalized. Yellows disappeared and became white. On my phone, the middle palette doesn't appear too bright to my eyes, either. Even the linearized gradient looks worse, . Maybe linear is not best for perceptual accuracy.
- obrhubr 2y agoThanks for your comment! I'm glad you're seeing the same thing :) I re-implemented the linearised dithering in python and got similar results. I checked and rechecked the colour profiles in GIMP, nothing... At this point I can only hope for an expert to appear and tell me what exactly I am doing wrong.
- mkesper 2y agoDid you try any of the OKlab color space implementations for calculating? https://bottosson.github.io/posts/oklab/ https://bottosson.github.io/posts/oklab/
- obrhubr 2y agoI’ll try them as soon as I get the chance, I have perceptual luminance implemented already. I’ll compare :)
- ack_complete 2y agoIt looks like the images on your blog might have gone through a non-gamma-corrected scaler. The linear images produced the program look correct, they do overall match the original image in Krita when scaled in scRGB linear 32-bit float.
- bazzargh 2y agoI got better results just dithering the rgb channels separately (so effectively an 8 colour palette, black, white, rgb, yellow, cyan, magenta). In p5js: var img var pixel var threshold var error = [0, 0, 0] var a0 function preload() { img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/4/44/Albrecht_D%C3%BCrer_-_Hare%2C_1502_-_Google_Art_Project.jpg/1920px-Albrecht_D%C3%BCrer_-_Hare%2C_1502_-_Google_Art_Project.jpg") } function setup() { // I'm just using a low discrepancy sequence for a quasirandom // dither and diffusing the error to the right, because it's // trivial to implement a0 = 1/sqrt(5) pixelDensity(2) createCanvas(400, 400); image(img, 0, 0, 400, 400) loadPixels() pixel = 0 threshold = 0 } function draw() { if (pixel > 400*400*16) { return } for (var i = 0; i < 2000; i++) { threshold = (threshold + a0)%1 for(var j=0; j< 3; j++) { var c = pixels[pixel + j] pixels[pixel + j] = c + error[j] > threshold * 255 ? 255 : 0 error[j] += c - pixels[pixel + j] } pixel += 4 } updatePixels() } Of course this isn't trying to pick the closest colour in the palette as you're doing - it's just trying to end up with the same intensity of rgb as the original image. It does make me wonder if you should be using the manhattan distance instead of euclidean, to get the errors to add correctly.
- nextts 2y ago> We have just committed a mortal sin of image processing. I didn’t notice it, you might not have noticed either, but colour-space enthusiasts will be knocking on your door shortly.
- Sesse__ 2y agoFor perceptual color difference, there are much better metrics than “distance in linear RGB”. CIE has some implementations of a metric called ΔE*, for instance. I don't know if they actually do well in dithering, though. My experience with dithering is that it actually works better in gamma space than trying to linearize anything, since the quantization is fundamentally after gamma.
- badmintonbaseba 2y agoI agree. I think the problem is a banal missing color transformation somewhere in the pipeline, like converting the palette and image to linear colorspace, doing the dithering there and mistakenly writing the linear color values instead of sRGB color values into the image. Others suggest that the error is using the wrong metric for choosing the closest color, but I disagree. That wouldn't such drastic systematic darkening like this, as the palette is probably still pretty dense in the RGB cube. Where the linearisation really matters is the arithmetic for the error diffusion, you definitely want to diffuse the error in a linear colorspace, and you are free to choose a good perceptual space for choosing the closest color at each pixel, but calculate the error in a linear space. Visual perception is weird. But when you squint your eyes to blur the image, you are definitely mixing in a linear colorspace, as that's physical mixing of light intensities before the light even reaches your retina. So you have to match that when diffusing the error. edit: It also doesn't help that most (all?) browsers do color mixing wrong when the images are scaled, so if you don't view the dithered images at 100% without DPI scaling than you might get significantly distorted colors due to that too. edit2: For comparison this is what imageworsener does: https://imgur.com/a/XmJKQnz https://imgur.com/a/XmJKQnz You really need to open the image in a viewer where each image pixel is exactly one device pixel large, otherwise the color arithmetic used for scaling by viewers is of variable quality (often very poor).
- contravariant 2y agoThe linearized gradient does look off, but not because it is linearized. It is simply wrong. The dithered gradient shouldn't be pure black halfway through.
- Const-me 2y agoYeah, every time I see articles about importance of linear color space for gradients, and see images there, I observe the opposite of what’s written in the text of these articles. Gradients in sRGB color space look better. I have a suspicion that might be because I usually buy designer-targeted wide gamut IPS displays. I also set up low brightness on them, e.g. right now I’m looking at BenQ PD2700U display with brightness 10/100 and contrast 50/100. However, sRGB color space was developed decades ago for CRT displays.
- obrhubr 2y agoYour monitor and your browser 100% affect the appearance. After calibrating your monitor, try opening the image in full resolution and take a few steps back. For me, viewing the images on my phone makes them look off.
- ack_complete 2y agoIt's not you or your display, linear color just isn't great for gradients: https://bottosson.github.io/posts/colorwrong/ https://bottosson.github.io/posts/colorwrong/ It can be better than sRGB for the color part of gradients but is awful for the brightness axis. The reason why linear color is so important for operations like blending light, antialiasing, and dithering is also why it is bad where perceptual uniformity is desired. sRGB isn't as good as Oklab or CIELAB perceptually, but a grayscale ramp rendered in linear color is so distorted towards white it's useless. Image formats also encode non-linear color for good reason. "Use linear color everywhere" is overly simplistic and bad advice.
- yapyap 2y agoDithering is so neat.
- oniony 2y agoPrint halftoning is interesting too https://photo.stackexchange.com/questions/5779/what-is-the-difference-between-halftoning-and-dithering https://photo.stackexchange.com/questions/5779/what-is-the-d...
- mattdesl 2y agoIt might be worth using a lightness estimate like OKLab, OKLrab[1], or CIE Lab instead of the RGB luminance weighting, as it should produce a more perceptually accurate result. The other issue with your code right now, is that it is using euclidean distance in RGB space to choose the nearest color, but it would be probably also more accurate to use a perceptual color difference metric, a very simple choice is euclidean distance on OKLab colors. I think dithering is a pretty interesting area of exploration, especially as a lot of the popular dithering algorithms are quite old and optimized for ancient compute requirements. It would be nice to see some dithering that isn't using 8-bits for errors, is based on perceptual accuracy, and perhaps uses something like a neural net to diffuse things in the best way possible. [1] https://bottosson.github.io/posts/colorpicker/ https://bottosson.github.io/posts/colorpicker/
- funks_ 2y agoIf you are interested in color dithering with different color difference metrics [1], I've implemented just that [2]. You can find an example comparing metrics in my docs [3]. [1]: https://juliagraphics.github.io/Colors.jl/stable/colordifferences/ https://juliagraphics.github.io/Colors.jl/stable/colordiffer... [2]: https://github.com/JuliaImages/DitherPunk.jl https://github.com/JuliaImages/DitherPunk.jl [3]: https://juliaimages.org/DitherPunk.jl/stable/#Dithering-with-custom-colors https://juliaimages.org/DitherPunk.jl/stable/#Dithering-with...
- rikroots 2y agoI moved my canvas library's reduce-palette filter over to OKLAB calculations a while back. The calculations are more computationally intensive, but worth the effort. https://scrawl-v8.rikweb.org.uk/demo/filters-027.html https://scrawl-v8.rikweb.org.uk/demo/filters-027.html
- mattdesl 2y agoI quite like the look of the blue noise dithering on this. Are you using just a texture as a mask, or something else?
- kaoD 2y ago> Dithering a black-to-white gradient will be wrong without linearising first. TBH both look wrong to me. If I squint, neither dithering patterns match the original gradient... but the non-linearized one looks the most similar. What could be causing this?
- hagbard_c 2y ago> What could be causing this? Hypercorrection, in this care over-linearisation.
- badmintonbaseba 2y agoApart from implementing it incorrect, an uncalibrated display could also cause this. Check out http://www.lagom.nl/lcd-test/gamma_calibration.php http://www.lagom.nl/lcd-test/gamma_calibration.php with DPI scaling turned off, at 100% zoom level (how browsers scale images are also horrible, so you want to avoid that). edit: Reading back, viewing the gradients also not at 100% zoom level could also itself cause the mismatch, because browsers just suck at image scaling.
- gus_massa 2y agoMac vs pc? They have a different default gamma and they may show a different gray level. (It bite me a long time ago. I made a gif that has the same RGB bacground than a webpage. In my pc it was fine, but in a mac they the border was very visible and the result horrible. My solution was to change the backgroung of the webpage from a RGB number to a 1 pixel gif with repetition or scale to fill the page.)
- TinkersW 2y agoI don't know where they got the idea you don't dither in srgb, the point of dithering is to map it to the nearest bit pattern with a random adjustment so that it could go either way(aside from artistic choice), you should dither in srgb if you are going to display it in srgb, which is probably why the "not linearized" version looks more accurate. See: Dithering should happen in sRGB https://www.shadertoy.com/view/NssBRX https://www.shadertoy.com/view/NssBRX
- 2y ago
- magicalhippo 2y agoI've always been curious to what degree, if any, color constancy[1] affects color dithering. Seems that at some level it should, though perhaps not directly at the pixel level due to the high frequency of the per-pixel differences, but maybe at the more coarse "averaged" level? One of those things I've wanted to explore but remains on my to-do list... [1]: https://en.wikipedia.org/wiki/Color_constancy https://en.wikipedia.org/wiki/Color_constancy
- spacejunkjim 2y agoWhen I saw this, I immediately had flashbacks to a little project I did for my CS course when I was an undergrad! We were all assigned a computer graphics algorithm and were tasked to build an animation explaining how it works. This was nearly eight years ago, but I managed to find it this morning and uploaded it to YouTube. Here was the resulting animation: https://youtu.be/FHrIQOWeerg https://youtu.be/FHrIQOWeerg I remember I used Processing to build it, and it took so long to animate as I had to export it frame-by-frame. Fun days!
- AndrewStephens 2y agoDithering is something of a lost art now that our displays can handle millions of colors in high definition, but it can be a striking artistic effect. If anyone thinks their websites are too colorful, I made a pure JavaScript web component to dither images on client in real time, taking into account the real pixel size of the current display. https://sheep.horse/2023/1/improved_web_component_for_pixel-accurate_atkinson.html https://sheep.horse/2023/1/improved_web_component_for_pixel-...
- hooli_gan 2y agoVery cool, but the image in the bottom of the page flickers when scrolling.
- tuyiown 2y agoDithering does that !
- AndrewStephens 2y agoMy code can (optionally, since it is often not useful) dither all the way down to the physical pixels of your display device for that really crisp, old-fashioned look. Most dithering projects on the web don't take this into account so look slightly soft around the edges of the pixels. The image at the bottom is an example. On some devices this interacts weirdly with the pattern of pixels or even the refresh rate when in motion due to scrolling.
- TJSomething 2y agoYou're likely using a TN screen, which basically does dithering over time.
- Aardwolf 2y agoI think dithering should still be considered, since a super high detailed game otherwise pretty engine that then has banding in the sky is pretty ugly. 32-bit RGBA can still have visible banding which dithering can fix. 256 brightness levels per channel isn't all that much when it comes to subtle variations in sky colors, the eye is more sensitive than that 12-bit per channel color might be enough to never have visible banding. Or dithering
- crazygringo 2y agoDid the author forget to finish the blog post? They show a single example of incorrect dithering, explain it's wrong, and then don't show a corrected version. There isn't a single example of proper color dithering. And they talk about the distance to the nearest color (RGB) but don't explain how to account for black or white -- how to trade off between accuracy of hue, brightness, and saturation, for example. This post doesn't explain at all how to actually dither in color. I don't understand why this is on the front page with over 50 votes.
- obrhubr 2y agoYou’re right it kind of isn’t finished… I had it done, then had an exchange with the author of didder and I’m still in the process of rewriting :)
- danybittel 2y agoError diffusion dithering is kind of old fashioned. It is a great algorithm where you only need to go though the image once, pixel by pixel. But it doesn't work well with todays hardware, especially GPUs. Would be fun to come up with new algorithms that are better parallelizable.
- pmarreck 2y agoDeterministic random value dithering, where the chance of being the dithered color or not is based on the percentage that the true value is that color?
- badmintonbaseba 2y agoBlue noise threshold map works really well on GPUs.
- criddell 2y agoThe dithering work Mark Ferrari did by hand on some of the old LucasFilm games was really impressive. https://www.superrune.com/tutorials/loom_ega.php https://www.superrune.com/tutorials/loom_ega.php
- alberth 2y agoA fun website to try out different Dithering algorithms and settings. https://doodad.dev/dither-me-this/ https://doodad.dev/dither-me-this/
- somewhereoutth 2y agoSee also FadeCandy by Micah: https://scanlime.org/2013/11/fadecandy-easier-tastier-and-more-creative-led-art/ https://scanlime.org/2013/11/fadecandy-easier-tastier-and-mo... > Firmware that uses unique dithering and color correction algorithms to raise the bar for quality while getting out of the way of your creativity.
- marcusestes 2y agoCool project. Sad README update: https://git.approximate.life/fadecandy/file/README.md.html https://git.approximate.life/fadecandy/file/README.md.html
- ggambetta 2y agoDidn't try error diffusion, but I had good results with Bayer for the ZX Spectrum Raytracer [0]. Bayer only ever looks at the pixel it's considering, doesn't do math beyond comparing a value to its threshold, it was surprisingly easy to implement, and looks nice. A great choice for ridiculously underpowered devices :) https://gabrielgambetta.com/zx-raytracer.html#fourth-iteration-light-just-the-one https://gabrielgambetta.com/zx-raytracer.html#fourth-iterati...
- cytocync 2y ago[dead]
- vintagedave 2y ago> If the linearised version looks wrong to you, try opening it on a larger monitor in it’s original size and check your gamma settings. It looks far too dark, and I’m viewing it on an iPad with a high DPI screen. I also strongly suspect I can’t change the gamma on this device, nor have I ever knowingly done so. Anyone know why it looks bad?
- svantana 2y agoIt's the same on my 2024 MBP. Looking at a gamma calibration image [1], I estimate the built-in screen to have a gamma of ~1.4 (where the stripes and filled areas have the same brightness), way below the standard 2.2. [1] http://www.lagom.nl/lcd-test/gamma_calibration.php http://www.lagom.nl/lcd-test/gamma_calibration.php
- omoikane 2y agoSee also: "Joel Yliluoma's arbitrary-palette positional dithering algorithm" https://bisqwit.iki.fi/story/howto/dither/jy/ https://bisqwit.iki.fi/story/howto/dither/jy/ This page focuses on ordered dithering, which tend to work better for animations compared to error diffusion based dithering schemes (like the linked article).
- JKCalhoun 2y agoJust a guess — perhaps Bill Atkinson dropped the two x 1/8 as a poor-man's contrast.
- nigeltao 2y agoMight be relevant to your interests: https://nigeltao.github.io/blog/2022/gamma-aware-ordered-dithering.html https://nigeltao.github.io/blog/2022/gamma-aware-ordered-dit...
- nikolay 2y agoI've done this in the early '90s, when I implemented all published algorithms back then into a Turbo Pascal program, which was allowing me to see full-color images on poor post-Soviet color displays - it wasn't just doing dithering, but also allowing you to pan and zoom, while switching algorithms. Give the limitations of the hardware, I had to implement the algorithms with a running buffer, optimized for panning, i.e. incrementally dithering the image. As some could guess, not a small part of these images was erotic. The program also was able to print on a matrix printer and I was improving it and had some test printouts in my bag. As I was in high school back. At one point some kids put a classroom on fire and they were doing an investigation, so, at one point they asked me to open my school bag and show them the contents. And, yes, I got into a big, big trouble. It was really tough to provide a meaningful explanation that I had only limited set of true-color images and, of course, the most popular were erotic given the post-Soviet interest in erotica and pornography. So, every time when I read something about dithering here on HN, it brings back my memory of being taken to the police station by two cops in front of the entire school for "distributing pornographic materials".
- eternityforest 2y agoI wonder what would happen if you 3D printed a dithering pattern, of either a picture or just some perlin noise, as a fuzzy skin. It seems like it could have a really nice effect.