rvanwijnen / spectral.js

Spectral.js is a paint like color mixing library utilizing the Kubelka-Munk theory.
https://onedayofcrypto.art/
MIT License
766 stars 18 forks source link

Ability to mix multiple colors in different proportions, not just 2 colors #6

Open rider85 opened 1 year ago

rider85 commented 1 year ago

it would be awesome if I could play around with mixing multiple (2+) colors. In fact I will try to implement it in the active project as soon as several colors are available.

Here are a few examples of implementation:

  1. Chroma-js: https://gka.github.io/chroma.js/#chroma-average
  2. Mixbox:
    //   MULTI-COLOR MIXING
    //
    //      var z1 = mixbox.rgbToLatent(rgb1);
    //      var z2 = mixbox.rgbToLatent(rgb2);
    //      var z3 = mixbox.rgbToLatent(rgb3);
    //
    //      var zMix = new Array(mixbox.LATENT_SIZE);
    //
    //      for (var i = 0; i < zMix.length; i++) { // mix:
    //          zMix[i] = (0.3*z1[i] +       // 30% of rgb1
    //                     0.6*z2[i] +       // 60% of rgb2
    //                     0.1*z3[i]);       // 10% of rgb3
    //      }
    //
    //      var rgbMix = mixbox.latentToRgb(zMix);
rvanwijnen commented 1 year ago

When I find the time I will look in to this

seguso commented 9 months ago

Hi, there, I have the same need: I need to translate this kind of thing:

mixbox_latent finalLat = 0.2 latUp + 0.3 latDown + 0.4 latLeft + 0.1 latRight;

The context would be

        vec3 up = ...
        vec3 down = ...
        vec3 left = ...
        vec3 right =...

        mixbox_latent latUp = mixbox_rgb_toLatent(up);
        mixbox_latent latDown = mixbox_rgb_toLatent(down);
        mixbox_latent latLeft = mixbox_rgb_toLatent(left);
        mixbox_latent latRight  = mixbox_rgb_toLatent(right);

                    mixbox_latent finalLat =  0.2  * latUp + 0.3  * latDown + 0.4  * latLeft + 0.1  * latRight;

                    vec3 colBg = mixbox_latent_to_rgb(finalLat);

Thanks!

rvanwijnen commented 9 months ago

It’s coming, I have this in my dev version. No ETA though as I’m working on some other features before 3.0 will be released.

eugene-khyst commented 3 weeks ago

Hi @rvanwijnen,

Thanks for this great library. It really helps to understand how to implement the Kubelka-Munk theory.

I'm trying to adapt the existing code to support mixing more than 2 colors. The most complicated part is to adapt linear_to_concentration function to support more that two values.

function linear_to_concentration(l1, l2, t) {
    let t1 = l1 * (1 - t) ** 2;
    let t2 = l2 * t ** 2;

    return t2 / (t1 + t2);
  }

In the comment to #10 and comment to #13 you wrote:

Concerning the concentration function (you are referring to this function right?): the reason it's not linearly interpolating is because the luminance is needed for determining the correct concentrations, this ensures the color is perceptually evenly distributed between 0 and 1 where 0 is the first color and 1 is the second color. This concentration function has been the hardest to figure out and took me a lot of time (although probably not unique I haven't seen this used anywhere else).

The Y value is not the Y tristimulus value but the CIE-XYZ Y value which is the luminance value. The luminance is used for an even mix distribution between 0 (color 1) and 1 (color 2). The concentration function creates a curve based on this luminance, you can't use a linear function. This is the most important function in spectral.js and can't be omitted.

How did you come up with this solution? $t^\prime=\frac{l_{2}t^{2}}{l_{1}\left(1-t\right)^{2}+l_{2}t^{2}}$

Maybe you can share some references?

The following function graph shows the difference between linear (y=x) and linear_to_concentration interpolation.

2 color interpolation function graph

2 color interpolation function graph

eugene-khyst commented 3 weeks ago

It looks like the most practical way to mix 3 or more colors is iteratively. Each next color have to be mixed with the mixture of previous two colors.

Mix first two colors, then mix the resulting mixture with the third color, then mix the resulting mixture with the fourth color and so on.

Mix 3 colors in 1:1:1 ratio:

const colorMix12 = mix(color1, color2, 1/(1+1))
const finalMix = mix(colorMix12, color3, 1/(1+1+1))

This approach can be extended on any number of colors.