mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 15:49:48 +00:00
M6: the missing-image placeholder's stripes stay clean when scaled, and a .fig gives one note (#1646)
This commit is contained in:
1 parent
db41f2cd37
commit
1aab3fa001
2 files changed
+20
-7
No files matched your search
@@ -42,15 +42,18 @@ fn a_missing_image_keeps_its_name_and_shows_as_missing() {
|
||||
}
|
||||
let note = "image fills whose image isn't in the file show a missing-image placeholder and keep its name";
|
||||
assert_eq!(warnings.iter().filter(|w| w.starts_with(note)).count(), 1, "{warnings:?}");
|
||||
assert!(!warnings.iter().any(|w| w.contains("without image data")), "the REST JSON note isn't given too: {warnings:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_placeholder_reads_as_missing_not_as_grey() {
|
||||
let png = image::load_from_memory(missing_image_placeholder().unwrap()).unwrap().to_rgba8();
|
||||
let mut colours: Vec<[u8; 4]> = png.pixels().map(|p| p.0).collect();
|
||||
colours.sort();
|
||||
colours.dedup();
|
||||
assert_eq!(colours.len(), 2, "two tones: stripes over a light ground, not one flat grey");
|
||||
// Two tones (their anti-aliased edges between): darker stripes over a light ground, not one
|
||||
// flat grey.
|
||||
let count = |c: u8| png.pixels().filter(|p| p.0 == [c, c, c, 0xFF]).count();
|
||||
let all = png.pixels().count();
|
||||
let (ground, stripe) = (count(0xE6), count(0xB4));
|
||||
assert!(ground > all / 2 && stripe > all / 8 && ground + stripe > all * 3 / 4, "{ground} ground, {stripe} stripe of {all}");
|
||||
// A real image of one colour is never taken for the placeholder.
|
||||
let flat = figcraft_doc::ImageData { bytes: std::sync::Arc::new(vec![1, 2, 3]), width: 64, height: 64 };
|
||||
assert!(!is_missing_image(&flat));
|
||||
|
||||
@@ -1053,6 +1053,8 @@ impl<'a> Ctx<'a> {
|
||||
|
||||
/// Cross-references that need every layer imported first.
|
||||
fn finish(&mut self) {
|
||||
// A `.fig` import (its sources are taken below) noted each missing image already.
|
||||
let fig = self.fig_sources.is_some();
|
||||
self.resolve_swap_defaults();
|
||||
// Variants share their set's property definitions.
|
||||
for (set, defs) in std::mem::take(&mut self.set_defs) {
|
||||
@@ -1086,7 +1088,7 @@ impl<'a> Ctx<'a> {
|
||||
flows.push(Flow { name, start: id, description: String::new() });
|
||||
}
|
||||
}
|
||||
if self.missing_images > 0 && self.fig_sources.is_none() {
|
||||
if self.missing_images > 0 && !fig {
|
||||
let n = self.missing_images;
|
||||
let (s, verb) = if n == 1 { ("", "shows") } else { ("s", "show") };
|
||||
self.warn(format!(
|
||||
@@ -1472,7 +1474,7 @@ fn sane(p: &BezPath) -> bool {
|
||||
}
|
||||
|
||||
/// Side of the placeholder image (large enough that scaling it up stays flat).
|
||||
const PLACEHOLDER_SIDE: u32 = 64;
|
||||
const PLACEHOLDER_SIDE: u32 = 256;
|
||||
|
||||
/// The PNG standing in for image fills whose bytes aren't available (#1646): light grey crossed
|
||||
/// by darker diagonal stripes, so it reads as an image that is missing, not as a grey the design
|
||||
@@ -1480,8 +1482,16 @@ const PLACEHOLDER_SIDE: u32 = 64;
|
||||
pub fn missing_image_placeholder() -> Option<&'static [u8]> {
|
||||
static PNG: std::sync::OnceLock<Option<Vec<u8>>> = std::sync::OnceLock::new();
|
||||
PNG.get_or_init(|| {
|
||||
// Stripes 6 px wide every 18 px across the diagonal, their edges anti-aliased, so they
|
||||
// stay clean when a fill scales the picture up.
|
||||
let (period, width) = (18.0f32, 6.0f32);
|
||||
let (ground, stripe) = ([0xE6u8, 0xE6, 0xE6], [0xB4u8, 0xB4, 0xB4]);
|
||||
let img = image::RgbaImage::from_fn(PLACEHOLDER_SIDE, PLACEHOLDER_SIDE, |x, y| {
|
||||
if (x + y) % 16 < 5 { image::Rgba([0xB8, 0xB8, 0xB8, 0xFF]) } else { image::Rgba([0xE6, 0xE6, 0xE6, 0xFF]) }
|
||||
let t = ((x + y) as f32 + 1.0) / std::f32::consts::SQRT_2 % period;
|
||||
let inside = if t < width { t.min(width - t) + 0.5 } else { 0.5 - (t - width).min(period - t) };
|
||||
let k = inside.clamp(0.0, 1.0);
|
||||
let mix = |i: usize| (f32::from(ground[i]) + (f32::from(stripe[i]) - f32::from(ground[i])) * k).round() as u8;
|
||||
image::Rgba([mix(0), mix(1), mix(2), 0xFF])
|
||||
});
|
||||
let mut out = Vec::new();
|
||||
img.write_to(&mut std::io::Cursor::new(&mut out), image::ImageFormat::Png).ok()?;
|
||||
|
||||
Reference in new issue
Block a user