color_operations: add color transform primitives#221
Conversation
|
This was done with the assistance of Codex (GPT 5.5, xhigh). |
738467e to
4fc30bd
Compare
Introduce the `color_operations` crate as a small `no_std` layer for per-pixel color transforms in caller-selected working color spaces. Add `ComponentTransfer` and `TransferFunction` for per-channel identity, table, discrete, linear, and gamma operations. Add `ColorMatrix` for affine channel-mixing operations, including common constructors for opacity, brightness, contrast, invert, saturate, grayscale, hue rotation, sepia, and luminance-to-alpha. Add `ColorOperation` for storing mixed matrix and component-transfer pipelines. Add typed apply support for `AlphaColor`, `PremulColor`, and `DynamicColor`, plus raw straight and premultiplied component helpers. `DynamicColor` operations preserve the runtime color-space tag and missing-component flags while discarding named-color state. Operations intentionally do not clip, clamp, gamut-map, pack, quantize, or perform color-space conversion; those remain caller/rendering-boundary responsibilities.
4fc30bd to
f2f6986
Compare
There was a problem hiding this comment.
I agree it would be useful to have some primitives here for implementing these kinds of color filter effects.
I have some observations and unfinished initial thoughts.
The spec limits these filters to sRGB and linear sRGB (in fact, I believe in the spec all filters apply to linear sRGB by default). That is a sensible limitation though also a bit restrictive: these filters are not well-defined at all for e.g. Oklab or HSL, though some filters could make sense for any RGB color model (like Display P3).
Some filter short-hands introduced here, like invert, are a bit antithetical to how color handles gamuts: we allow all component values so any color space can represent any color, but invert is relative to the white point.
Some of the matrix operations have hard-coded coefficients for, e.g., saturation, hue-rotation, and luminance-to-alpha. These are derived for sRGB's chromaticities specifically, and (I believe) are only correct for linear sRGB.
Altogether, we could extract some nice general primitives, but most of the spec is quite opinionated. One thing I think we should not do is to have APIs that implicitly convert to/from (linear) sRGB to do the operations, as that could result in quite bad performance. We could get fancy and limit specific operations to specific color spaces, but that's a serious amount of type system noise for not that much benefit, and would be somewhat hard for users to extend.
There's a discussion in a different issue that touches on some related points: #192 (comment) (specifically, the discussion is about the cost of generics as well as color's inability to operate on slices of colors with a single dynamic color space.) Similarly, most operations introduced here are not truly generic over the color space: some are completely color-space-unaware transforms that simply perform math on the components, and others are very specific to (linear) sRGB (or at the very least, specific to RGB color models).
There may be two concerns in color that intermingle a bit: one is authoring and being clear about the color space, and the other is about bulk operation on color values. One option would be to just provide functions over [f32; 4], and document. The ones depending on linear sRGB's chromaticities and transfer function could be limited to LinearSrgb, but that would introduce some surprising asymmetry.
Introduce the
color_operationscrate as a smallno_stdlayer for per-pixel color transforms in caller-selected working color spaces.Add
ComponentTransferandTransferFunctionfor per-channel identity, table, discrete, linear, and gamma operations. AddColorMatrixfor affine channel-mixing operations, including common constructors for opacity, brightness, contrast, invert, saturate, grayscale, hue rotation, sepia, and luminance-to-alpha.Add
ColorOperationfor storing mixed matrix and component-transfer pipelines. Add typed apply support forAlphaColor,PremulColor, andDynamicColor, plus raw straight and premultiplied component helpers.DynamicColoroperations preserve the runtime color-space tag and missing-component flags while discarding named-color state.Operations intentionally do not clip, clamp, gamut-map, pack, quantize, or perform color-space conversion; those remain caller/rendering-boundary responsibilities.