1,653 karma · joined March 28, 2014
https://mattdesl.com
It may be of interest to some game/graphics devs here...
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
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]
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.
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.
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...
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.
By what metric? If the target is parity with CAM16-UCS, OKLab comes closer than many color spaces also designed to be perceptually uniform.
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!
- 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...
My own gripe with box cutting is that perceptual color spaces tend not to have cube shaped volumes. But they are very fast algorithms.
https://arxiv.org/abs/2310.11453
https://github.com/cpldcpu/BitNetMCU/blob/main/docs/document...
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.
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.
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.
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.
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!)
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.