Address review: keep scaled pattern scales within 1..1000 %

scale_effects (Image Size's Scale Styles, Layer Style > Scale Effects, the
Free Transform style preview) now clamps the Pattern Overlay, Bevel & Emboss
Texture and pattern Stroke scales to the 1..1000 % the Layer Style dialog
allows, through one shared photocraft_doc::effects::PATTERN_SCALE_RANGE that
the layer style param parser now uses too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
David MannandClaude Opus 5.5 committed 2026-11-06 01:24:20 +01:00
1 parent 945fec8665
commit 608cedc5eb
4 files changed
+86 -5

No files matched your search

+4
View File
@@ -10,6 +10,10 @@ use serde::{Deserialize, Serialize};
use crate::adjust::CurvePoint;
/// The range of a style's pattern scale as a fraction (1 = 100 %): Pattern Overlay, a pattern
/// Stroke and the Bevel & Emboss Texture all take 1..1000 % in the Layer Style dialog.
pub const PATTERN_SCALE_RANGE: (f32, f32) = (0.01, 10.0);
/// Gradient geometry (fill layers, gradient overlays, gradient strokes).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum GradientStyle {
+13 -3
View File
@@ -151,16 +151,16 @@ pub fn scale_effects(fx: &mut Effects, k: f32) {
b.size *= k;
b.soften *= k;
if let Some(t) = &mut b.texture {
t.scale *= k;
scale_pattern(&mut t.scale, k);
}
}
Effect::PatternOverlay { scale, .. } => *scale *= k,
Effect::PatternOverlay { scale, .. } => scale_pattern(scale, k),
Effect::GradientOverlay { .. } | Effect::ColorOverlay { .. } => {}
}
if let Effect::Stroke(s) = e
&& let FxPaint::Pattern { scale, .. } = &mut s.paint
{
*scale *= k;
scale_pattern(scale, k);
}
}
// Effects changed: the preserved PSD block no longer applies.
@@ -169,6 +169,16 @@ pub fn scale_effects(fx: &mut Effects, k: f32) {
}
}
/// Scale a style's pattern scale, kept within the 1..1000 % the Layer Style dialog and the
/// reference application allow (a 10 % pattern reduced to 5 % stays at 1 %).
fn scale_pattern(scale: &mut f32, k: f32) {
let (min, max) = photocraft_doc::effects::PATTERN_SCALE_RANGE;
let v = *scale * k;
if !v.is_nan() {
*scale = v.clamp(min, max);
}
}
/// Image Size's resampling methods; `None` changes the resolution only.
#[derive(Clone, Copy)]
enum Method {
@@ -160,6 +160,72 @@ fn scale_effects_scales_the_bevel_texture_with_the_pattern_overlay() {
assert_eq!((overlay, texture), (Some(0.5), Some(0.5)));
}
/// The pattern scales of Pattern Overlay, the Bevel & Emboss Texture and a pattern Stroke, in
/// that order.
fn pattern_scales(s: &Session) -> (Option<f32>, Option<f32>, Option<f32>) {
let fx = &active(s).effects.items;
let overlay = fx.iter().find_map(|e| match e {
Effect::PatternOverlay { scale, .. } => Some(*scale),
_ => None,
});
let texture = fx.iter().find_map(|e| match e {
Effect::BevelEmboss(b) => b.texture.as_ref().map(|t| t.scale),
_ => None,
});
let stroke = fx.iter().find_map(|e| match e {
Effect::Stroke(st) => match &st.paint {
photocraft_doc::FxPaint::Pattern { scale, .. } => Some(*scale),
_ => None,
},
_ => None,
});
(overlay, texture, stroke)
}
/// A layer with a Pattern Overlay, a Bevel & Emboss Texture and a pattern Stroke, all at `pct` %.
fn pattern_styled(pct: f64) -> Session {
let mut s = session(8, "rgb");
paint(&mut s, disc);
s.execute("layer.layerStyle.patternOverlay", json!({"scale": pct})).unwrap();
s.execute("layer.layerStyle.bevelEmboss", json!({"texture": "Bubbles", "textureScale": pct})).unwrap();
s.execute("layer.layerStyle.stroke", json!({"size": 3})).unwrap();
s.edit("pattern stroke", |doc, active| {
let layer = doc.layer_mut(active.unwrap()).unwrap();
for e in &mut layer.effects.items {
if let Effect::Stroke(st) = e {
st.paint = photocraft_doc::FxPaint::Pattern { name: "Bubbles".into(), id: String::new(), scale: pct as f32 / 100.0 };
}
}
Ok(())
})
.unwrap();
s
}
#[test]
fn scale_effects_keeps_pattern_scales_within_the_dialog_range() {
// 10 % reduced to 1 % would be 0.1 %: the patterns stop at the dialog's 1 %.
let mut s = pattern_styled(10.0);
s.execute("layer.layerStyle.scaleEffects", json!({"scale": 1})).unwrap();
assert_eq!(pattern_scales(&s), (Some(0.01), Some(0.01), Some(0.01)));
// 500 % enlarged to 1000 % would be 5000 %: they stop at 1000 %.
let mut s = pattern_styled(500.0);
s.execute("layer.layerStyle.scaleEffects", json!({"scale": 1000})).unwrap();
assert_eq!(pattern_scales(&s), (Some(10.0), Some(10.0), Some(10.0)));
// Inside the range nothing is clamped: 200 % halved is 100 %.
let mut s = pattern_styled(200.0);
s.execute("layer.layerStyle.scaleEffects", json!({"scale": 50})).unwrap();
assert_eq!(pattern_scales(&s), (Some(1.0), Some(1.0), Some(1.0)));
}
#[test]
fn image_size_scale_styles_keeps_pattern_scales_within_the_dialog_range() {
// 40x30 -> 400x300 with Scale Styles multiplies by 10: 500 % would become 5000 %.
let mut s = pattern_styled(500.0);
s.execute("image.imageSize", json!({"width": 400, "height": 300, "scaleStyles": true})).unwrap();
assert_eq!(pattern_scales(&s), (Some(10.0), Some(10.0), Some(10.0)));
}
#[test]
fn global_light_rejects_numbers_beyond_f32() {
let mut s = session(8, "rgb");
+3 -2
View File
@@ -2,6 +2,7 @@
use photocraft_color::{BlendMode, Color};
use photocraft_doc::adjust::CurvePoint;
use photocraft_doc::effects::PATTERN_SCALE_RANGE;
use photocraft_doc::{
Bevel, BevelStyle, BevelTechnique, Contour, Effect, FxCommon, FxPaint, Glow, GlowSource, GlowTechnique, Gradient, GradientStyle, Satin, Shadow, StrokeFx,
StrokePosition,
@@ -795,7 +796,7 @@ fn overlay_pattern(fx: &mut Effect, p: &Value) {
*id = String::new();
}
if let Some(x) = f_of(p, "scale") {
*scale = (x / 100.0).clamp(0.01, 10.0);
*scale = (x / 100.0).clamp(PATTERN_SCALE_RANGE.0, PATTERN_SCALE_RANGE.1);
}
if let Some(x) = f_of(p, "angle") {
*angle = x;
@@ -903,7 +904,7 @@ fn overlay_bevel(bv: &mut Bevel, p: &Value) {
bv.texture = Some(photocraft_doc::BevelTexture {
name: t.to_string(),
id: t.to_string(),
scale: (f(p, "textureScale", 100.0) / 100.0).clamp(0.01, 10.0),
scale: (f(p, "textureScale", 100.0) / 100.0).clamp(PATTERN_SCALE_RANGE.0, PATTERN_SCALE_RANGE.1),
depth: (f(p, "textureDepth", 100.0) / 100.0).clamp(-10.0, 10.0),
invert: b(p, "textureInvert", false),
link: b(p, "textureLink", true),