Arbitrary rotation starts at the ruler line's straightening angle (#660)
* fix(image): Arbitrary rotation starts at the ruler's straightening angle Fixes #602 * Arbitrary rotation: ignore a non-finite ruler angle (from #659) Co-Authored-By: Alex Gorevski <alex.gorevski@live.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Alex Gorevski <alex.gorevski@live.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1e1f78325c
commit
9d46c8ea80
|
|
@ -629,13 +629,17 @@ fn inscribed(w: f64, h: f64, a: f64) -> (f64, f64) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Clockwise rotation in degrees that turns the ruler line onto its nearest axis.
|
||||
pub fn straighten_angle(r: &Ruler) -> f64 {
|
||||
let (dx, dy) = (r.end[0] - r.start[0], r.end[1] - r.start[1]);
|
||||
let a = (-dy).atan2(dx).to_degrees();
|
||||
a - 90.0 * (a / 90.0).round()
|
||||
}
|
||||
|
||||
fn straighten(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let d = doc(s)?;
|
||||
let r = d.measurement.ruler.ok_or_else(|| EngineError::Other("no ruler line".into()))?;
|
||||
let (dx, dy) = (r.end[0] - r.start[0], r.end[1] - r.start[1]);
|
||||
let a = (-dy).atan2(dx).to_degrees();
|
||||
// Straighten to the nearest axis: rotate clockwise by the remaining angle.
|
||||
let rot = a - 90.0 * (a / 90.0).round();
|
||||
let rot = straighten_angle(&r);
|
||||
if rot.abs() < 1e-9 {
|
||||
return Ok(json!({"angle": 0.0}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -902,6 +902,23 @@ pub(crate) mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arbitrary_rotation_starts_at_the_ruler_angle() {
|
||||
let (mut app, ctx) = app();
|
||||
let open = |app: &mut PhotocraftApp| {
|
||||
let r = crate::menus::invoke(app, &ctx, "image.rotation.arbitrary", json!({})).unwrap();
|
||||
let f = &app.ui.dialog_mut(r["dialog"].as_u64().unwrap()).unwrap().fields;
|
||||
(f["angle"].as_f64().unwrap(), f["direction"].clone())
|
||||
};
|
||||
assert_eq!(open(&mut app), (0.0, json!("cw")));
|
||||
// A line falling to the right straightens counter-clockwise, a near-vertical one to the y axis.
|
||||
for (end, dir) in [([150, 60], "ccw"), ([150, 40], "cw"), ([60, -50], "ccw")] {
|
||||
app.run("image.analysis.rulerTool", json!({"start": [50, 50], "end": end})).unwrap();
|
||||
let (a, d) = open(&mut app);
|
||||
assert!((a - 5.7106).abs() < 1e-3 && d == json!(dir), "{end:?}: {a} {d}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruler_drag_protractor_and_clear() {
|
||||
let (mut app, _) = app();
|
||||
|
|
|
|||
|
|
@ -192,6 +192,9 @@ pub fn open(app: &mut PhotocraftApp, command: &str) -> Option<u64> {
|
|||
};
|
||||
fields.insert(p.key, v);
|
||||
}
|
||||
if command == "image.rotation.arbitrary" {
|
||||
straighten_defaults(app, &mut fields);
|
||||
}
|
||||
if parse_spec(spec.params).iter().any(|p| p.kind == Kind::Document) {
|
||||
// The document picker lists every open document (params refer to them by index).
|
||||
let names: Vec<String> = app.session.documents().iter().map(|d| d.doc.name.clone()).collect();
|
||||
|
|
@ -201,6 +204,18 @@ pub fn open(app: &mut PhotocraftApp, command: &str) -> Option<u64> {
|
|||
Some(id)
|
||||
}
|
||||
|
||||
/// Arbitrary rotation starts at the angle that straightens the ruler line, when there is one.
|
||||
fn straighten_defaults(app: &PhotocraftApp, fields: &mut Map<String, Value>) {
|
||||
let Some(r) = app.session.active().and_then(|d| d.doc.measurement.ruler) else { return };
|
||||
let rot = photocraft_engine::analysis_cmds::straighten_angle(&r);
|
||||
// A ruler read from a damaged file could hold non-finite ends: keep the 0° default then.
|
||||
if !rot.is_finite() {
|
||||
return;
|
||||
}
|
||||
fields.insert("angle".into(), json!(rot.abs()));
|
||||
fields.insert("direction".into(), json!(if rot < 0.0 { "ccw" } else { "cw" }));
|
||||
}
|
||||
|
||||
/// A filter dialog with live preview for `command` whose parameters follow `spec` (registry
|
||||
/// notation) instead of the command's own; `fixed` params (e.g. a plug-in id) are passed through.
|
||||
pub fn open_with_spec(app: &mut PhotocraftApp, command: &str, label: &str, spec: &str, fixed: Map<String, Value>) -> u64 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue