mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 07:16:52 +00:00
Swatches: Select All Unused looks gradients up by their whole definition
swatch.unused grouped the gradients in use by their stop colors and compared each gradient swatch with every gradient in its group, so gradients that share stop colors but differ in offsets, midpoints, opacity, kind, method, dither or links still took quadratic time. Each gradient now has a key that is equal exactly when the gradients are equal: kind, method, dither, and each stop's offset, color, opacity, midpoint, link, tint and process plates. The gradients in use go into one hash set, and each gradient swatch is looked up in it.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_color::{Color, Gradient, Paint, Swatch, SwatchGroup, tint_percent};
|
||||
use vectorcraft_color::{Color, Gradient, GradientKind, GradientMethod, GradientStop, Paint, Swatch, SwatchGroup, tint_percent};
|
||||
use vectorcraft_doc::pattern::pattern_paint;
|
||||
use vectorcraft_doc::swatches::{LinkFn, color_name, map_node_solid_paints, node_colors};
|
||||
use vectorcraft_doc::{Document, NodeId};
|
||||
@@ -320,10 +320,35 @@ fn float_key(v: f32) -> Option<u32> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The keys of a gradient's stop colors, equal for equal gradients; `None` when a stop's color
|
||||
/// equals no color, so the gradient equals no gradient.
|
||||
fn gradient_key(g: &Gradient) -> Option<Vec<ColorKey>> {
|
||||
g.stops.iter().map(|s| color_key(&s.color)).collect()
|
||||
/// A gradient stop's offset, color, opacity, midpoint, swatch link, tint and process plates, as
|
||||
/// keys.
|
||||
type StopKey<'a> = (u32, ColorKey, u32, u32, Option<&'a str>, u32, Option<[u32; 4]>);
|
||||
|
||||
/// A gradient's kind, method, dither and stops, as keys.
|
||||
type GradientKey<'a> = (u8, u8, bool, Vec<StopKey<'a>>);
|
||||
|
||||
/// A key that is equal for two gradients exactly when the gradients are equal (`==`); `None` when
|
||||
/// any of its numbers is NaN, so the gradient equals no gradient.
|
||||
fn gradient_key(g: &Gradient) -> Option<GradientKey<'_>> {
|
||||
let kind = match g.kind {
|
||||
GradientKind::Linear => 0,
|
||||
GradientKind::Radial => 1,
|
||||
GradientKind::Freeform => 2,
|
||||
};
|
||||
let method = match g.method {
|
||||
GradientMethod::Classic => 0,
|
||||
GradientMethod::Perceptual => 1,
|
||||
};
|
||||
Some((kind, method, g.dither, g.stops.iter().map(stop_key).collect::<Option<_>>()?))
|
||||
}
|
||||
|
||||
/// A stop's key for [`gradient_key`]; `None` when any of its numbers is NaN.
|
||||
fn stop_key(s: &GradientStop) -> Option<StopKey<'_>> {
|
||||
let plates = match s.plates {
|
||||
Some(p) => Some([float_key(p[0])?, float_key(p[1])?, float_key(p[2])?, float_key(p[3])?]),
|
||||
None => None,
|
||||
};
|
||||
Some((float_key(s.offset)?, color_key(&s.color)?, float_key(s.opacity)?, float_key(s.midpoint)?, s.swatch.as_deref(), float_key(s.tint)?, plates))
|
||||
}
|
||||
|
||||
/// Link the solid paints in the subtrees of `ids` whose color is one of `links`' to that swatch
|
||||
@@ -891,16 +916,7 @@ fn swatch_unused(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
let link_tints: HashSet<(&str, u32)> = links.iter().filter_map(|(l, t)| Some((l.as_str(), float_key(*t)?))).collect();
|
||||
let colors: HashSet<ColorKey> = colors.iter().filter_map(color_key).collect();
|
||||
let patterns: HashSet<&String> = patterns.iter().collect();
|
||||
// Gradients by their stops' colors, each listed once.
|
||||
let mut by_stops: HashMap<Vec<ColorKey>, Vec<&Gradient>> = HashMap::new();
|
||||
for g in &gradients {
|
||||
if let Some(k) = gradient_key(g) {
|
||||
let list = by_stops.entry(k).or_default();
|
||||
if !list.contains(&g) {
|
||||
list.push(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
let gradients: HashSet<GradientKey> = gradients.iter().filter_map(gradient_key).collect();
|
||||
let used = |w: &Swatch| {
|
||||
gradient_links.contains(&w.name)
|
||||
|| match w.tint_of() {
|
||||
@@ -910,7 +926,7 @@ fn swatch_unused(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
|| match &w.paint {
|
||||
Paint::None => true,
|
||||
Paint::Solid { color, .. } => color_key(color).is_some_and(|k| colors.contains(&k)),
|
||||
Paint::Gradient(g) => gradient_key(&g.gradient).and_then(|k| by_stops.get(&k)).is_some_and(|list| list.contains(&&g.gradient)),
|
||||
Paint::Gradient(g) => gradient_key(&g.gradient).is_some_and(|k| gradients.contains(&k)),
|
||||
Paint::Pattern { pattern, .. } => patterns.contains(pattern),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -207,15 +207,24 @@ fn unused_matches_links_tints_colors_gradients_and_patterns_exactly() {
|
||||
set_fill(&mut s, tinted, Paint::Solid { color: teal.tinted(0.4), swatch: Some("Teal".into()), tint: 0.4 });
|
||||
let zero = rect_filled(&mut s, json!({"color": "#000000"}));
|
||||
set_fill(&mut s, zero, Paint::solid(Color::rgb(-0.0, 128.0 / 255.0, 1.0)));
|
||||
// Sunset's gradient without a link to it, and Ocean's with one stop moved.
|
||||
// Sunset's gradient without a link to it, beside gradients with Sunset's stop colors and a
|
||||
// different midpoint or opacity; Ocean's with one stop moved, and Ocean's with another kind,
|
||||
// method or dither.
|
||||
let gradient = |s: &Session, n: &str| match &doc(s).swatch(n).unwrap().paint {
|
||||
Paint::Gradient(g) => g.gradient.clone(),
|
||||
p => panic!("{p:?}"),
|
||||
};
|
||||
let sunset = gradient(&s, "Sunset");
|
||||
let mut ocean = gradient(&s, "Ocean");
|
||||
ocean.stops[0].offset += 0.25;
|
||||
for g in [sunset.clone(), sunset, ocean] {
|
||||
let (mut midpoint, mut opacity) = (sunset.clone(), sunset.clone());
|
||||
midpoint.stops[0].midpoint = 0.3;
|
||||
opacity.stops[1].opacity = 0.5;
|
||||
let ocean = gradient(&s, "Ocean");
|
||||
let (mut moved, mut radial, mut perceptual, mut dither) = (ocean.clone(), ocean.clone(), ocean.clone(), ocean);
|
||||
moved.stops[0].offset += 0.25;
|
||||
radial.kind = vectorcraft_color::GradientKind::Radial;
|
||||
perceptual.method = vectorcraft_color::GradientMethod::Perceptual;
|
||||
dither.dither = true;
|
||||
for g in [midpoint, sunset.clone(), opacity, sunset, moved, radial, perceptual, dither] {
|
||||
let id = rect_filled(&mut s, json!({"color": "#000000"}));
|
||||
set_fill(&mut s, id, Paint::Gradient(Box::new(vectorcraft_color::GradientPaint::new(g))));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user