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

Future overengineering? #19

Closed
lunacookies opened this issue Oct 25, 2020 · 5 comments · Fixed by #24
Closed

Future overengineering? #19

lunacookies opened this issue Oct 25, 2020 · 5 comments · Fixed by #24

Comments

@lunacookies
Copy link
Collaborator

lunacookies commented Oct 25, 2020

Hey @matklad, I’ve recently been thinking about some (possibly overengineered) changes I think might be nice for Pale Fire.

Generation

I recently fixed a bug that arose from not having "fontStyle": "" somewhere. After fixing it I thought it might be a good idea to add this to every single rule so that:

  1. the rules that do have it don’t look strange (because there’s no reason you should look at "fontStyle": "" and think about why it’s there – it really doesn’t matter)
  2. no such bugs can appear in the future

Rather than doing it manually, I thought it might be a good idea to use a generator program instead. I’ve done this previously, and it worked really well. Another benefit of generating the theme is that colours can be synced between semantic and TextMate rules. The current approach of ‘hopefully remember to update both the semantic and TextMate colour’ has led to at least one bug.

Colour palette harmonisation using fancy colour spaces

The Zenburn palette that Pale Fire uses has generated all its different colour variations through the RGB colour space. For example, here are zenburn-green and zenburn-green+1:

Screen Shot 2020-10-25 at 11 12 26 pm

Screen Shot 2020-10-25 at 11 12 32 pm

Notice how the H and S values stay the same – only V changes.

The problem with this is that the movement along any one of these three dimensions does not appear smooth to humans. For example, take a look at the hue dimension (the colourful bar) – although this is only meant to be changing the hue, the blues look much darker than everything else, and the yellow looks less saturated than the other colours. To us, it appears as if the hue dimension is also affecting the other two dimensions. This page has some good examples of these kinds of problems arising in gradients.

Although physically the change in the photons is linear, it isn’t perceived as such by our visual system. This is something which HSV (along with HSL and CMYK) has inherited from sRGB.

This ‘perceptual non-uniformity’ can be fixed by using a colour space that has been deliberately warped so that it appears smooth to us, such as CIELAB or CIECAM02. As far as I can tell, CIECAM02 is the state of the art in this field, but I’m just someone who’s done a bit of Googling and don’t know what any of the equations on that page mean :)

It’s hard to say whether this change would actually make the theme appear more harmonious without actually trying it, but I think it might be worth a shot.

Combining the two

If the theme is being generated, why not generate the colour palette in the same program? This could allow changing, e.g. contrast by modifying the value of a single constant and running the generator. This idea is really appealing to me, as it allows changing the overall colours of the theme without having to manually change each value. Additionally, it would also allow fixing #10 by generating a second theme with increased contrast.

I know these changes are large and stray further from the original intention of this theme (port Emacs Zenburn to VS Code), which is why I wanted to ask you before actually trying to implement anything.

@lunacookies lunacookies mentioned this issue Oct 26, 2020
3 tasks
@matklad
Copy link
Owner

matklad commented Jan 5, 2021

It’s hard to say whether this change would actually make the theme appear more harmonious without actually trying it, but I think it might be worth a shot.

+1 to just trying this out. Might be a good idea to raise the issue in the original zenburn repo as well? To be clear, I now nothing about the domain :)

@lunacookies
Copy link
Collaborator Author

Since writing that comment I have come across this paper, which lists some improvements to the CAM16 colour space. The author has written a Python library for dealing with colour spaces which contains an implementation of this improved version of CAM16. I’ve been thinking about porting it to Rust for a while, and just opened an issue in a Rust colour space library to see if I (or anyone else) can implement it.

I might try porting it later today, and see how far I get :)

If I can make it work, I’ll probably write a few libraries that we can use in Pale Fire:

  • gradient library that builds on top of the Rust colour space library from earlier
  • VS Code theme library that deals with generating JSON and takes RGB colours as input

@lunacookies
Copy link
Collaborator Author

Might be a good idea to raise the issue in the original zenburn repo as well?

I think there’s a good chance it’ll be rejected, since Zenburn has been around for so long and has spawned so many other colour schemes. I bet a lot of people would be upset if the colour scheme they’ve used for the past decade suddenly changes its palette.

@lunacookies
Copy link
Collaborator Author

I assumed that CAM16 is the ‘best’ colour space currently available. As it turns out, CAM16 has some problems of its own. I found this recent article in which some of its and other colour spaces’ flaws are pointed out. The article then describes a new colour space, Oklab, which is very simple to implement. Here’s a C++ implementation from the article:

struct Lab {float L; float a; float b;};
struct RGB {float r; float g; float b;};

Lab linear_srgb_to_oklab(RGB c) 
{
    float l = 0.4121656120f * c.r + 0.5362752080f * c.g + 0.0514575653f * c.b;
    float m = 0.2118591070f * c.r + 0.6807189584f * c.g + 0.1074065790f * c.b;
    float s = 0.0883097947f * c.r + 0.2818474174f * c.g + 0.6302613616f * c.b;

    float l_ = cbrtf(l);
    float m_ = cbrtf(m);
    float s_ = cbrtf(s);

    return {
        0.2104542553f*l_ + 0.7936177850f*m_ - 0.0040720468f*s_,
        1.9779984951f*l_ - 2.4285922050f*m_ + 0.4505937099f*s_,
        0.0259040371f*l_ + 0.7827717662f*m_ - 0.8086757660f*s_,
    };
}

RGB oklab_to_linear_srgb(Lab c) 
{
    float l_ = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b;
    float m_ = c.L - 0.1055613458f * c.a - 0.0638541728f * c.b;
    float s_ = c.L - 0.0894841775f * c.a - 1.2914855480f * c.b;

    float l = l_*l_*l_;
    float m = m_*m_*m_;
    float s = s_*s_*s_;

    return {
        + 4.0767245293f*l - 3.3072168827f*m + 0.2307590544f*s,
        - 1.2681437731f*l + 2.6093323231f*m - 0.3411344290f*s,
        - 0.0041119885f*l - 0.7034763098f*m + 1.7068625689f*s,
    };
}

Compare this with the pages and pages of mathematics needed for CAM16:

Screen Shot 2021-01-06 at 10 54 31 am

Although I don’t understand the why of either the Oklab implementation or the CAM16 implementation, I’m much more OK with that in Oklab‘s case due to its simplicity. Furthermore, it looks like Oklab performs better on blending (one of the primary uses of a colour space in a theme) than CAM16:

Screen Shot 2021-01-06 at 10 58 14 am

Note how CAM16 starts to dull and become slightly purple in the middle of the gradient, while Oklab stays consistent.

@lunacookies
Copy link
Collaborator Author

Status update: I’ve implemented Oklab support in Ogeon/palette#200, but it’s pretty hacky. I found the codebase difficult to work with, and covers many usecases that aren’t relevant to Pale Fire. I then considered adding Oklab to ChevyRay/color_space, but found that the code wasn’t formatted with rustfmt and had a number of outstanding Clippy problems. There’s an open pull request from November that fixes this, but it doesn’t look like it’ll be merged anytime soon.

Because of this, I’ve created yet another colour space crate. It’s super simple and supports the bare minimum colour spaces needed for Pale Fire.

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.

2 participants