Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add interpolation support for OkLrCH #505

Open
KeyboardDanni opened this issue Apr 20, 2024 · 7 comments · May be fixed by #511
Open

Add interpolation support for OkLrCH #505

KeyboardDanni opened this issue Apr 20, 2024 · 7 comments · May be fixed by #511

Comments

@KeyboardDanni
Copy link

Hi, I am working on an app to generate shades of colors for game artwork. During development I noticed that when using steps() to lerp over OkLCH, the result is too dark at lower luminance:

2024-04-20 15_39_52-Palettejuicer

It looks like I'm not the only one who noticed this issue.

It would be nice if OkLrCH were an option for interpolation, as I want to generate shades of color with roughly linear perceptual luminance, and the Lr function mentioned here seems to give results close to that of LAB. The code for the function already seems to be in the library via the OkHSL colorspace. Would it be possible to repurpose this as OkLrCH for better luminance transitions in OkLCH?

@facelessuser
Copy link
Collaborator

I don't think OkLrCh has to be added to solve this problem. If you want shades using Lab lightness, just calculate lab lightness. This isn't to argue against adding OkLrCh, but more stating that it isn't expressly needed if all you want to do is create tints and shades.

const shades = (color, steps) => {
    const oklch = color.to('oklch');
    const lab1 = color.to('lab').set({'a': 0, 'b': 0});
    const lab2 = lab1.clone().set('l', 0);
    return lab1.steps(lab2, {space: 'lab', steps: steps}).map(lab => {
        return oklch.clone()
                    .set('l', lab.to('oklab').l)
                    .toGamut('srgb');
    });
};

// Shades using lab lightness
shades(new Color("#777777"), 8);
// Shades using oklab lightness
new Color("#777777").steps('black', {steps: 8})
Screenshot 2024-04-20 at 3 23 10 PM

@KeyboardDanni
Copy link
Author

In my case I'm doing more than just varying luminance. A big part of designing colors for pixel art is also varying hue and saturation/chroma. The goal of this project is to provide a sandbox for mixing and interpolating colors across a variety of colorspaces and channels on a 2D grid.

LAB/LCH has some weaknesses here where the hue doesn't stay consistent, while OkLCH produces a better result. I have considered writing my own function to handle interpolation, particularly mapping L to Lr and back via toe/toeInv but am unsure about the ramifications of doing this myself (I'm still learning about color science).

@facelessuser
Copy link
Collaborator

Keep in mind, in my example, we are not relying on Lab or LCh's hue preservation, we are only interpolating its lightness. We could apply this to interpolating between any two colors as well.

Here we interpolate the lightness within Lab for better distribution of lightness and we interpolate the chroma and hue within OkLCh. We return the lightness converted back to OkLCh and the chroma and hue as interpolated in OKLch.

const interpolate = (color1, color2, steps) => {
    const oklch1 = color1.to('oklch');
    const oklch2 = color2.to('oklch');
    const lab1 = color1.to('lab').set({'a': 0, 'b': 0});
    const lab2 = color2.to('lab').set({'a': 0, 'b': 0});
    const colors = oklch1.steps(oklch2, {space: 'oklch', steps: steps});
    return lab1.steps(lab2, {space: 'lab', steps: steps}).map((lab, i) => {
        return colors[i]
                    .set('l', lab.to('oklab').l);
    });
};

// Shades using lab lightness
interpolate(new Color("red"), new Color("green"), 8);
Screenshot 2024-04-20 at 5 18 50 PM

You are welcome to use the toe/toeInv to convert OkLCh lightness and interpolate just that channel, then convert it back to OKLCh's normal lightness as well. The toe/toeInv sets middle gray equivalent to Lab's middle gray, but it does not completely give you the same lightness distribution as Lab, but if that is sufficient for your needs, that would work. Alternatively, you apply the same logic above, but use Okhsl which applies that toe/toeinv:

const interpolate = (color1, color2, steps) => {
    const oklch1 = color1.to('oklch');
    const oklch2 = color2.to('oklch');
    const okhsl1 = color1.to('okhsl').set({'h': 0, 's': 0});
    const okhsl2 = color2.to('okhsl').set({'h': 0, 's': 0});
    const colors = oklch1.steps(oklch2, {space: 'oklch', steps: steps});
    return okhsl1.steps(okhsl2, {space: 'okhsl', steps: steps}).map((lab, i) => {
        return colors[i].set('l', lab.to('oklab').l);
    });
};

// Shades using lab lightness
interpolate(new Color("red"), new Color("green"), 8);
Screenshot 2024-04-20 at 5 21 54 PM

I don't think Okhsl and Okhsv are formally in the most recent release, but they are on the main branch waiting to be released.

@svgeesus
Copy link
Member

svgeesus commented May 1, 2024

I don't think OkLrCh has to be added to solve this problem. If you want shades using Lab lightness, just calculate lab lightness.

True, but it would be useful to add it as an option; we have so many now.

We should also add some guidance to the start of "supported color space" because it has now become a bit overwhelming to people just getting started.

@facelessuser
Copy link
Collaborator

It's easy enough to add the space, but are we wanting to add both the Oklab and OkLCH space with this adjusted lightness?

@svgeesus
Copy link
Member

svgeesus commented May 1, 2024

That would seem logical (I increasingly see the a,b spaces as just an intermediate stage to the polar ones)

facelessuser added a commit to facelessuser/color.js that referenced this issue May 2, 2024
@facelessuser facelessuser linked a pull request May 2, 2024 that will close this issue
@facelessuser
Copy link
Collaborator

PR is up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants