facelessuser / coloraide

A library to aid in using colors
https://facelessuser.github.io/coloraide
MIT License
194 stars 12 forks source link

RYB Support #342

Closed facelessuser closed 1 year ago

facelessuser commented 1 year ago

I've been working on this off and on, the original issue is here: https://github.com/facelessuser/coloraide-extras/issues/12.

I've basically been evaluating different RYB approaches and trying to see which ones are better.

  1. The easiest approach is something like what is described in Junichi Sugita's and Tokiichiro Takahashi's paper . This basically just takes the sRGB color space and compresses blue and such to align yellow as a primary color. It has the advantage that it encompasses the entire gamut of sRGB. While in their evaluation they claim decent RYB mixing for watercolors and such, the color space creates some odd discontinuities. This can be seen in the 3D model.

    Screenshot 2023-07-29 at 11 04 20 PM
  2. The other approach is the Gosset and Chen approach. They provide a single-direction algorithm. This approach uses trilinear interpolation which is easy to implement and actually creates a color space with pretty consistent smooth transitions in the space. It does have a smaller gamut (things like cyan are not present).

    The round-trip translation is a problem, but I've actually been able to solve this. Digging around, I was able to find an inverse trilinear interpolation approach using the Gauss–Newton algorithm. This works well for colors within the gamut, especially if you tune the initial guesses for problematic cases. I assume the guesses could be tuned to better handle colors outside the gamut as well, but that is less of a concern. We also can reverse their "biasing" using Newton's method as well, tuning the guesses helps improve good round-tripping. Employing these methods, we are able to convert back and forth and generate the 3D model of the space.

    Screenshot 2023-07-29 at 11 32 04 PM

    Another approach they employ is using a cubic easing function to bias the colors towards the primaries to produce a non-linear interpolation of the colors. This is done to " limit the use of ambiguous colors in [their] implementation". More specifically they state:

    Biasing in this manner will ensure that the viewer will encounter a more limited number of easily identiable colors, but still llows for smooth transitions between colors.

    I actually find this biasing to have downsides. If you are trying to generate monochromatic steps, you get too little variance as you get close to the primary and tertiary colors. The good news is that we do not have to bias if we do not want to. Unless you specifically require further limiting the colors in the space, I would not generally recommend this. Basically, the colors are more localized. The blue is more tightly bunched in its corner, etc.

    Screenshot 2023-07-29 at 11 33 41 PM

    They select a number of colors they claim are close-ish to those used by Itten. The thing is that there are various color sets by Itten. From different revisions of his book, the color wheel uses different variants of colors. The colors chosen in the paper have some pretty washed-out blues, etc. There are variants of Ittens' supposed colors that have more vibrant colors, and others that are less so. At least for the images here, we are using a pretty common, documented set of Itten colors, but which are the right or best, who knows. We actually, for testing purposes, implemented the Gosset and Chen colors and the Itten colors. Obviously, we'd pick a set to officially ship if we went this route.

    The last thing to note with this approach is that you get a more pigment-like mixing. The absence of colors gives you white, which makes sense, and mixing all colors together will give you a muddy brown, which also makes sense. Which makes sense. You can't really mix achromatic colors as easily. And if you want to darken a color with pure black or white, that can be tricky as that is not how the space works. I have experimented with an RYBK variant that actually allows you to mix colors in a normal pigment-like fashion, and then using K allows you to mix in pure black to darken.

facelessuser commented 1 year ago

Example of RYBK normal pigment mixing and mixing with pure black:

Screenshot 2023-07-30 at 12 05 58 AM
facelessuser commented 1 year ago

Moving forward, I think we will be using the Gosset and Chen algorithm. I think we are just going to ship a traditional RYB space. It will naturally have a more limited gamut because the primary colors cannot create all colors like CMY can. I think this is a more accurate approach than just squeezing the RGB gamut creating odd bands in the space.

The current hold up is related to some minor round trip values on the edge of the cube. In these few cases, the results are very close, but we'd like to be even closer. We'd like to ship these with the best precision we can. These Newton algorithms perform best depending on how good your initial guess is. The inverse trilinear guess is generally pretty good for almost any color within the RYB gamut, but near some corners we fall just a bit shorter than we'd like. We are talking about 0.9876 vs 1.0, so not huge. I'd like to see closer if possible.

We are experimenting with 3 variants. The original Gosset and Chen values. Values by Itten (though we have to guess a decent brown - all colors mixed together), and a vibrant variant using pure sRGB red, yellow, and blue. I don't know which one we'd ship, or if we'd ship multiple variants, but these are currently under evaluation.

facelessuser commented 1 year ago

I think we have something fairly reliable for all colors in the gamut. There is no guarantee that colors outside the RYB gamut will round trip though. As a matter of fact, I can probably guarantee there are colors that won't round trip at all.

facelessuser commented 1 year ago

I think we will ship the Gosset and Chen RYB model. We will provide it without the biasing, but for those who want to try it out as described in the paper, we will also provide the biased model just to be faithful to the paper.

It should be noted, that biasing doesn't give more accurate colors, on the contrary, it limits the colors to provide "less ambiguous" colors. This just means that the colors clump at the corners and you have less in between. They had a very specific use case they were targeting. Baising isn't helpful when representing this as a color space.

As for round tripping. Round tripping is quite good while inside the gamut. Outside, all bets are off, but this is fine. This isn't the only color space that has such behaviors.

Can any 8 colors be used to get different variants of RYB? Well, technically yes, but round tripping might now always be as good depending on the selected set. Different sets of colors can warp the interpolation cube more or less. Warp it too much, you may find the invert algorithm can't resolve some colors at the edge of the gamut. Let's take a look at the Gosset and Chen cube, first rendered in its own cube, and the sRGB color space rendered in the RYB gamut. Notice how warped the sRGB color space is within the RYB gamut.

Screenshot 2023-08-03 at 9 19 37 PM Screenshot 2023-08-03 at 9 20 09 PM

Even though the sRGB space is so warped in the RYB gamut, it round trips perfectly. But in our tests, we tried colors from Itten's color wheel (pictures on the internet), and it had occasional difficulties with resolving some corners of the cube at their maximum values.

The main reason I can see people wanting to tweak the cube is to have more vibrant colors. I am currently omitting such a variant, but I may include one if people requested it. If a more vibrant RYB is desired, the following values could be used which have great round tripping. Colors were sourced from here: https://www.w3schools.com/colors/colors_wheels.asp. We did not yet select a proper brown for the "black".

RYB_CUBE = [
    [1.0, 1.0, 1.0],                                                # White (c000)
    [0.996078431372549, 0.15294117647058825, 0.07058823529411765],  # Red (c100)
    [0.996078431372549, 0.996078431372549, 0.2],                    # Yellow (C010)
    [0.984313725490196, 0.6, 0.00784313725490196],                  # Orange (c110)
    [0.00784313725490196, 0.2784313725490196, 0.996078431372549],   # Blue (c001)
    [0.5254901960784314, 0.00392156862745098, 0.6862745098039216],  # Violet (c101)
    [0.4, 0.6901960784313725, 0.19607843137254902],                 # Green (c011)
    [0.0, 0.0, 0.0]                                                 # Black (c111)
]