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

Pack color name into DynamicColor flags #39

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions color/make_x11_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,12 @@ def minimal_perfect_hash(d):
print("];")
print()

print(f"const NAMES: [&str; {n}] = [")
print(f"pub(crate) const NAMES: [&str; {n}] = [")
for (name, rgba) in keys:
print(f' "{name}",')
print("];")
print()
print(f"const COLORS: [[u8; 4]; {n}] = [")
print(f"pub(crate) const COLORS: [[u8; 4]; {n}] = [")
for (name, rgba) in keys:
print(f' {list(rgba)},')
print("];")
Expand All @@ -260,15 +260,15 @@ def minimal_perfect_hash(d):
(((y as u64) * (n as u64)) >> 32) as usize
}

pub(crate) fn lookup_palette(s: &str) -> Option<[u8; 4]> {
pub(crate) fn lookup_palette(s: &str) -> Option<usize> {
let mut key = 0_u32;
for b in s.as_bytes() {
key = key.wrapping_mul(9).wrapping_add(*b as u32);
}
let salt = SALTS[weak_hash(key, 0, SALTS.len())] as u32;
let ix = weak_hash(key, salt, SALTS.len());
if s == NAMES[ix] {
Some(COLORS[ix])
Some(ix)
} else {
None
}
Expand Down
72 changes: 42 additions & 30 deletions color/src/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

use crate::{
color::{add_alpha, fixup_hues_for_interpolate, split_alpha},
AlphaColor, ColorSpace, ColorSpaceLayout, ColorSpaceTag, HueDirection, LinearSrgb, Missing,
AlphaColor, ColorSpace, ColorSpaceLayout, ColorSpaceTag, Flags, HueDirection, LinearSrgb,
Missing,
};

/// A color with a color space tag decided at runtime.
Expand All @@ -29,8 +30,9 @@ use crate::{
pub struct DynamicColor {
/// The color space.
pub cs: ColorSpaceTag,
/// A bitmask of missing components.
pub missing: Missing,
/// The state of this color, tracking whether it has missing components and how it was
/// constructed. See the documentation of [`Flags`] for more information.
pub flags: Flags,
/// The components.
///
/// The first three components are interpreted according to the
Expand All @@ -39,6 +41,12 @@ pub struct DynamicColor {
pub components: [f32; 4],
}

// `DynamicColor` was carefully packed. Ensure its size doesn't accidentally change.
#[cfg(test)]
const _: () = if size_of::<DynamicColor>() != 20 {
tomcur marked this conversation as resolved.
Show resolved Hide resolved
panic!("`DynamicColor` size changed");
};

/// An intermediate struct used for interpolating between colors.
///
/// This is the return value of [`DynamicColor::interpolate`].
Expand Down Expand Up @@ -75,7 +83,7 @@ impl DynamicColor {
if let Some(cs) = CS::TAG {
Self {
cs,
missing: Missing::default(),
flags: Flags::default(),
components: color.components,
}
} else {
Expand All @@ -95,33 +103,36 @@ impl DynamicColor {
let (opaque, alpha) = split_alpha(self.components);
let mut components = add_alpha(self.cs.convert(cs, opaque), alpha);
// Reference: §12.2 of Color 4 spec
let missing = if !self.missing.is_empty() {
let missing = if self.flags.has_missing() {
if self.cs.same_analogous(cs) {
for (i, component) in components.iter_mut().enumerate() {
if self.missing.contains(i) {
if self.flags.missing(i) {
*component = 0.0;
}
}
self.missing
Flags::from_missing(self.flags.extract_missing())
} else {
let mut missing = self.missing & Missing::single(3);
if self.cs.h_missing(self.missing) {
cs.set_h_missing(&mut missing, &mut components);
let mut flags = Flags::from_missing(
self.flags.extract_missing()
& Flags::from_single_missing(3).extract_missing(),
);
if self.cs.h_missing(self.flags) {
cs.set_h_missing(&mut flags, &mut components);
}
if self.cs.c_missing(self.missing) {
cs.set_c_missing(&mut missing, &mut components);
if self.cs.c_missing(self.flags) {
cs.set_c_missing(&mut flags, &mut components);
}
if self.cs.l_missing(self.missing) {
cs.set_l_missing(&mut missing, &mut components);
if self.cs.l_missing(self.flags) {
cs.set_l_missing(&mut flags, &mut components);
}
missing
Flags::from_missing(self.flags.extract_missing())
}
} else {
Missing::default()
Flags::default()
};
let mut result = Self {
cs,
missing,
flags: missing,
components,
};
result.powerless_to_missing();
Expand All @@ -135,9 +146,9 @@ impl DynamicColor {
/// a corresponding component which is 0. This method restores that
/// invariant after manipulation which might invalidate it.
fn zero_missing_components(mut self) -> Self {
if !self.missing.is_empty() {
if self.flags.has_missing() {
for (i, component) in self.components.iter_mut().enumerate() {
if self.missing.contains(i) {
if self.flags.missing(i) {
*component = 0.0;
}
}
Expand All @@ -154,7 +165,7 @@ impl DynamicColor {
let components = self.cs.scale_chroma(opaque, scale);
Self {
cs: self.cs,
missing: self.missing,
flags: self.flags,
components: add_alpha(components, alpha),
}
.zero_missing_components()
Expand All @@ -171,15 +182,15 @@ impl DynamicColor {
let alpha = alpha.clamp(0., 1.);
Self {
cs: self.cs,
missing: self.missing,
flags: self.flags,
components: add_alpha(components, alpha),
}
}

fn premultiply_split(self) -> ([f32; 3], f32) {
// Reference: §12.3 of Color 4 spec
let (opaque, alpha) = split_alpha(self.components);
let premul = if alpha == 1.0 || self.missing.contains(3) {
let premul = if alpha == 1.0 || self.flags.missing(3) {
opaque
} else {
self.cs.layout().scale(opaque, alpha)
Expand All @@ -195,8 +206,7 @@ impl DynamicColor {
if self.cs.layout() != ColorSpaceLayout::Rectangular
&& self.components[1] < POWERLESS_EPSILON
{
self.cs
.set_h_missing(&mut self.missing, &mut self.components);
self.cs.set_h_missing(&mut self.flags, &mut self.components);
}
}

Expand All @@ -214,12 +224,14 @@ impl DynamicColor {
) -> Interpolator {
let mut a = self.convert(cs);
let mut b = other.convert(cs);
let missing = a.missing & b.missing;
if self.missing != other.missing {
let a_missing = a.flags.extract_missing();
let b_missing = b.flags.extract_missing();
let missing = a_missing & b_missing;
if a_missing != b_missing {
for i in 0..4 {
if (a.missing & !b.missing).contains(i) {
if (a_missing & !b_missing).contains(i) {
a.components[i] = b.components[i];
} else if (!a.missing & b.missing).contains(i) {
} else if (!a_missing & b_missing).contains(i) {
b.components[i] = a.components[i];
}
}
Expand Down Expand Up @@ -262,7 +274,7 @@ impl DynamicColor {
let [x, y, z, a] = self.components;
Self {
cs: self.cs,
missing: self.missing,
flags: Flags::from_missing(self.flags.extract_missing()),
components: f(x, y, z, a),
}
.zero_missing_components()
Expand Down Expand Up @@ -320,7 +332,7 @@ impl Interpolator {
let components = add_alpha(opaque, alpha);
DynamicColor {
cs: self.cs,
missing: self.missing,
flags: Flags::from_missing(self.missing),
components,
}
}
Expand Down
4 changes: 2 additions & 2 deletions color/src/gradient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ pub fn gradient<CS: ColorSpace>(
tolerance: f32,
) -> GradientIter<CS> {
let interpolator = color0.interpolate(color1, interp_cs, direction);
if !color0.missing.is_empty() {
if color0.flags.has_missing() {
color0 = interpolator.eval(0.0);
}
let target0 = color0.to_alpha_color().premultiply();
if !color1.missing.is_empty() {
if color1.flags.has_missing() {
color1 = interpolator.eval(1.0);
}
let target1 = color1.to_alpha_color().premultiply();
Expand Down
2 changes: 1 addition & 1 deletion color/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub use colorspace::{
};
pub use dynamic::{DynamicColor, Interpolator};
pub use gradient::{gradient, GradientIter};
pub use missing::Missing;
pub use missing::{Flags, Missing};
pub use parse::{parse_color, ParseError};
pub use tag::ColorSpaceTag;

Expand Down
111 changes: 110 additions & 1 deletion color/src/missing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,136 @@

//! A simple bitset.

/// A simple bitset for representing missing components.
use crate::x11_colors;

/// Flags indicating [`DynamicColor`](crate::DynamicColor) state.
///
/// This tracks missing color components of a `DynamicColor` and details of how a `DynamicColor`
/// was constructed.
///
/// The "missing" flags indicate whether a specific color component is missing (either the three
/// color channels or the alpha channel). The "named" flag represents whether the dynamic color was
/// generated from one of the named colors in [CSS Color Module Level 4 § 6.1][css-named-colors] or
/// named color space functions in [CSS Color Module Level 4 § 4.1][css-named-color-spaces].
///
/// The latter is primarily useful for serializing.
///
/// [css-named-colors]: https://www.w3.org/TR/css-color-4/#named-colors
/// [css-named-color-spaces]: https://www.w3.org/TR/css-color-4/#color-syntax
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
pub struct Flags {
/// A bitset of missing color components.
missing: u8,
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense for this to be Missing directly?


/// The named source a [`crate::DynamicColor`] was constructed from. Meanings:
/// - 0 - not constructed from a named source;
/// - 255 - constructed from a named color space function;
/// - otherwise - the 1-based index into [`crate::x11_colors::NAMES`].
name: u8,
}

/// Missing color components, extracted from [`Flags`]. Some bitwise operations are implemented on
/// this type, making certain manipulations more ergonomic.
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
pub struct Missing(u8);

impl Flags {
/// Construct flags indicating the given color component is missing. The component index must
/// be 0, 1, 2, or 3.
pub const fn from_single_missing(ix: usize) -> Self {
debug_assert!(ix <= 3, "color component index must be 0, 1, 2 or 3");
Flags {
missing: 1 << ix,
name: 0,
}
}

/// Construct flags with the given missing components.
pub const fn from_missing(missing: Missing) -> Self {
Flags {
missing: missing.0,
name: 0,
}
}

/// Construct flags indicating the color was generated from one of the named colors.
pub(crate) fn set_named_color(&mut self, name_ix: usize) {
debug_assert!(name_ix < x11_colors::NAMES.len());
debug_assert!(x11_colors::NAMES.len() <= 253);

self.name = name_ix as u8 + 1;
}

/// Construct flags indicating the color was generated from one of the named color space
/// functions.
pub(crate) fn set_named_color_space(&mut self) {
self.name = 255;
}

/// Set the given component as missing.
pub fn set_missing(&mut self, ix: usize) {
self.missing = Self::from_single_missing(ix).missing;
}

/// Extract the missing components from the flags.
#[inline]
pub fn extract_missing(self) -> Missing {
Missing(self.missing)
}

/// Returns `true` if the flags indicate the given color component is missing. The component
/// index must be 0, 1 or 2.
#[inline]
pub fn missing(self, ix: usize) -> bool {
self.extract_missing().contains(ix)
}

/// Returns `true` if at least one component is missing.
#[inline]
pub fn has_missing(self) -> bool {
!self.extract_missing().is_empty()
}

/// Returns `true` if the flags indicate the color was generated from a named color or named
/// color space function.
pub fn named(self) -> bool {
self.name != 0
}

/// If the color was constructed from a named color, returns that name.
///
/// See also [`parse_color`][crate::parse_color].
pub fn color_name(self) -> Option<&'static str> {
Copy link
Member

Choose a reason for hiding this comment

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

Can we use this in Debug somewhere?

let name_ix = self.name;
if name_ix == 0 || name_ix == 255 {
None
} else {
Some(x11_colors::NAMES[name_ix as usize - 1])
}
}
}

impl Missing {
/// Returns `true` if the set contains the component index.
#[inline]
pub fn contains(self, ix: usize) -> bool {
(self.0 & (1 << ix)) != 0
}

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

/// The set containing a single component index.
#[inline]
pub fn single(ix: usize) -> Self {
Self(1 << ix)
}

/// Returns `true` if the set contains no indices.
#[inline]
pub fn is_empty(self) -> bool {
self.0 == 0
}
Expand Down
Loading