feat: Bilder beim Skalieren nicht verzerren (Issue #29)
Enthält die Auswahl ein Bild, skaliert der Eckgriff gleichmäßig, das Seitenverhältnis bleibt erhalten. Mit gedrückter Umschalttaste gilt das auch für Striche und Formen. Der Faktor ist die Projektion der Zugposition auf die Diagonale der Auswahl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+12
-14
@@ -31,7 +31,7 @@ use ink_notes::osk::{self, KeyboardRequester};
|
|||||||
use ink_notes::pdf_doc::PdfRenderService;
|
use ink_notes::pdf_doc::PdfRenderService;
|
||||||
use ink_notes::platform::{self, DisplayBackend};
|
use ink_notes::platform::{self, DisplayBackend};
|
||||||
use ink_notes::selection::{
|
use ink_notes::selection::{
|
||||||
render_selection_overlay, DragMode, SelectionHandleHit, SelectionState,
|
render_selection_overlay, scale_factors, DragMode, SelectionHandleHit, SelectionState,
|
||||||
};
|
};
|
||||||
use ink_notes::settings::{
|
use ink_notes::settings::{
|
||||||
SidebarSide, StylusAction, StylusButton, UiSettings, MAX_STYLUS_BUTTONS,
|
SidebarSide, StylusAction, StylusButton, UiSettings, MAX_STYLUS_BUTTONS,
|
||||||
@@ -649,12 +649,18 @@ fn selection_press(
|
|||||||
..
|
..
|
||||||
},
|
},
|
||||||
_,
|
_,
|
||||||
) => DragMode::Scale {
|
) => {
|
||||||
|
let initial_strokes = page.get_strokes(&selection_state.selected_ids);
|
||||||
|
// Bilder nicht verzerren (Issue #29); Shift hält das Verhältnis auch bei Strichen
|
||||||
|
let keep_aspect = shift || initial_strokes.iter().any(|s| s.is_image());
|
||||||
|
DragMode::Scale {
|
||||||
fixed_corner_page,
|
fixed_corner_page,
|
||||||
start_pos_page,
|
start_pos_page,
|
||||||
initial_box_size,
|
initial_box_size,
|
||||||
initial_strokes: page.get_strokes(&selection_state.selected_ids),
|
initial_strokes,
|
||||||
},
|
keep_aspect,
|
||||||
|
}
|
||||||
|
}
|
||||||
(SelectionHandleHit::Body, Some(p)) => DragMode::Move {
|
(SelectionHandleHit::Body, Some(p)) => DragMode::Move {
|
||||||
start_page: p,
|
start_page: p,
|
||||||
initial_strokes: page.get_strokes(&selection_state.selected_ids),
|
initial_strokes: page.get_strokes(&selection_state.selected_ids),
|
||||||
@@ -729,18 +735,10 @@ fn selection_drag(
|
|||||||
DragMode::Scale {
|
DragMode::Scale {
|
||||||
fixed_corner_page,
|
fixed_corner_page,
|
||||||
start_pos_page,
|
start_pos_page,
|
||||||
|
keep_aspect,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let orig = *start_pos_page - *fixed_corner_page;
|
let factors = scale_factors(*fixed_corner_page, *start_pos_page, page_pt, *keep_aspect);
|
||||||
let new = page_pt - *fixed_corner_page;
|
|
||||||
let factor = |n: f32, o: f32| {
|
|
||||||
if o.abs() > 1.0 {
|
|
||||||
(n / o).clamp(0.05, 20.0)
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let factors = Vec2::new(factor(new.x, orig.x), factor(new.y, orig.y));
|
|
||||||
updated
|
updated
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|s| s.scale(*fixed_corner_page, factors));
|
.for_each(|s| s.scale(*fixed_corner_page, factors));
|
||||||
|
|||||||
@@ -36,9 +36,34 @@ pub enum DragMode {
|
|||||||
start_pos_page: Vec2,
|
start_pos_page: Vec2,
|
||||||
initial_box_size: Vec2,
|
initial_box_size: Vec2,
|
||||||
initial_strokes: Vec<Stroke>,
|
initial_strokes: Vec<Stroke>,
|
||||||
|
/// Seitenverhältnis beibehalten (Bilder in der Auswahl oder Shift gedrückt)
|
||||||
|
keep_aspect: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Skalierungsfaktoren, wenn der Eckgriff von `start` (relativ zur festen Ecke `fixed`)
|
||||||
|
/// nach `current` gezogen wird. Mit `keep_aspect` wird gleichmäßig skaliert: der Faktor ist
|
||||||
|
/// die Projektion der Zugposition auf die Diagonale, damit das Seitenverhältnis bleibt.
|
||||||
|
pub fn scale_factors(fixed: Vec2, start: Vec2, current: Vec2, keep_aspect: bool) -> Vec2 {
|
||||||
|
let orig = start - fixed;
|
||||||
|
let new = current - fixed;
|
||||||
|
if keep_aspect {
|
||||||
|
let len_sq = orig.length_squared();
|
||||||
|
if len_sq <= 1.0 {
|
||||||
|
return Vec2::ONE;
|
||||||
|
}
|
||||||
|
return Vec2::splat((new.dot(orig) / len_sq).clamp(0.05, 20.0));
|
||||||
|
}
|
||||||
|
let factor = |n: f32, o: f32| {
|
||||||
|
if o.abs() > 1.0 {
|
||||||
|
(n / o).clamp(0.05, 20.0)
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Vec2::new(factor(new.x, orig.x), factor(new.y, orig.y))
|
||||||
|
}
|
||||||
|
|
||||||
/// Ergebnis eines Hit-Tests auf Auswahl-Griffe (Handles).
|
/// Ergebnis eines Hit-Tests auf Auswahl-Griffe (Handles).
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum SelectionHandleHit {
|
pub enum SelectionHandleHit {
|
||||||
@@ -523,6 +548,35 @@ mod tests {
|
|||||||
assert!((orig_pos.x - restored_pos.x).abs() < 1e-4);
|
assert!((orig_pos.x - restored_pos.x).abs() < 1e-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scale_factors_free_and_keep_aspect() {
|
||||||
|
let fixed = Vec2::new(0.0, 0.0);
|
||||||
|
let start = Vec2::new(200.0, 100.0);
|
||||||
|
// Frei: Breite und Höhe unabhängig
|
||||||
|
let free = scale_factors(fixed, start, Vec2::new(400.0, 100.0), false);
|
||||||
|
assert_eq!(free, Vec2::new(2.0, 1.0));
|
||||||
|
// Seitenverhältnis fest: gleicher Faktor in beiden Richtungen
|
||||||
|
let locked = scale_factors(fixed, start, Vec2::new(400.0, 100.0), true);
|
||||||
|
assert!((locked.x - locked.y).abs() < 1e-6);
|
||||||
|
assert!((locked.x - 1.8).abs() < 1e-4);
|
||||||
|
// Entlang der Diagonale genau verdoppelt
|
||||||
|
let diag = scale_factors(fixed, start, Vec2::new(400.0, 200.0), true);
|
||||||
|
assert!((diag - Vec2::splat(2.0)).length() < 1e-5);
|
||||||
|
// Über die feste Ecke hinaus: kein Spiegeln
|
||||||
|
let past = scale_factors(fixed, start, Vec2::new(-50.0, -50.0), true);
|
||||||
|
assert_eq!(past, Vec2::splat(0.05));
|
||||||
|
// Senkrechte Linie (Breite 0): frei bleibt x unverändert, fest skaliert beides
|
||||||
|
let line = Vec2::new(0.0, 100.0);
|
||||||
|
assert_eq!(
|
||||||
|
scale_factors(fixed, line, Vec2::new(30.0, 200.0), false),
|
||||||
|
Vec2::new(1.0, 2.0)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
scale_factors(fixed, line, Vec2::new(30.0, 200.0), true),
|
||||||
|
Vec2::splat(2.0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recolor_ignores_images() {
|
fn test_recolor_ignores_images() {
|
||||||
let mut page = Page::new(1, PageBackground::Blank);
|
let mut page = Page::new(1, PageBackground::Blank);
|
||||||
|
|||||||
Reference in New Issue
Block a user