GithubHelp home page GithubHelp logo

Add RYB colors about culori HOT 19 CLOSED

meodai avatar meodai commented on June 15, 2024
Add RYB colors

from culori.

Comments (19)

facelessuser avatar facelessuser commented on June 15, 2024 4

Whoops! I didn't enable RYB gamut in 3D demo I just updated it.

Certain algorithms do have limits. For instance, doing RYB in other gamuts will just give you messed up models, so it only works well with its own gamut, but doing other spaces in the RYB gamut is kind of fun.

Here's Jzazbz: https://facelessuser.github.io/coloraide/demos/3d_models.html?space=jzazbz&gamut=ryb&edges=false&aspect=false&ortho=false
Screenshot 2023-08-06 at 4 27 19 PM

EDIT: Some spaces will refuse to render in other gamuts, for instance, HPLuv I only renders in its own gamut. I should probably do that for RYB as well as it is smaller than the other gamuts, so you can't resize the gamut shell to fit it.

from culori.

facelessuser avatar facelessuser commented on June 15, 2024 3

RYB is therefore not a candidate for defining as color space in Culori. Similar issues make it challenging to add pigment-decomposition models inspired by Kubelka-Munk theory (e.g. mixbox) as color spaces.

Just a note on this comment, the RYB space as Gosset and Chen describe can be translated both in the forward and reverse direction, even with biasing applied. Using Newton's method you can successfully implement a forward and reverse transform for all colors in gamut. If extrapolating outside of the gamut, things get a little dicey, but in gamut, it actually works quite well.

>>> c1 = Color.random('ryb')
>>> c1
color(--ryb 0.98577 0.35263 0.92582 / 1)
>>> c1.convert('srgb')
color(srgb 0.43535 0.05045 0.30214 / 1)
>>> c1.convert('srgb').convert('ryb')
color(--ryb 0.98577 0.35263 0.92582 / 1)

I think as a color space, biasing should not be the default as it just clumps all the colors toward the corners, but the approach works for both.

>>> c2 = Color.random('ryb-biased')
>>> c2
color(--ryb-biased 0.13099 0.43229 0.29285 / 1)
>>> c2.convert('srgb')
color(srgb 0.81598 0.85969 0.54394 / 1)
>>> c2.convert('srgb').convert('ryb-biased')
color(--ryb-biased 0.13099 0.43229 0.29285 / 1)

A reverse transform may not work for any 8 colors either. It is easy to turn the 3D space inside out. You can see how sRGB looks in the Gosset and Chen RYB color space. It gets quite twisted, but it still works.

Screenshot 2023-08-05 at 8 17 53 AM

Through testing, I found that picking any colors for the 8 RYB corners may yield degraded round trip results near the limits in some cases, but for the Gosset and Chen colors, it works quite well.

The actual implementation for inverse trilinear interpolation is straightforward, but no, I didn't come up with this part myself. Reference code for both reverse bilinear and trilinear interpolation is found here: https://stackoverflow.com/a/18332009/3609487. This approach seems to use this method of trilinear interpolation to calculate the inverse (http://paulbourke.net/miscellaneous/interpolation/) as it is easier to target and calculate the individual components in this way. As for the biasing, there was no reference, but I was able to just code up a simple Newton's method inverse as the easing function is simple to calculate the derivative from.

You do need a fairly sound matrix inverse function as you can sometimes get zeros at the pivot points, but that does not necessarily mean the matrix is not invertible, so you need to shuffle the rows around when reducing them. If you do get a Jacobian matrix that is not invertible (only ever occurs outside the gamut), you can probably just bail, or that is what I did. I do not guarantee round trips outside of the RYB gamut.

This may or may not be more work than this library cares to do for RYB, but I had been working on this off and on for a bit, and when I saw this, I figured I'd share. It helped motivate me to actually clean up the work and get it out into the wild.

from culori.

danburzo avatar danburzo commented on June 15, 2024 3

Just for fun, a little RYB color picker: https://danburzo.ro/demos/color/ryb.html

from culori.

danburzo avatar danburzo commented on June 15, 2024 2

Added blerp() and trilerp() functions to [email protected].

With this in place, the RYB to RGB method looks like this:

import { trilerp } from 'culori';

const RYB_CUBE = [
	{ mode: 'rgb', r: 1, g: 1, b: 1 }, // white
	{ mode: 'rgb', r: 1, g: 0, b: 0 }, // red
	{ mode: 'rgb', r: 1, g: 1, b: 0 }, // yellow
	{ mode: 'rgb', r: 1, g: 0.5, b: 0 }, // orange
	{ mode: 'rgb', r: 0.163, g: 0.373, b: 0.6 }, // blue
	{ mode: 'rgb', r: 0.5, g: 0, b: 0.5 }, // violet
	{ mode: 'rgb', r: 0, g: 0.66, b: 0.2 }, // green
	{ mode: 'rgb', r: 0.2, g: 0.094, b: 0 } // black
];

function ryb2rgb(coords) {
	const biased_coords = coords.map(t => t * t * (3 - 2 * t));
	return {
		mode: 'rgb',
		r: trilerp(...RYB_CUBE.map(it => it.r), ...biased_coords),
		g: trilerp(...RYB_CUBE.map(it => it.g), ...biased_coords),
		b: trilerp(...RYB_CUBE.map(it => it.b), ...biased_coords)
	};
}

