This is super cool, great work. Is there a video or demo of the 3D point cloud "gaussian splat" like experiments?
HN user
mattdesl
generative artist
https://mattdesl.com
For those just learning about perceptual colour spaces, I’d recommend exploring OKLab which is simpler to implement and overcomes some of the problems of CIELab.
Still going through the article but loving all the detail and interactive components! Nice writeup.
PS: worth mentioning the RGB to CMYK function credited to me is not my original work, I believe I got it off stack overflow or similar many years ago. A more robust way of doing this transformation would be with a color management system and profile, as it happens I’ve done a bit of work on that! [1] Used this here [2].
Transforming with ICC profile will give you a result that might be closer to how a screen printer would turn your digital image into a four colour print, but more advanced screen printing workflows these days tend to use “rip” software that handles many layers (eg: 12 colors instead of 4) and stochastic screening [3] which produces quite different results than what most halftone shaders are doing.
[1] https://github.com/mattdesl/lcms-wasm
Nice work, the outputs look ethereal and quite beautiful. For some related work using genetic algorithms and evolution strategies, see[1].
Sketch synthesis is an area I'm pretty interested in lately; I'm currently exploring similar things with CLIP to guide fitness, natural evolution strategy to optimize the rendered results, and using an implicit neural representation to represent pen plotter paths (rather than a series of explicit curves/strokes).[2]
Amazing as always, Bruno is a wizard with ThreeJS.
There’s a surprising amount of stutter and lag on iOS, evident after the loading bar completes and the app freezes for 30 sec. Also during gameplay, quite a bit of stuttering. My guess is GPU texture uploads or shader compilations. Otherwise it was buttery smooth.
A similar thing happens when you search “Canada eTA” — a $7 (required) entry visa the government typically issues instantly. But on Google, several sponsored sites appear above the gov site, and charge $100+ for the same service but slower, and they do god knows what with your passport details and personal data.
There are tons of other examples like this. It’s very easy to get tricked by Google ads if you aren’t suspecting a scam.
Nice post. OKLCH is quite handy but for writing colors in CSS I hope eventually we’ll get some form of OKHSL/OKHSV[1] so users don’t need to worry about gamut boundaries.
A lot of it comes down to which pens you happen to have - I’ve had some success with Sakura gelly rolls for white, and also more recently have been enjoying sharpie creative acrylic markers which has a moderately opaque white ink. I’ve also had some really frustrating experiences with some other pens and instruments!
Yes, it's a matmul; many color models just boil down to simple math. For example, look at Li and Luo's 2024 "simple color appearance model"[1], which is very similar to OKLab (just matmul!), and created for many of the same reasons (just an approximation!). Like OKLab, it also improves upon CAM16-UCS hue linearity issues in blue. Ironically, Luo was one of the authors who proposed CAM16-UCS in 2017. And, although it certainly improves upon CAM16-UCS for many applications, I'm not yet convinced it is superior to OKLab (you can see my implementation here: [2]).
And I think you might be mis-remembering Ottosson's original blog post; he demonstrates a gradient between white and blue, not blue and yellow.
[1] https://opg.optica.org/oe/fulltext.cfm?uri=oe-32-3-3100
[2] https://github.com/texel-org/color/blob/main/test/spaces/sim...
Obviously; but this doesn’t suggest that OKLab is not a perceptually uniform color space.
There is no “one true” UCS model - all of these are just approximations of various perception and color matching studies, and at some point CAM16-UCS will probably be made obsolete as well.
Oklab is not perceptually uniform
By what metric? If the target is parity with CAM16-UCS, OKLab comes closer than many color spaces also designed to be perceptually uniform.
It does a pretty good job at emulating CAM16 with a fraction of the parameters, computational complexity, and processing; it’s no wonder it was adopted by CSS.
I don’t know what you mean by “not being linked to any perceptual color space” - it is derived from CAM16 & CIEDE2000, pretty similar in ethos to other spaces like ITP and the more recently published sUCS.
There’s also tons of discussion on w3c GitHub about OKLab, and it’s evolved in many ways since the original blog post such as improved matrices, new lightness estimate and OKHSV/OKHSL, and very useful cusp & gamut approximations.
I have a hard time seeing how it’s a nightmare in practice!
I’ve done some color quantization tests with HyAB and OKLab on this same image. A couple notes:
- what works well for this image might not work well for other images! I learned the hard way after lots of testing on this image, only to find things that did not generalize well.
- parametrizing the AB plane weight is pretty useful for color quantization; I’ve found some images will be best with more weight given to colour, and other images need more weight given to tone. OKLab creator suggests a factor of 2 in deltaEOK[1] but again this is something that should be adjustable IMHO..
- there’s another interesting and efficient color space (poorly named) sUCS and sCAM[2] that boasts impressive results in their paper for tasks like this. Although I’ve found it not much better for my needs than OKLab in my brief tests[3] (and note, both color spaces are derived using CIEDE2000)
[1] https://github.com/color-js/color.js/blob/9d812464aa318a9b47...
[2] https://opg.optica.org/oe/fulltext.cfm?uri=oe-32-3-3100&id=5...
Surely this would be even faster and potentially better with OKLab? Especially in the context of CIELab based distance metrics like CIEDE2000 which are a bit heavy.
My own gripe with box cutting is that perceptual color spaces tend not to have cube shaped volumes. But they are very fast algorithms.
I think the techniques in “Weight Agnostic Neural Networks” should be applicable here, too. It uses a variant of NEAT I believe. This would allow for learning the topology and wiring rather than just gates. But, in practice it is probably pretty slow, and may not be all that different than a pruned and optimized DLGN..
The full title of the paper is “Questioning Representational Optimism in Deep Learning: The Fractured Entangled Representation Hypothesis.”
There's also 'evolutionary strategy' algorithms that do not use the typical mutation and crossover, but instead use a population of candidates (search samples) to basically approximate the gradient landscape.
The author is working on a program synthesizer using interaction nets/calculus, which should be released soon. It sounds quite interesting:
I wonder how well BitNet (ternary weights) would work for this. It seems like a promising way forward for constrained hardware.
https://arxiv.org/abs/2310.11453
https://github.com/cpldcpu/BitNetMCU/blob/main/docs/document...
Developing an open source library that simulates pigment mixing in the browser, inspired by mixbox[1].
Really like this, nice work!
Something to note is that Color Theif (Quantize) is using median cut on RGB, it would be interesting to try and extract dominant color in OKLab instead.
I also love the idea of a genetic algorithm to find an ideal match for a given image; it should be possible to simulate radial gradients server & client side with webgpu, but probably overkill for such a simple task.
EDIT: Although it works for me in Chrome, it doesn't seem to work in Safari v16.1.
What are the practical implications of this kind of assembly language? Surely there’s more efficient means of describing 2D SDFs?
Fun exercise! I’ve been enjoying trying to find some new ways to approach the challenge. I managed to build a single string expression for the entire program, so it could be evaluated per-pixel in a shader, but it turns out the expression is too complex for WebGL & WebGPU and the shader fails to compile.
My next thought would be to evaluate the program at a low resolution to create a low res SDF texture for the shader to draw at a higher resolution. Some information will probably be lost, though.
Other than as an exercise, it's not clear why someone would write a massively parallel 2D renderer that needs a GPU. Modern GPUs are overkill for 2D. Now, 3D renderers, we need all the help we can get.
A ton of 2D applications could benefit from further GPU parallelization. Games, GUIs, blurs & effects, 2D animations, map apps, text and symbol rendering, data visualization...
Canvas2D in Chrome is already hardware accelerated, so most users get better performance and reduced load on main UI & CPU threads out of the box.
I think in the inputs I tested, I found Paeth to be the best for lower file sizes. I haven’t tested it extensively and to be honest, I should probably include some easier API for per-scanline filter selection rather than just a global choice. It should be possible right now with the lower level functions the library exposes, but not as easy.
I quite like the look of the blue noise dithering on this. Are you using just a texture as a mask, or something else?
It 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.
Last time I benchmarked png-tools, it was about 2-6x faster than fast-png for encoding.
https://github.com/mattdesl/png-tools
I’ve also added some other features like multi-threaded encoding, cancellation, encoding physical dimensions, color profiles, all of which is useful for encoding large print-ready PNGs on the client.
(No shade against fast-png, it’s a good library, but maybe not the fastest!)
Really nice work and great post.
Just to add, if you want smooth anti-aliased edges without the second super-sampling pass, you can use standard derivatives in your SDFs. Basically, replacing your step functions with 'aastep', e.g.
This is by Daniel Shiffman aka The Coding Train, source code:
Although the article is intimidating, the implementation appears pretty simple and flexible. Has anyone tried using this either in the Darktable app or in their own libraries? I might like to try porting it to JS to compare it for web based color picker GUIs.