mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-11 00:11:46 +00:00
Merge pull request #12169 from DuBearte/fix/11886-airbrush-rate
Airbrush: about ten dabs a second while held, and a click lays two dabs
This commit is contained in:
5 files changed
+67
-7
No files matched your search
@@ -0,0 +1,47 @@
|
||||
//! #11886: with Airbrush on, a brush held still builds up at about ten dabs a second and a plain
|
||||
//! click lays two dabs, as in Photoshop. Measured there with the default 13 px soft brush at Flow
|
||||
//! 10 %, black on white: the dab centre reads 205 after a click and 26 after a 2 s hold, and 229
|
||||
//! with Airbrush off however long the button is held.
|
||||
|
||||
use photocraft_color::PixelFormat;
|
||||
use photocraft_raster::Surface;
|
||||
|
||||
use crate::{BrushSettings, StrokePoint, render_stroke};
|
||||
|
||||
fn brush(build_up: bool) -> BrushSettings {
|
||||
BrushSettings { size: 13.0, hardness: 0.0, flow: 0.1, pressure_size: false, build_up, color: [0.0, 0.0, 0.0, 1.0], ..BrushSettings::default() }
|
||||
}
|
||||
|
||||
/// The 0–255 value at the dab centre over white after holding the button still for `ms`.
|
||||
fn centre_after_hold(b: &BrushSettings, ms: f64) -> f32 {
|
||||
let a = StrokePoint { time: 0.0, ..StrokePoint::new(30.0, 30.0, 1.0) };
|
||||
let z = StrokePoint { time: ms, ..a };
|
||||
let mut s = Surface::new(PixelFormat::RGBA32F);
|
||||
render_stroke(&mut s, b, &[a, z], None, false, 1.0);
|
||||
255.0 * (1.0 - s.rgba(30, 30)[3])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_default_rate_is_ten_dabs_a_second() {
|
||||
assert_eq!(BrushSettings::default().build_up_rate, 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_click_lays_two_dabs() {
|
||||
let v = centre_after_hold(&brush(true), 0.0);
|
||||
assert!((v - 205.0).abs() <= 5.0, "{v}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_two_second_hold_builds_up_to_the_reference_value() {
|
||||
let v = centre_after_hold(&brush(true), 2000.0);
|
||||
assert!((v - 26.0).abs() <= 8.0, "{v}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn airbrush_off_keeps_one_dab_however_long_it_is_held() {
|
||||
for ms in [0.0, 500.0, 2000.0] {
|
||||
let v = centre_after_hold(&brush(false), ms);
|
||||
assert!((v - 229.0).abs() <= 3.0, "{ms} ms: {v}");
|
||||
}
|
||||
}
|
||||
@@ -656,7 +656,8 @@ impl Default for BrushSettings {
|
||||
noise: false,
|
||||
wet_edges: false,
|
||||
build_up: false,
|
||||
build_up_rate: 20.0,
|
||||
// About ten dabs a second while held still, Photoshop's Airbrush rate (#11886).
|
||||
build_up_rate: 10.0,
|
||||
smoothing: Smoothing::default(),
|
||||
protect_texture: false,
|
||||
locks: SectionLocks::default(),
|
||||
|
||||
@@ -609,7 +609,8 @@ fn build_up_accumulates_over_time() {
|
||||
let (n0, a0) = hold(0.0);
|
||||
let (n1, a1) = hold(500.0);
|
||||
let (n2, a2) = hold(2000.0);
|
||||
assert_eq!((n0, n1, n2), (1, 11, 41));
|
||||
// A click lays two dabs with the airbrush on (#11886), then one per 50 ms at this rate.
|
||||
assert_eq!((n0, n1, n2), (2, 12, 42));
|
||||
assert!(a0 < a1 && a1 < a2, "{a0} {a1} {a2}");
|
||||
// Without build-up, lingering adds nothing.
|
||||
let nb = BrushSettings { build_up: false, ..b };
|
||||
@@ -624,15 +625,15 @@ fn huge_airbrush_time_gap_emits_bounded_catch_up() {
|
||||
let b = BrushSettings { build_up: true, build_up_rate: 1000.0, ..brush() };
|
||||
let a = StrokePoint { time: 0.0, ..StrokePoint::new(20.0, 20.0, 1.0) };
|
||||
let brief_pause = StrokePoint { time: 200.0, ..a };
|
||||
assert_eq!(dabs_of(&b, &[a, brief_pause]).len(), 201, "a 200 ms pause should retain its airbrush build-up");
|
||||
assert_eq!(dabs_of(&b, &[a, brief_pause]).len(), 202, "a 200 ms pause should retain its airbrush build-up (after the click's two dabs)");
|
||||
|
||||
let z = StrokePoint { time: f64::MAX, ..a };
|
||||
let d = dabs_of(&b, &[a, z]);
|
||||
assert!(d.len() <= 2, "a huge pause should resume with one dab, not replay its time backlog: {}", d.len());
|
||||
assert!(d.len() <= 3, "a huge pause should resume with one dab, not replay its time backlog: {}", d.len());
|
||||
assert_eq!(d.last().map(|dab| dab.center), Some(Point::new(20.0, 20.0)));
|
||||
|
||||
let infinite = StrokePoint { time: f64::INFINITY, ..a };
|
||||
assert!(dabs_of(&b, &[a, infinite]).len() <= 1);
|
||||
assert!(dabs_of(&b, &[a, infinite]).len() <= 2, "the click's two dabs, no backlog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -341,6 +341,15 @@ impl PathWalker {
|
||||
self
|
||||
}
|
||||
|
||||
/// The stroke's first point. With the airbrush on it lays two dabs, so a plain click builds
|
||||
/// up as Photoshop's does (#11886).
|
||||
fn emit_first(&mut self, point: StrokePoint, out: &mut impl FnMut(StepInput)) {
|
||||
self.emit(point, out);
|
||||
if self.interval.is_some() {
|
||||
self.emit(point, out);
|
||||
}
|
||||
}
|
||||
|
||||
fn emit(&mut self, point: StrokePoint, out: &mut impl FnMut(StepInput)) {
|
||||
if self.step >= MAX_STEPS_PER_STROKE {
|
||||
return;
|
||||
@@ -369,7 +378,7 @@ impl PathWalker {
|
||||
}
|
||||
}
|
||||
if let Some(f) = self.pending_first.take() {
|
||||
self.emit(f, out);
|
||||
self.emit_first(f, out);
|
||||
}
|
||||
if len > 1e-9 {
|
||||
// Speed spacing needs the segment's duration; without timestamps (a straight ⇧-click
|
||||
@@ -432,7 +441,7 @@ impl PathWalker {
|
||||
|
||||
pub fn finish(&mut self, out: &mut impl FnMut(StepInput)) {
|
||||
if let Some(f) = self.pending_first.take() {
|
||||
self.emit(f, out);
|
||||
self.emit_first(f, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,6 +406,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod airbrush_rate_tests;
|
||||
#[cfg(test)]
|
||||
mod brush_tests;
|
||||
|
||||
|
||||
Reference in new issue
Block a user