ryb2rgb([1, 0.5, 0.25]);
/*
{
	mode: 'rgb',
	r: 0.8984375,
	g: 0.21828124999999998,
	b: 0.0390625
}
*/

By changing the colors at the corner of the cube you can perform a trilinear interpolation between any 8 colors.

from culori.

facelessuser avatar facelessuser commented on June 15, 2024 2

Update made, thanks for the suggestion!

from culori.

danburzo avatar danburzo commented on June 15, 2024 1

I’m thinking the following may be useful additions to the Culori API:

  • blerp (bilinear interpolation) and trilerp (trilinear interpolation) as low-level primitives
  • maybe mix(colors)(t), mix2d(colors)(tx, ty) and mix3d(colors)(tx, ty, tz) helpers

from culori.

meodai avatar meodai commented on June 15, 2024 1

@danburzo Itten would be proud :D https://codepen.io/meodai/pen/NWELdGW/7cbdbbcc1d1dd0ae42527341eef1c23c?editors=0010 PS: looks like the teal color is getting lost in the "mix" somehow.

from culori.

meodai avatar meodai commented on June 15, 2024 1

haha I should not late-night code :D thanks

from culori.

danburzo avatar danburzo commented on June 15, 2024 1

That’s an extremely useful tool for visualizing gamuts, thanks for it!

One small note: I believe the X rendered in Y gamut should actually be Y gamut rendered in X space? The image below looks like the gamut of RYB plotted in sRGB space:

The gamut of the RYB color space plotted in sRGB color space

from culori.

danburzo avatar danburzo commented on June 15, 2024

The paper referenced here proposes an "intuitive color mixing" technique: use an RYB (Red-Yellow-Blue) subtractive model inspired by how physical pigments work.

By expressing colors as RYB, you can perform simple compositing, presumably using the multiply operation ((b, s) => b * s) to achieve an intuitive result. Afterwards, to display the result, you perform the RYB -> RGB operation:

  1. given the eight colors in the corners of the RYB cube expressed as RGB, for each RGB component:
  2. perform trilinear interpolation of the eight corner values, made non-linear with the easing function t => t * t * (3 - 2 * t).

As defined like this, it’s not clear how much of the corresponding RGB space can a RYB space contain, and in any case it’s defined as a one-way conversion, with no formula provided for decomposing RGB into the RYB components.

RYB is therefore not a candidate for defining as color space in Culori. Similar issues make it challenging to add pigment-decomposition models inspired by Kubelka-Munk theory (e.g. mixbox) as color spaces.

Let’s see, however, if it makes sense to add some helper functions to Culori to make RYB to RGB conversion more pleasant to implement.

from culori.

meodai avatar meodai commented on June 15, 2024

@danburzo thanks so much!

from culori.

danburzo avatar danburzo commented on June 15, 2024

Yeah, the code is a crude approximation of real-world color mixing, but it looks pretty nice!

from culori.

meodai avatar meodai commented on June 15, 2024

@danburzo I think the code in your example inverts white & black (they need to be swapped in RYB_CUBE)

from culori.

danburzo avatar danburzo commented on June 15, 2024

RYB is a subtractive color model, so [1, 1, 1] produces black, not white (as with RGB/HSL).

from culori.

danburzo avatar danburzo commented on June 15, 2024

I appreciate you taking the time to write this up, Isaac! Very useful additions to the thread.

Bourke’s variant of trilerp is much nicer to look at. You’re right though that the matrix toolkit involved in performing the inverse trilinear interpolation is too much for including in the library.

Playing some more with RYB, I noticed the easing function is in fact Smoothstep, so the code needed to add a convertXtoY-style function to Culori is even more compact, since it’s already part of the API:

import { trilerp, easingSmoothstep } from 'culori';

function convertRybToRgb(ryb) {
  // Omit the call to easingSmoothstep() to remove bias.
  const r = easingSmoothstep(ryb.r);
  const y = easingSmoothstep(ryb.y);
  const b = easingSmoothstep(ryb.b);
	return {
		mode: 'rgb',
		r: trilerp(1, 1, 1, 1, .163, .5, 0, .2, r, y, b),
		g: trilerp(1, 0, 1, .5, .373, 0, .66, .094, r, y, b),
		b: trilerp(1, 0, 0, 0, .6, .5, .2, 0, r, y, b)
	};
}

// Usage
convertRybToRgb({ mode: 'ryb', r:, y:, b: });

It’s also nice that Smoothstep has an analytical inverse, to help in the RGB to RYB conversion:

function easingInverseSmoothstep(t) {
  return 0.5 - Math.sin(Math.asin(1 - 2 * t) / 3);
}

Is the code to produce 3D gamut visualizations available somewhere I could take a closer look?

from culori.

facelessuser avatar facelessuser commented on June 15, 2024

3D demos and others found here https://facelessuser.github.io/coloraide/demos/

from culori.

facelessuser avatar facelessuser commented on June 15, 2024

3D demo is also available in the repo to do locally, but the browser demo is more accessible and doesn't require me to explain setup :).

from culori.

facelessuser avatar facelessuser commented on June 15, 2024

One small note: I believe the X rendered in Y gamut should actually be Y gamut rendered in X space? The image below looks like the gamut of RYB plotted in sRGB space:

I do think it makes a bit more sense. I'll probably make that adjustment.

from culori.

facelessuser avatar facelessuser commented on June 15, 2024

That's actually the phrasing I use in my docs 🤦🏻 .

from culori.

Related Issues (20)

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.