-
Notifications
You must be signed in to change notification settings - Fork 7
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
Conversation
This is copied from Raph's "koloro" local repository, with lots of fiddly changes to satisfy clippy.
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 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. |
There was a problem hiding this 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.
#[expect( | ||
clippy::suspicious_arithmetic_impl, | ||
reason = "somebody please teach clippy about multiplicative inverses" | ||
)] | ||
fn div(self, rhs: f32) -> Self { | ||
self * rhs.recip() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😄
There was a problem hiding this 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
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. |
There was a problem hiding this 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> { |
There was a problem hiding this comment.
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>
)
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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] { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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]), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ...)
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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>( |
There was a problem hiding this comment.
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
In response to feedback on #3.
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.
This is copied from Raph's "koloro" local repository, with lots of fiddly changes to satisfy clippy.