Skip to content

Commit ff03335

Browse files
tomcurDJMcNab
andcommitted
Pack color name into DynamicColor flags
A rough implementation of the ideas in linebender#26 (comment). The ergonomics require some tweaking, and perhaps the performance too, though it should be easy to write this in a form where the compiler can easily optimize the bitwise operations. I haven't verified what the compiler does with the current form. An alternative is to keep `Missing` unchanged, and add something similar to `Flags` as a new field on `DynamicColor`. But adding a new field makes constructing `DynamicColor` a bit less ergonomic. Squashed: hsl() and hwb() serialize to rgb() Naming consistency Check size only during test Test all named colors Mixed case test Separate into two `u8`s consistency Debug impl Clean up Clippy Use Missing directly Reduce api surface Module name Simplify Debug Module header Inline more Docs --------- Co-authored-by: Daniel McNab <[email protected]>
1 parent db101e6 commit ff03335

9 files changed

+356
-132
lines changed

color/make_x11_colors.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,12 @@ def minimal_perfect_hash(d):
239239
print("];")
240240
print()
241241

242-
print(f"const NAMES: [&str; {n}] = [")
242+
print(f"pub(crate) const NAMES: [&str; {n}] = [")
243243
for (name, rgba) in keys:
244244
print(f' "{name}",')
245245
print("];")
246246
print()
247-
print(f"const COLORS: [[u8; 4]; {n}] = [")
247+
print(f"pub(crate) const COLORS: [[u8; 4]; {n}] = [")
248248
for (name, rgba) in keys:
249249
print(f' {list(rgba)},')
250250
print("];")
@@ -260,15 +260,15 @@ def minimal_perfect_hash(d):
260260
(((y as u64) * (n as u64)) >> 32) as usize
261261
}
262262
263-
pub(crate) fn lookup_palette(s: &str) -> Option<[u8; 4]> {
263+
pub(crate) fn lookup_palette(s: &str) -> Option<usize> {
264264
let mut key = 0_u32;
265265
for b in s.as_bytes() {
266266
key = key.wrapping_mul(9).wrapping_add(*b as u32);
267267
}
268268
let salt = SALTS[weak_hash(key, 0, SALTS.len())] as u32;
269269
let ix = weak_hash(key, salt, SALTS.len());
270270
if s == NAMES[ix] {
271-
Some(COLORS[ix])
271+
Some(ix)
272272
} else {
273273
None
274274
}

color/src/dynamic.rs

+51-34
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
66
use crate::{
77
color::{add_alpha, fixup_hues_for_interpolate, split_alpha},
8-
AlphaColor, ColorSpace, ColorSpaceLayout, ColorSpaceTag, HueDirection, LinearSrgb, Missing,
8+
AlphaColor, ColorSpace, ColorSpaceLayout, ColorSpaceTag, Flags, HueDirection, LinearSrgb,
9+
Missing,
910
};
1011
use core::hash::{Hash, Hasher};
1112

@@ -31,8 +32,9 @@ use core::hash::{Hash, Hasher};
3132
pub struct DynamicColor {
3233
/// The color space.
3334
pub cs: ColorSpaceTag,
34-
/// A bitmask of missing components.
35-
pub missing: Missing,
35+
/// The state of this color, tracking whether it has missing components and how it was
36+
/// constructed. See the documentation of [`Flags`] for more information.
37+
pub flags: Flags,
3638
/// The components.
3739
///
3840
/// The first three components are interpreted according to the
@@ -41,6 +43,12 @@ pub struct DynamicColor {
4143
pub components: [f32; 4],
4244
}
4345

46+
// `DynamicColor` was carefully packed. Ensure its size doesn't accidentally change.
47+
#[cfg(test)]
48+
const _: () = if size_of::<DynamicColor>() != 20 {
49+
panic!("`DynamicColor` size changed");
50+
};
51+
4452
/// An intermediate struct used for interpolating between colors.
4553
///
4654
/// This is the return value of [`DynamicColor::interpolate`].
@@ -77,7 +85,7 @@ impl DynamicColor {
7785
if let Some(cs) = CS::TAG {
7886
Self {
7987
cs,
80-
missing: Missing::default(),
88+
flags: Flags::default(),
8189
components: color.components,
8290
}
8391
} else {
@@ -97,23 +105,23 @@ impl DynamicColor {
97105
let (opaque, alpha) = split_alpha(self.components);
98106
let mut components = add_alpha(self.cs.convert(cs, opaque), alpha);
99107
// Reference: §12.2 of Color 4 spec
100-
let missing = if !self.missing.is_empty() {
108+
let missing = if !self.flags.missing().is_empty() {
101109
if self.cs.same_analogous(cs) {
102110
for (i, component) in components.iter_mut().enumerate() {
103-
if self.missing.contains(i) {
111+
if self.flags.missing().contains(i) {
104112
*component = 0.0;
105113
}
106114
}
107-
self.missing
115+
self.flags.missing()
108116
} else {
109-
let mut missing = self.missing & Missing::single(3);
110-
if self.cs.h_missing(self.missing) {
117+
let mut missing = self.flags.missing() & Missing::single(3);
118+
if self.cs.h_missing(self.flags.missing()) {
111119
cs.set_h_missing(&mut missing, &mut components);
112120
}
113-
if self.cs.c_missing(self.missing) {
121+
if self.cs.c_missing(self.flags.missing()) {
114122
cs.set_c_missing(&mut missing, &mut components);
115123
}
116-
if self.cs.l_missing(self.missing) {
124+
if self.cs.l_missing(self.flags.missing()) {
117125
cs.set_l_missing(&mut missing, &mut components);
118126
}
119127
missing
@@ -123,7 +131,7 @@ impl DynamicColor {
123131
};
124132
let mut result = Self {
125133
cs,
126-
missing,
134+
flags: Flags::from_missing(missing),
127135
components,
128136
};
129137
result.powerless_to_missing();
@@ -137,9 +145,9 @@ impl DynamicColor {
137145
/// a corresponding component which is 0. This method restores that
138146
/// invariant after manipulation which might invalidate it.
139147
fn zero_missing_components(mut self) -> Self {
140-
if !self.missing.is_empty() {
148+
if !self.flags.missing().is_empty() {
141149
for (i, component) in self.components.iter_mut().enumerate() {
142-
if self.missing.contains(i) {
150+
if self.flags.missing().contains(i) {
143151
*component = 0.0;
144152
}
145153
}
@@ -153,13 +161,13 @@ impl DynamicColor {
153161
/// will be ignored and the color returned unchanged.
154162
#[must_use]
155163
pub const fn multiply_alpha(self, rhs: f32) -> Self {
156-
if self.missing.contains(3) {
164+
if self.flags.missing().contains(3) {
157165
self
158166
} else {
159167
let (opaque, alpha) = split_alpha(self.components);
160168
Self {
161169
cs: self.cs,
162-
missing: self.missing,
170+
flags: Flags::from_missing(self.flags.missing()),
163171
components: add_alpha(opaque, alpha * rhs),
164172
}
165173
}
@@ -181,13 +189,13 @@ impl DynamicColor {
181189
/// ```
182190
#[must_use]
183191
pub const fn with_alpha(self, alpha: f32) -> Self {
184-
if self.missing.contains(3) {
192+
if self.flags.missing().contains(3) {
185193
self
186194
} else {
187195
let (opaque, _alpha) = split_alpha(self.components);
188196
Self {
189197
cs: self.cs,
190-
missing: self.missing,
198+
flags: Flags::from_missing(self.flags.missing()),
191199
components: add_alpha(opaque, alpha),
192200
}
193201
}
@@ -200,9 +208,12 @@ impl DynamicColor {
200208
pub fn scale_chroma(self, scale: f32) -> Self {
201209
let (opaque, alpha) = split_alpha(self.components);
202210
let components = self.cs.scale_chroma(opaque, scale);
211+
212+
let mut flags = self.flags;
213+
flags.discard_name();
203214
Self {
204215
cs: self.cs,
205-
missing: self.missing,
216+
flags,
206217
components: add_alpha(components, alpha),
207218
}
208219
.zero_missing_components()
@@ -219,15 +230,15 @@ impl DynamicColor {
219230
let alpha = alpha.clamp(0., 1.);
220231
Self {
221232
cs: self.cs,
222-
missing: self.missing,
233+
flags: self.flags,
223234
components: add_alpha(components, alpha),
224235
}
225236
}
226237

227238
fn premultiply_split(self) -> ([f32; 3], f32) {
228239
// Reference: §12.3 of Color 4 spec
229240
let (opaque, alpha) = split_alpha(self.components);
230-
let premul = if alpha == 1.0 || self.missing.contains(3) {
241+
let premul = if alpha == 1.0 || self.flags.missing().contains(3) {
231242
opaque
232243
} else {
233244
self.cs.layout().scale(opaque, alpha)
@@ -243,8 +254,9 @@ impl DynamicColor {
243254
if self.cs.layout() != ColorSpaceLayout::Rectangular
244255
&& self.components[1] < POWERLESS_EPSILON
245256
{
246-
self.cs
247-
.set_h_missing(&mut self.missing, &mut self.components);
257+
let mut missing = self.flags.missing();
258+
self.cs.set_h_missing(&mut missing, &mut self.components);
259+
self.flags.set_missing(missing);
248260
}
249261
}
250262

@@ -262,12 +274,14 @@ impl DynamicColor {
262274
) -> Interpolator {
263275
let mut a = self.convert(cs);
264276
let mut b = other.convert(cs);
265-
let missing = a.missing & b.missing;
266-
if self.missing != other.missing {
277+
let a_missing = a.flags.missing();
278+
let b_missing = b.flags.missing();
279+
let missing = a_missing & b_missing;
280+
if a_missing != b_missing {
267281
for i in 0..4 {
268-
if (a.missing & !b.missing).contains(i) {
282+
if (a_missing & !b_missing).contains(i) {
269283
a.components[i] = b.components[i];
270-
} else if (!a.missing & b.missing).contains(i) {
284+
} else if (!a_missing & b_missing).contains(i) {
271285
b.components[i] = a.components[i];
272286
}
273287
}
@@ -310,9 +324,12 @@ impl DynamicColor {
310324
#[must_use]
311325
pub fn map(self, f: impl Fn(f32, f32, f32, f32) -> [f32; 4]) -> Self {
312326
let [x, y, z, a] = self.components;
327+
328+
let mut flags = self.flags;
329+
flags.discard_name();
313330
Self {
314331
cs: self.cs,
315-
missing: self.missing,
332+
flags,
316333
components: f(x, y, z, a),
317334
}
318335
.zero_missing_components()
@@ -371,7 +388,7 @@ impl Hash for DynamicColor {
371388
/// match behavior for Rust float types.
372389
fn hash<H: Hasher>(&self, state: &mut H) {
373390
self.cs.hash(state);
374-
self.missing.hash(state);
391+
self.flags.hash(state);
375392
for c in self.components {
376393
c.to_bits().hash(state);
377394
}
@@ -382,7 +399,7 @@ impl PartialEq for DynamicColor {
382399
/// Equality is determined based on the bit representation.
383400
fn eq(&self, other: &Self) -> bool {
384401
self.cs == other.cs
385-
&& self.missing == other.missing
402+
&& self.flags == other.flags
386403
&& self.components[0].to_bits() == other.components[0].to_bits()
387404
&& self.components[1].to_bits() == other.components[1].to_bits()
388405
&& self.components[2].to_bits() == other.components[2].to_bits()
@@ -410,7 +427,7 @@ impl Interpolator {
410427
let components = add_alpha(opaque, alpha);
411428
DynamicColor {
412429
cs: self.cs,
413-
missing: self.missing,
430+
flags: Flags::from_missing(self.missing),
414431
components,
415432
}
416433
}
@@ -424,15 +441,15 @@ mod tests {
424441
fn missing_alpha() {
425442
let c = parse_color("oklab(0.5 0.2 0 / none)").unwrap();
426443
assert_eq!(0., c.components[3]);
427-
assert_eq!(Missing::single(3), c.missing);
444+
assert_eq!(Missing::single(3), c.flags.missing());
428445

429446
// Alpha is missing, so we shouldn't be able to get an alpha added.
430447
let c2 = c.with_alpha(0.5);
431448
assert_eq!(0., c2.components[3]);
432-
assert_eq!(Missing::single(3), c2.missing);
449+
assert_eq!(Missing::single(3), c2.flags.missing());
433450

434451
let c3 = c.multiply_alpha(0.2);
435452
assert_eq!(0., c3.components[3]);
436-
assert_eq!(Missing::single(3), c3.missing);
453+
assert_eq!(Missing::single(3), c3.flags.missing());
437454
}
438455
}

0 commit comments

Comments
 (0)