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

Populate first draft of color crate #3

Merged
merged 3 commits into from
Nov 4, 2024
Merged

Populate first draft of color crate #3

merged 3 commits into from
Nov 4, 2024

Conversation

raphlinus
Copy link
Contributor

This is copied from Raph's "koloro" local repository, with lots of fiddly changes to satisfy clippy.

This is copied from Raph's "koloro" local repository, with lots of fiddly changes to satisfy clippy.
@raphlinus
Copy link
Contributor Author

A few more things to say about this. A few things are still in flux, though overall I'm happy with the direction it's going. I am especially curious to learn whether it meets the needs of potential users, or has some gaps.

Not all the CSS color spaces are done. Adding those should be fairly straightforward.

This crate would benefit from extensive testing. There are a bunch of tests in the CSS Color 4 spec, and likely many of those could be adapted without much trouble. In general, the color.js library can be considered a source of truth, and perhaps tests could be written to test whether this library gives consistent results (I'm thinking of the extremely extensive fuzz testing against the reference cmark implementation in pulldown-cmark, which also tests against a JS ref).

The TaggedColor and CssColor types should almost certainly be merged, I don't think there's significant value in having them separate. The main difference is that the former doesn't have the concept of missing channels. I think the runtime cost of supporting them is minimal.

There are a few other todos and discussion questions in the code.

Discussion is welcome! There's more than a bit of background in a Zulip thread.

Copy link
Member

@xStrom xStrom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a quick pass and didn't find any obvious faults. Did not verify magic numbers or color theory.

Given the fresh nature of the repo, I think it's fine to merge and do improvements in follow-up PRs.

Comment on lines +354 to +360
#[expect(
clippy::suspicious_arithmetic_impl,
reason = "somebody please teach clippy about multiplicative inverses"
)]
fn div(self, rhs: f32) -> Self {
self * rhs.recip()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

Copy link
Member

@DJMcNab DJMcNab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to agree that landing is probably reasonable.
I do intend to do a proper code style review pass tomorrow.

The one other thing to check is whether the AUTHORS file matches your expectations

@raphlinus
Copy link
Contributor Author

Great, I'll merge as-is, then expect to do quite a bit of followup in other PRs.

The AUTHORS file seems fine to me. That said, in the various README and docstring (which is known incomplete) I do want to acknowledge the inspiration of other libraries, especially bevy_color, but also palette and color.js.

@raphlinus raphlinus added this pull request to the merge queue Nov 4, 2024
Merged via the queue into main with commit 3502072 Nov 4, 2024
15 checks passed
@raphlinus raphlinus deleted the color branch November 4, 2024 21:59
Copy link
Member

@DJMcNab DJMcNab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A single-pass review sweep.

/// that "opaque" refers to the color, not the representation; the components
/// are publicly accessible.
#[derive(Clone, Copy, Debug)]
pub struct OpaqueColor<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should use more descriptive generic names here (e.g. OpaqueColor<Space>)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree T is bad here, as it's not a generic container, but I wonder whether CS would be just as clear and more concise. I'll put CS in my PR, then we can bikeshed.


/// The hue direction for interpolation.
///
/// This type corresponds to `hue-interpolation-method` in the CSS Color
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These sorts of references should have an MDN link - https://developer.mozilla.org/en-US/docs/Web/CSS/hue-interpolation-method

}
}

pub(crate) const fn split_alpha(x: [f32; 4]) -> ([f32; 3], f32) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could potentially use [x, y, z, a]: [f32; 4]. This should only be a code-style thing.

/// Interpolate colors.
///
/// Note: this function doesn't fix up hue in cylindrical spaces. It is
/// still be useful if the hue angles are compatible, particularly if
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is still be useful

}

// Arithmetic traits. A major motivation for providing these is to enable
// weighted sums, which are well defined when the weights sum to 1. In
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the domain knowledge here, but this sentence suggests that the operations implemented here are potentially suspect, and might be better as methods with documentation and caveats.

At the very least, the impls themselves probably should have documentation explaining some of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've thought about this deeply and feel that arithmetic operations are worthwhile to include. Basically, it enables spline interpolation with a generic spline implementation that has the arithmetic traits as bounds, and to me that upside outweighs any downside. Other color crates impl these as well.

I have moved this comment to documentation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable. An alternative curve bending design might be to have an Arithmetic<T> wrapper, where you're opting in to thinking carefully about this.

#[derive(Clone, Copy, Debug)]
pub struct Oklab;

// Matrices taken from Oklab blog post, precision reduced to f32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link?


const LAYOUT: ColorspaceLayout = ColorspaceLayout::HueThird;

fn from_linear_srgb(src: [f32; 3]) -> [f32; 3] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In bevy_color, I believe it is possible to convert between Oklab and Oklch directly. Would this be a useful operation to support?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wrestling between code size and runtime efficiency. But on reflection, I think it makes sense to have a convert<TargetCS: ColorSpace> method with a default impl. Probably I'll do that in a separate PR to keep the next one focused on cleanup.

impl Interpolator {
pub fn eval(&self, t: f32) -> CssColor {
let premul = [
self.premul1[0] + t * (self.premul2[0] - self.premul1[0]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like premul2 is ever used except to compute the same difference value here.

Should this difference instead be stored?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a bit of super-geekery, there are two basic strategies for implementing lerp. One is a + t * (b - a), the other is a * (1 - t) + b * t. The latter has the advantage of zero rounding error at t = 1, but I think for the purposes of this crate one ulp at f32 is completely acceptable.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glam ended up switching to the precise lerp (reported in bitshifter/glam-rs#275, fixed in bitshifter/glam-rs#548 (but some of the concerns about differences in magnitude are different in color code vs generic vectors ...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, for geometry sometimes you really care about watertightness, but for color close is pretty much always good enough.

macro_rules! define_float_funcs {
($(
fn $name:ident(self $(,$arg:ident: $arg_ty:ty)*) -> $ret:ty
=> $lname:ident/$lfname:ident;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lname doesn't seem to be used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, these were adapted from kurbo, but I think we can simplify and only include f32 versions.

end_color: PremulColor<CS>,
}

pub fn gradient<CS: Colorspace>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More evidence in favour of ColorSpace in this generic name

raphlinus added a commit that referenced this pull request Nov 5, 2024
In response to feedback on #3.
@raphlinus raphlinus mentioned this pull request Nov 5, 2024
github-merge-queue bot pushed a commit that referenced this pull request Nov 5, 2024
In response to feedback on #3.

One comment that was not addressed was direct conversion between color
spaces, as opposed to always going through linear rgb.
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 this pull request may close these issues.

4 participants