mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 02:37:01 -06:00
Merge pull request #3889 from dylanmtaylor/fix/audit-effects-color
Security: Scribble rows, Outer Glow dilation and CSS export names
This commit is contained in:
@@ -181,7 +181,9 @@ fn css_comment(s: &str) -> String {
|
||||
|
||||
fn write_css(lib: &SwatchLibrary, keep: impl Fn(&&Swatch) -> bool) -> String {
|
||||
let mut out = format!("/* {} */\n:root {{\n", css_comment(&lib.name));
|
||||
let mut taken: Vec<String> = vec![];
|
||||
let mut taken: HashSet<String> = HashSet::new();
|
||||
// The next number to try for each base, so repeated names don't search the ones used before.
|
||||
let mut next: HashMap<String, usize> = HashMap::new();
|
||||
let mut props = |out: &mut String, list: &[Swatch]| {
|
||||
for w in list.iter().filter(&keep) {
|
||||
let value = match &w.paint {
|
||||
@@ -198,8 +200,18 @@ fn write_css(lib: &SwatchLibrary, keep: impl Fn(&&Swatch) -> bool) -> String {
|
||||
};
|
||||
// Names differing only in case or punctuation map to one property: number the others.
|
||||
let base = css_property(&w.name);
|
||||
let name = (1..).map(|i| if i == 1 { base.clone() } else { format!("{base}-{i}") }).find(|n| !taken.contains(n)).unwrap_or(base);
|
||||
taken.push(name.clone());
|
||||
let name = if taken.insert(base.clone()) {
|
||||
base
|
||||
} else {
|
||||
let n = next.entry(base.clone()).or_insert(1);
|
||||
loop {
|
||||
*n += 1;
|
||||
let name = format!("{base}-{n}");
|
||||
if taken.insert(name.clone()) {
|
||||
break name;
|
||||
}
|
||||
}
|
||||
};
|
||||
let note = if w.spot { " /* spot */" } else { "" };
|
||||
out.push_str(&format!(" {name}: {value};{note}\n"));
|
||||
}
|
||||
|
||||
@@ -932,22 +932,41 @@ pub fn dilate_plane(a: &mut Vec<f32>, w: usize, h: usize, radius: f64) {
|
||||
/// One horizontal pass of [`dilate_plane`] over the rows of `w` values of a plane: the largest
|
||||
/// value within `whole`, blended by `frac` towards the largest within `whole + 1`.
|
||||
fn max_plane(src: &[f32], dst: &mut [f32], w: usize, whole: usize, frac: f32) {
|
||||
let (mut inner, mut ring) = (vec![0.0f32; w], vec![0.0f32; w]);
|
||||
for (row, out) in src.chunks_exact(w).zip(dst.chunks_exact_mut(w)) {
|
||||
for (x, o) in out.iter_mut().enumerate() {
|
||||
let lo = x.saturating_sub(whole + 1);
|
||||
let hi = (x + whole + 1).min(w - 1);
|
||||
let (mut inner, mut ring) = (row[x], row[x]);
|
||||
for (j, v) in row[lo..=hi].iter().enumerate() {
|
||||
if (lo + j).abs_diff(x) <= whole {
|
||||
inner = inner.max(*v);
|
||||
}
|
||||
ring = ring.max(*v);
|
||||
}
|
||||
*o = inner + (ring - inner) * frac;
|
||||
window_max(row, whole, &mut inner);
|
||||
window_max(row, whole + 1, &mut ring);
|
||||
for ((o, i), r) in out.iter_mut().zip(&inner).zip(&ring) {
|
||||
*o = i + (r - i) * frac;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// For each `x`, the largest of `row[x - radius..=x + radius]` (cut at the row's ends) into `out`,
|
||||
/// in time linear in the row (a sliding window, not a scan of the window for each `x`).
|
||||
fn window_max(row: &[f32], radius: usize, out: &mut [f32]) {
|
||||
use std::collections::VecDeque;
|
||||
// Indices of the window's values that can still be its largest, falling in value.
|
||||
let mut keep: VecDeque<usize> = VecDeque::new();
|
||||
let mut next = 0;
|
||||
for (x, o) in out.iter_mut().enumerate().take(row.len()) {
|
||||
let hi = x.saturating_add(radius).min(row.len().saturating_sub(1));
|
||||
while next <= hi {
|
||||
let v = row[next];
|
||||
while keep.back().is_some_and(|&b| row[b] <= v) {
|
||||
keep.pop_back();
|
||||
}
|
||||
keep.push_back(next);
|
||||
next += 1;
|
||||
}
|
||||
let lo = x.saturating_sub(radius);
|
||||
while keep.front().is_some_and(|&f| f < lo) {
|
||||
keep.pop_front();
|
||||
}
|
||||
*o = keep.front().map_or(row[x], |&f| row[f]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Premultiplied RGBA at 16 bits (×257): blurs and averages that keep their tones.
|
||||
pub(super) type Px16 = [u16; 4];
|
||||
|
||||
@@ -1061,3 +1080,22 @@ mod tests;
|
||||
mod tests_paint;
|
||||
#[cfg(test)]
|
||||
mod tests_sketch;
|
||||
|
||||
#[cfg(test)]
|
||||
mod window_max_tests {
|
||||
use super::window_max;
|
||||
|
||||
#[test]
|
||||
fn the_sliding_maximum_matches_a_scan_of_each_window() {
|
||||
let row: Vec<f32> = (0..40u32).map(|i| ((i * 7919) % 31) as f32).collect();
|
||||
for radius in [0, 1, 2, 5, 39, 100] {
|
||||
let mut out = vec![0.0; row.len()];
|
||||
window_max(&row, radius, &mut out);
|
||||
for (x, got) in out.iter().enumerate() {
|
||||
let (lo, hi) = (x.saturating_sub(radius), (x + radius).min(row.len() - 1));
|
||||
let want = row[lo..=hi].iter().copied().fold(f32::MIN, f32::max);
|
||||
assert_eq!(*got, want, "radius {radius} at {x}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,9 +184,14 @@ pub fn scribble(path: &PathData, b: Rect, p: &Value) -> PathData {
|
||||
row: u64,
|
||||
}
|
||||
let mut chains: Vec<Chain> = vec![];
|
||||
let mut y = y0 + step / 2.0;
|
||||
let mut row = 0u64;
|
||||
while y < y1 {
|
||||
// Rows are counted, not summed: adding a step that is below the spacing of the numbers at `y`
|
||||
// would never move it.
|
||||
while row <= 2001 {
|
||||
let y = y0 + step / 2.0 + row as f64 * step;
|
||||
if y.partial_cmp(&y1) != Some(std::cmp::Ordering::Less) {
|
||||
break;
|
||||
}
|
||||
let mut xs: Vec<f64> = vec![];
|
||||
for pl in &polys {
|
||||
for w in pl.windows(2) {
|
||||
@@ -225,7 +230,6 @@ pub fn scribble(path: &PathData, b: Rect, p: &Value) -> PathData {
|
||||
}
|
||||
}
|
||||
row += 1;
|
||||
y += step;
|
||||
}
|
||||
let lines: Vec<SubPath> = chains
|
||||
.into_iter()
|
||||
|
||||
Reference in New Issue
Block a user