mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
fix(atn): keep Photoshop's other transform interpolation modes on import (#8698)
The .atn importer kept only bicubic, bilinear and nearest from a transform's Intr, so a recorded Bicubic Automatic, Sharper or Smoother Transform Selection replayed with the command's default (bilinear) and re-exported without its choice. The bicubic variants are now kept (the transforms already render them as Bicubic), Automatic becomes Bicubic Automatic and Preserve Details Bicubic, and both transform commands list the variants (#8697). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
Brandon Thomas
parent
2b83be6c81
commit
fc0b40dae5
@@ -1949,7 +1949,15 @@ fn transform_from(d: &Descriptor) -> Mapped {
|
||||
let whole_pixels = ["dx", "dy"].iter().all(|k| p.get(*k).and_then(Value::as_f64).is_none_or(|v| v.fract() == 0.0));
|
||||
let geometry_only = p.keys().all(|k| k == "dx" || k == "dy") && whole_pixels;
|
||||
// The resampling applies to a selection as much as to a layer (#7515).
|
||||
let interpolation = get_enum(d, "Intr").and_then(|i| lookup(INTERPOLATION, &i)).filter(|i| matches!(*i, "bicubic" | "bilinear" | "nearest"));
|
||||
// Photoshop's bicubic variants are kept (the transforms render them as Bicubic, and they export
|
||||
// back unchanged); Automatic and Preserve Details become the nearest bicubic choice. Dropping
|
||||
// them fell back to the command's default, bilinear for a selection (#8697).
|
||||
let interpolation = get_enum(d, "Intr").and_then(|i| lookup(INTERPOLATION, &i)).and_then(|i| match i {
|
||||
"bicubic" | "bilinear" | "nearest" | "bicubicSharper" | "bicubicSmoother" | "bicubicAutomatic" => Some(i),
|
||||
"automatic" => Some("bicubicAutomatic"),
|
||||
"preserveDetails" => Some("bicubic"),
|
||||
_ => None,
|
||||
});
|
||||
match (ref_class(r).as_deref(), target(r)) {
|
||||
(Some("Chnl"), Target::Property(prop)) if prop == "fsel" => {
|
||||
if let Some(i) = interpolation {
|
||||
|
||||
@@ -2053,6 +2053,28 @@ fn options_the_descriptors_cannot_carry_refuse_strict_export() {
|
||||
}
|
||||
}
|
||||
|
||||
/// #8697: Photoshop's other transform interpolation modes survive import: its bicubic variants
|
||||
/// are kept (they render as Bicubic and export back unchanged), Automatic becomes Bicubic
|
||||
/// Automatic and Preserve Details Bicubic; before, they fell back to the command's default.
|
||||
#[test]
|
||||
fn photoshop_transform_interpolation_modes_survive_import() {
|
||||
let one = |id: &str, p: Value| Action { name: "Probe".into(), steps: vec![(id.into(), p)], ..Default::default() };
|
||||
for id in ["select.transformSelection", "edit.transform"] {
|
||||
for (mode, imported) in [
|
||||
("bicubicSharper", "bicubicSharper"),
|
||||
("bicubicSmoother", "bicubicSmoother"),
|
||||
("bicubicAutomatic", "bicubicAutomatic"),
|
||||
("automatic", "bicubicAutomatic"),
|
||||
("preserveDetails", "bicubic"),
|
||||
] {
|
||||
let (bytes, _) = export("Audit8", &[&one(id, json!({"scaleX": 140, "interpolation": mode}))], &[], false).unwrap();
|
||||
let steps = import(&bytes).unwrap().actions[0].steps.clone();
|
||||
let got = steps.first().and_then(|(_, p)| p.get("interpolation")).and_then(Value::as_str).map(str::to_owned);
|
||||
assert_eq!(got.as_deref(), Some(imported), "{id} {mode}: {steps:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// #8531: a partial `layer.setAdjustment` of Color Balance or Hue/Saturation can't be written
|
||||
/// (their descriptors carry every setting, filling absent ones with defaults), so strict export
|
||||
/// refuses it instead of replaying an edit that also resets the rest. Complete ones still export.
|
||||
|
||||
@@ -250,7 +250,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
label: "Transform Selection",
|
||||
menu: &["Select"],
|
||||
shortcut: None,
|
||||
params: r##"{"rect":[x0,y0,x1,y1]? (frame; default = selection bounds),"quad"|"corners":[[x,y]×4]? (where the frame's corners go: distort/perspective),"matrix":[a,b,c,d,e,f]?,"scaleX":%=100,"scaleY":%=100,"rotate":deg=0,"skewX":deg=0,"skewY":deg=0,"dx":px=0,"dy":px=0,"reference":"center|topLeft|top|topRight|left|right|bottomLeft|bottom|bottomRight"|[x,y]="center","style"|"mesh"|"grid"|"warp":… (warp, as edit.transform.warp),"interpolation":"bilinear|bicubic|nearest"="bilinear"}"##,
|
||||
params: r##"{"rect":[x0,y0,x1,y1]? (frame; default = selection bounds),"quad"|"corners":[[x,y]×4]? (where the frame's corners go: distort/perspective),"matrix":[a,b,c,d,e,f]?,"scaleX":%=100,"scaleY":%=100,"rotate":deg=0,"skewX":deg=0,"skewY":deg=0,"dx":px=0,"dy":px=0,"reference":"center|topLeft|top|topRight|left|right|bottomLeft|bottom|bottomRight"|[x,y]="center","style"|"mesh"|"grid"|"warp":… (warp, as edit.transform.warp),"interpolation":"bilinear|bicubic|nearest|bicubicSharper|bicubicSmoother|bicubicAutomatic"="bilinear" (Photoshop's bicubic variants render as Bicubic)}"##,
|
||||
enabled: has_selection,
|
||||
run: transform_selection,
|
||||
journal: true,
|
||||
@@ -297,6 +297,26 @@ mod tests {
|
||||
s
|
||||
}
|
||||
|
||||
/// #8697: Photoshop's bicubic variants are accepted and render as Bicubic, for a selection
|
||||
/// and for a layer (an imported action keeps them).
|
||||
#[test]
|
||||
fn photoshop_bicubic_variants_transform_as_bicubic() {
|
||||
let run = |id: &str, interpolation: &str| {
|
||||
let mut s = landscape(8);
|
||||
s.execute("select.rect", json!({"x": 20, "y": 10, "width": 60, "height": 40, "feather": 3})).unwrap();
|
||||
s.execute(id, json!({"scaleX": 137, "scaleY": 81, "rotate": 7, "interpolation": interpolation})).unwrap();
|
||||
let st = s.active().unwrap();
|
||||
(st.doc.selection.clone(), st.doc.layers.iter().map(|l| l.surface().cloned()).collect::<Vec<_>>())
|
||||
};
|
||||
for id in ["select.transformSelection", "edit.transform"] {
|
||||
let bicubic = run(id, "bicubic");
|
||||
assert_ne!(bicubic, run(id, "bilinear"), "{id}: the interpolation matters here");
|
||||
for variant in ["bicubicSharper", "bicubicSmoother", "bicubicAutomatic"] {
|
||||
assert_eq!(run(id, variant), bicubic, "{id} {variant}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sky_selects_the_sky_at_every_depth() {
|
||||
for depth in [8, 16, 32] {
|
||||
|
||||
@@ -859,7 +859,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
label: "Free Transform",
|
||||
menu: &[],
|
||||
shortcut: None,
|
||||
params: r##"{"layer":id?,"rect":[x0,y0,x1,y1]? (source frame; default = layer content ∩ selection),"quad":[[x,y]×4]? (where the frame's corners go, clockwise from top-left),"matrix":[a,b,c,d,e,f]? (affine alternative),"scaleX":%?,"scaleY":%?,"rotate":deg?,"dx":px?,"dy":px? (relative alternative: scale and rotate about the frame's centre, then move, as a Photoshop action records a Free Transform),"interpolation":"bicubic|bilinear|nearest"="bicubic","target":"pixels"|"mask"|"quickMask"|{"channel":i}=Channels panel target (an unlinked mask, an alpha channel or the Quick Mask transforms alone; a linked mask moves with its layer)}"##,
|
||||
params: r##"{"layer":id?,"rect":[x0,y0,x1,y1]? (source frame; default = layer content ∩ selection),"quad":[[x,y]×4]? (where the frame's corners go, clockwise from top-left),"matrix":[a,b,c,d,e,f]? (affine alternative),"scaleX":%?,"scaleY":%?,"rotate":deg?,"dx":px?,"dy":px? (relative alternative: scale and rotate about the frame's centre, then move, as a Photoshop action records a Free Transform),"interpolation":"bicubic|bilinear|nearest|bicubicSharper|bicubicSmoother|bicubicAutomatic"="bicubic" (Photoshop's bicubic variants render as Bicubic),"target":"pixels"|"mask"|"quickMask"|{"channel":i}=Channels panel target (an unlinked mask, an alpha channel or the Quick Mask transforms alone; a linked mask moves with its layer)}"##,
|
||||
enabled: has_layer,
|
||||
journal: true,
|
||||
run: transform,
|
||||
|
||||
Reference in New Issue
Block a user