Skip to content

Commit ac22029

Browse files
committed
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.
1 parent 97a423a commit ac22029

File tree

8 files changed

+252
-82
lines changed

8 files changed

+252
-82
lines changed

color/src/dynamic.rs

+41-30
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

1112
/// A color with a color space tag decided at runtime.
@@ -29,8 +30,9 @@ use crate::{
2930
pub struct DynamicColor {
3031
/// The color space.
3132
pub cs: ColorSpaceTag,
32-
/// A bitmask of missing components.
33-
pub missing: Missing,
33+
/// The state of this color, tracking whether it has missing components and how it was
34+
/// constructed. See the documentation of [`Flags`] for more information.
35+
pub flags: Flags,
3436
/// The components.
3537
///
3638
/// The first three components are interpreted according to the
@@ -39,6 +41,11 @@ pub struct DynamicColor {
3941
pub components: [f32; 4],
4042
}
4143

44+
// `DynamicColor` was carefully packed. Ensure its size doesn't accidentally change.
45+
const _: () = if size_of::<DynamicColor>() != 20 {
46+
panic!("`DynamicColor` size changed");
47+
};
48+
4249
/// An intermediate struct used for interpolating between colors.
4350
///
4451
/// This is the return value of [`DynamicColor::interpolate`].
@@ -75,7 +82,7 @@ impl DynamicColor {
7582
if let Some(cs) = CS::TAG {
7683
Self {
7784
cs,
78-
missing: Missing::default(),
85+
flags: Flags::default(),
7986
components: color.components,
8087
}
8188
} else {
@@ -95,33 +102,36 @@ impl DynamicColor {
95102
let (opaque, alpha) = split_alpha(self.components);
96103
let mut components = add_alpha(self.cs.convert(cs, opaque), alpha);
97104
// Reference: §12.2 of Color 4 spec
98-
let missing = if !self.missing.is_empty() {
105+
let missing = if self.flags.has_missing() {
99106
if self.cs.same_analogous(cs) {
100107
for (i, component) in components.iter_mut().enumerate() {
101-
if self.missing.contains(i) {
108+
if self.flags.missing(i) {
102109
*component = 0.0;
103110
}
104111
}
105-
self.missing
112+
Flags::from_missing(self.flags.extract_missing())
106113
} else {
107-
let mut missing = self.missing & Missing::single(3);
108-
if self.cs.h_missing(self.missing) {
109-
cs.set_h_missing(&mut missing, &mut components);
114+
let mut flags = Flags::from_missing(
115+
self.flags.extract_missing()
116+
& Flags::from_single_missing(3).extract_missing(),
117+
);
118+
if self.cs.h_missing(self.flags) {
119+
cs.set_h_missing(&mut flags, &mut components);
110120
}
111-
if self.cs.c_missing(self.missing) {
112-
cs.set_c_missing(&mut missing, &mut components);
121+
if self.cs.c_missing(self.flags) {
122+
cs.set_c_missing(&mut flags, &mut components);
113123
}
114-
if self.cs.l_missing(self.missing) {
115-
cs.set_l_missing(&mut missing, &mut components);
124+
if self.cs.l_missing(self.flags) {
125+
cs.set_l_missing(&mut flags, &mut components);
116126
}
117-
missing
127+
Flags::from_missing(self.flags.extract_missing())
118128
}
119129
} else {
120-
Missing::default()
130+
Flags::default()
121131
};
122132
let mut result = Self {
123133
cs,
124-
missing,
134+
flags: missing,
125135
components,
126136
};
127137
result.powerless_to_missing();
@@ -135,9 +145,9 @@ impl DynamicColor {
135145
/// a corresponding component which is 0. This method restores that
136146
/// invariant after manipulation which might invalidate it.
137147
fn zero_missing_components(mut self) -> Self {
138-
if !self.missing.is_empty() {
148+
if self.flags.has_missing() {
139149
for (i, component) in self.components.iter_mut().enumerate() {
140-
if self.missing.contains(i) {
150+
if self.flags.missing(i) {
141151
*component = 0.0;
142152
}
143153
}
@@ -154,7 +164,7 @@ impl DynamicColor {
154164
let components = self.cs.scale_chroma(opaque, scale);
155165
Self {
156166
cs: self.cs,
157-
missing: self.missing,
167+
flags: self.flags,
158168
components: add_alpha(components, alpha),
159169
}
160170
.zero_missing_components()
@@ -171,15 +181,15 @@ impl DynamicColor {
171181
let alpha = alpha.clamp(0., 1.);
172182
Self {
173183
cs: self.cs,
174-
missing: self.missing,
184+
flags: self.flags,
175185
components: add_alpha(components, alpha),
176186
}
177187
}
178188

179189
fn premultiply_split(self) -> ([f32; 3], f32) {
180190
// Reference: §12.3 of Color 4 spec
181191
let (opaque, alpha) = split_alpha(self.components);
182-
let premul = if alpha == 1.0 || self.missing.contains(3) {
192+
let premul = if alpha == 1.0 || self.flags.missing(3) {
183193
opaque
184194
} else {
185195
self.cs.layout().scale(opaque, alpha)
@@ -195,8 +205,7 @@ impl DynamicColor {
195205
if self.cs.layout() != ColorSpaceLayout::Rectangular
196206
&& self.components[1] < POWERLESS_EPSILON
197207
{
198-
self.cs
199-
.set_h_missing(&mut self.missing, &mut self.components);
208+
self.cs.set_h_missing(&mut self.flags, &mut self.components);
200209
}
201210
}
202211

@@ -214,12 +223,14 @@ impl DynamicColor {
214223
) -> Interpolator {
215224
let mut a = self.convert(cs);
216225
let mut b = other.convert(cs);
217-
let missing = a.missing & b.missing;
218-
if self.missing != other.missing {
226+
let a_missing = a.flags.extract_missing();
227+
let b_missing = b.flags.extract_missing();
228+
let missing = a_missing & b_missing;
229+
if a_missing != b_missing {
219230
for i in 0..4 {
220-
if (a.missing & !b.missing).contains(i) {
231+
if (a_missing & !b_missing).contains(i) {
221232
a.components[i] = b.components[i];
222-
} else if (!a.missing & b.missing).contains(i) {
233+
} else if (!a_missing & b_missing).contains(i) {
223234
b.components[i] = a.components[i];
224235
}
225236
}
@@ -262,7 +273,7 @@ impl DynamicColor {
262273
let [x, y, z, a] = self.components;
263274
Self {
264275
cs: self.cs,
265-
missing: self.missing,
276+
flags: Flags::from_missing(self.flags.extract_missing()),
266277
components: f(x, y, z, a),
267278
}
268279
.zero_missing_components()
@@ -320,7 +331,7 @@ impl Interpolator {
320331
let components = add_alpha(opaque, alpha);
321332
DynamicColor {
322333
cs: self.cs,
323-
missing: self.missing,
334+
flags: Flags::from_missing(self.missing),
324335
components,
325336
}
326337
}

color/src/gradient.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ pub fn gradient<CS: ColorSpace>(
5454
tolerance: f32,
5555
) -> GradientIter<CS> {
5656
let interpolator = color0.interpolate(color1, interp_cs, direction);
57-
if !color0.missing.is_empty() {
57+
if color0.flags.has_missing() {
5858
color0 = interpolator.eval(0.0);
5959
}
6060
let target0 = color0.to_alpha_color().premultiply();
61-
if !color1.missing.is_empty() {
61+
if color1.flags.has_missing() {
6262
color1 = interpolator.eval(1.0);
6363
}
6464
let target1 = color1.to_alpha_color().premultiply();

color/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub use colorspace::{
4040
};
4141
pub use dynamic::{DynamicColor, Interpolator};
4242
pub use gradient::{gradient, GradientIter};
43-
pub use missing::Missing;
43+
pub use missing::{Flags, Missing};
4444
pub use parse::{parse_color, ParseError};
4545
pub use tag::ColorSpaceTag;
4646

color/src/missing.rs

+98-1
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,124 @@
33

44
//! A simple bitset.
55
6-
/// A simple bitset for representing missing components.
6+
use crate::x11_colors;
7+
8+
/// Flags indicating [`DynamicColor`](crate::DynamicColor) state.
9+
///
10+
/// This tracks missing color components of a `DynamicColor` and details of how a `DynamicColor`
11+
/// was constructed.
12+
///
13+
/// The "missing" flags indicate whether a specific color component is missing. The "named" flag
14+
/// represents whether the dynamic color was generated from one of the named colors in [CSS Color
15+
/// Module Level 4 § 6.1][css-named-colors] or named color space functions in [CSS Color Module
16+
/// Level 4 § 4.1][css-named-color-spaces].
17+
///
18+
/// The latter is primarily useful for serializing.
19+
///
20+
/// [css-named-colors]: https://www.w3.org/TR/css-color-4/#named-colors
21+
/// [css-named-color-spaces]: https://www.w3.org/TR/css-color-4/#color-syntax
22+
//
23+
// The flags are tracked with 16 bits. The first three are for missing components, the fourth
24+
// indicates whether the color was generated from a named color or named color space function. The
25+
// remaining bits indicate the named color.
26+
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
27+
pub struct Flags(u16);
28+
29+
/// Missing color components, extracted from [`Flags`]. Some bitwise operations are implemented on
30+
/// this type, making certain manipulations more ergonomic.
731
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
832
pub struct Missing(u8);
933

34+
impl Flags {
35+
/// Construct flags indicating the given color component is missing. The component index must
36+
/// be 0, 1, 2, or 3.
37+
pub const fn from_single_missing(ix: usize) -> Self {
38+
debug_assert!(ix <= 3, "color component index must be 0, 1, 2 or 3");
39+
Self(1 << ix)
40+
}
41+
42+
/// Construct flags with the given missing components.
43+
pub const fn from_missing(missing: Missing) -> Self {
44+
Self(missing.0 as u16)
45+
}
46+
47+
/// Construct flags indicating the color was generated from one of the named colors.
48+
pub(crate) fn set_from_named_color(&mut self, name_ix: u16) {
49+
let missing = self.0 & 0b1111;
50+
self.0 = missing | 1 << 4 | (name_ix + 1) << 5;
51+
}
52+
53+
/// Construct flags indicating the color was generated from one of the named color space
54+
/// functions.
55+
pub(crate) fn set_from_named_color_space(&mut self) {
56+
let missing = self.0 & 0b1111;
57+
self.0 = missing | 1 << 4;
58+
}
59+
60+
/// Set the given component as missing.
61+
pub fn set_missing(&mut self, ix: usize) {
62+
self.0 |= Self::from_single_missing(ix).0;
63+
}
64+
65+
/// Extract the missing components from the flags.
66+
#[inline]
67+
pub fn extract_missing(self) -> Missing {
68+
Missing((self.0 & 0b1111) as u8)
69+
}
70+
71+
/// Returns `true` if the flags indicate the given color component is missing. The component
72+
/// index must be 0, 1 or 2.
73+
#[inline]
74+
pub fn missing(self, ix: usize) -> bool {
75+
self.extract_missing().contains(ix)
76+
}
77+
78+
/// Returns `true` if at least one component is missing.
79+
#[inline]
80+
pub fn has_missing(self) -> bool {
81+
!self.extract_missing().is_empty()
82+
}
83+
84+
/// Returns `true` if the flags indicate the color was generated from a named color or named
85+
/// color space function.
86+
pub fn named(self) -> bool {
87+
self.0 & 1 << 4 != 0
88+
}
89+
90+
/// If the color was constructed from a named color, returns that name.
91+
///
92+
/// See also [`parse_color`][crate::parse_color].
93+
pub fn color_name(self) -> Option<&'static str> {
94+
let name_ix = self.0 >> 5;
95+
if name_ix == 0 {
96+
None
97+
} else {
98+
Some(x11_colors::NAMES[name_ix as usize - 1])
99+
}
100+
}
101+
}
102+
10103
impl Missing {
11104
/// Returns `true` if the set contains the component index.
105+
#[inline]
12106
pub fn contains(self, ix: usize) -> bool {
13107
(self.0 & (1 << ix)) != 0
14108
}
15109

16110
/// Adds a component index to the set.
111+
#[inline]
17112
pub fn insert(&mut self, ix: usize) {
18113
self.0 |= 1 << ix;
19114
}
20115

21116
/// The set containing a single component index.
117+
#[inline]
22118
pub fn single(ix: usize) -> Self {
23119
Self(1 << ix)
24120
}
25121

26122
/// Returns `true` if the set contains no indices.
123+
#[inline]
27124
pub fn is_empty(self) -> bool {
28125
self.0 == 0
29126
}

0 commit comments

Comments
 (0)