mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-27 09:42:00 +01:00
* skip redundant smart object resamples and per-pixel reads * resample smart objects only when their re-render fails --------- Co-authored-by: Brandon Thomas <echelon@gmail.com>
78 lines
3.1 KiB
Rust
78 lines
3.1 KiB
Rust
use std::sync::Arc;
|
|
|
|
use photocraft_doc::{LayerContent, SmartObject, SmartSource};
|
|
use photocraft_engine::{EngineError, Session};
|
|
use serde_json::{Value, json};
|
|
|
|
fn deny_blur(id: &str, _: &Value) -> photocraft_engine::Result<()> {
|
|
if id == "filter.blur.gaussianBlur" { Err(EngineError::Other("denied".into())) } else { Ok(()) }
|
|
}
|
|
|
|
fn edits() -> [(&'static str, Value); 2] {
|
|
[("edit.transform", json!({"scaleX": 50, "scaleY": 100})), ("image.imageSize", json!({"width": 100, "height": 50}))]
|
|
}
|
|
|
|
fn blurred_smart_object() -> Session {
|
|
let mut s = Session::new();
|
|
s.execute("file.new", json!({"width": 200, "height": 100})).unwrap();
|
|
s.execute("layer.new.layer", json!({})).unwrap();
|
|
s.execute("select.rect", json!({"x": 20, "y": 20, "width": 100, "height": 50})).unwrap();
|
|
s.execute("edit.fill", json!({"contents": "color", "color": "#ff0000"})).unwrap();
|
|
s.execute("select.deselect", json!({})).unwrap();
|
|
s.execute("layer.smartObjects.convertToSmartObject", json!({})).unwrap();
|
|
s.execute("filter.blur.gaussianBlur", json!({"radius": 2})).unwrap();
|
|
s
|
|
}
|
|
|
|
fn cache_width(s: &Session) -> i32 {
|
|
let st = s.active().unwrap();
|
|
let LayerContent::Smart(sm) = &st.doc.layer(st.active_layer.unwrap()).unwrap().content else { panic!("not a smart object") };
|
|
sm.cache.as_ref().unwrap().content_bounds().width() as i32
|
|
}
|
|
|
|
fn edit_smart_object(s: &mut Session, edit: impl FnOnce(&mut SmartObject)) {
|
|
let st = s.active_mut().unwrap();
|
|
let id = st.active_layer.unwrap();
|
|
let LayerContent::Smart(sm) = &mut Arc::make_mut(&mut st.doc).layer_mut(id).unwrap().content else { panic!("not a smart object") };
|
|
edit(sm);
|
|
}
|
|
|
|
#[test]
|
|
fn a_smart_object_whose_filters_are_denied_keeps_the_resampled_fallback() {
|
|
for (cmd, params) in edits() {
|
|
let mut s = blurred_smart_object();
|
|
let before = cache_width(&s);
|
|
s.authorize = Some(deny_blur);
|
|
s.execute(cmd, params).unwrap();
|
|
let after = cache_width(&s);
|
|
assert!(after < before * 3 / 4, "{cmd}: the fallback stayed at {after} px wide (was {before})");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_smart_object_that_fails_to_re_render_keeps_the_resampled_fallback() {
|
|
let unrenderable =
|
|
[SmartSource::Linked { path: "missing.psd".into() }, SmartSource::Embedded { file_name: "broken.png".into(), bytes: Arc::new(vec![0; 16]) }];
|
|
for (cmd, params) in edits() {
|
|
for source in &unrenderable {
|
|
let mut s = blurred_smart_object();
|
|
let before = cache_width(&s);
|
|
edit_smart_object(&mut s, |sm| sm.source = source.clone());
|
|
s.execute(cmd, params.clone()).unwrap();
|
|
let after = cache_width(&s);
|
|
assert!(after < before * 3 / 4, "{cmd} with {source:?}: the fallback stayed at {after} px wide (was {before})");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn an_allowed_smart_object_still_re_renders_at_the_new_size() {
|
|
for (cmd, params) in edits() {
|
|
let mut s = blurred_smart_object();
|
|
let before = cache_width(&s);
|
|
s.execute(cmd, params).unwrap();
|
|
let after = cache_width(&s);
|
|
assert!(after < before * 3 / 4, "{cmd}: {after} px wide (was {before})");
|
|
}
|
|
}
|