mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 05:57:09 -06:00
@@ -71,8 +71,9 @@ impl Frame {
|
||||
ow,
|
||||
oh,
|
||||
crop,
|
||||
flip_h: apply_crop && s.crop.flip_h,
|
||||
flip_v: apply_crop && s.crop.flip_v,
|
||||
// a flip is not part of the crop: the crop tool's uncropped view shows it too
|
||||
flip_h: s.crop.flip_h,
|
||||
flip_v: s.crop.flip_v,
|
||||
warp: (!warp.is_identity()).then_some(warp),
|
||||
view: None,
|
||||
};
|
||||
@@ -387,4 +388,22 @@ mod tests {
|
||||
assert!((out.get(0, 0)[0] - 3.0).abs() < 1e-5);
|
||||
assert!((out.get(3, 0)[0] - 0.0).abs() < 1e-5);
|
||||
}
|
||||
|
||||
/// The crop tool's uncropped frame keeps the flips: Flip H / Flip V show while cropping, and
|
||||
/// screen points map back through the mirror (the crop box is drawn and dragged right).
|
||||
#[test]
|
||||
fn uncropped_frame_keeps_the_flips() {
|
||||
let mut s = DevelopSettings::default();
|
||||
s.crop.flip_h = true;
|
||||
s.crop.flip_v = true;
|
||||
s.crop.geometry.rect = Rect::new(0.1, 0.2, 0.6, 0.9);
|
||||
let src = Rgb32f::from_fn(4, 2, |x, y| [x as f32, y as f32, 0.0]);
|
||||
let f = Frame::new(4, 2, &s, false);
|
||||
let out = f.sample(&src, 4, 2);
|
||||
assert!((out.get(0, 0)[0] - 3.0).abs() < 1e-5 && (out.get(0, 0)[1] - 1.0).abs() < 1e-5, "{:?}", out.get(0, 0));
|
||||
assert!((out.get(3, 1)[0] - 0.0).abs() < 1e-5 && (out.get(3, 1)[1] - 0.0).abs() < 1e-5, "{:?}", out.get(3, 1));
|
||||
// the whole image, mirrored: output top-left is the image's bottom-right
|
||||
let p = f.out_to_norm(400, 200).apply(Point::new(0.0, 0.0));
|
||||
assert!((p.x - 1.0).abs() < 1e-9 && (p.y - 1.0).abs() < 1e-9, "{p:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1255,11 +1255,13 @@ fn crop_overlay(app: &mut LightcraftApp, ui: &mut egui::Ui, resp: &egui::Respons
|
||||
let shade = Color32::from_black_alpha(150);
|
||||
let mut mesh = egui::epaint::Mesh::default();
|
||||
let outer = [img.left_top(), img.right_top(), img.right_bottom(), img.left_bottom()];
|
||||
// the box's corners in screen order (a flipped photo shows its top-left corner elsewhere)
|
||||
let on_screen = flipped_corners(&pts, frame.flip_h, frame.flip_v);
|
||||
for i in 0..4 {
|
||||
let a = outer[i];
|
||||
let b = outer[(i + 1) % 4];
|
||||
let c = pts[(i + 1) % 4];
|
||||
let dd = pts[i];
|
||||
let c = on_screen[(i + 1) % 4];
|
||||
let dd = on_screen[i];
|
||||
let base = mesh.vertices.len() as u32;
|
||||
for v in [a, b, c, dd] {
|
||||
mesh.vertices.push(egui::epaint::Vertex { pos: v, uv: Pos2::ZERO, color: shade });
|
||||
@@ -1509,6 +1511,19 @@ fn to_straight(n: Point, angle: f64, frame: &Frame) -> Point {
|
||||
Point::new(r.x / w, r.y / h)
|
||||
}
|
||||
|
||||
/// A crop box's corners (top-left, top-right, bottom-right, bottom-left of the unflipped photo)
|
||||
/// in the order they appear on screen when the photo is shown flipped.
|
||||
fn flipped_corners(pts: &[Pos2], flip_h: bool, flip_v: bool) -> [Pos2; 4] {
|
||||
let at = |i: usize| pts.get(i).copied().unwrap_or(Pos2::ZERO);
|
||||
let order = match (flip_h, flip_v) {
|
||||
(false, false) => [0, 1, 2, 3],
|
||||
(true, false) => [1, 0, 3, 2],
|
||||
(false, true) => [3, 2, 1, 0],
|
||||
(true, true) => [2, 3, 0, 1],
|
||||
};
|
||||
order.map(at)
|
||||
}
|
||||
|
||||
fn frame_crop_quad(d: &DevelopSettings, frame: &Frame) -> [Point; 4] {
|
||||
let f = Frame { crop: d.crop.geometry, ..frame.clone() };
|
||||
f.crop_quad_norm()
|
||||
@@ -2237,7 +2252,8 @@ mod tests {
|
||||
assert!(near(super::rotate_cursor_turn(&tilted, rot(pos2(200.0, 60.0)), false), 30f32.to_radians()));
|
||||
}
|
||||
|
||||
use super::{film_label, texture_side};
|
||||
use super::{film_label, flipped_corners, texture_side};
|
||||
use egui::pos2;
|
||||
|
||||
/// Issue #652: a native host reports the GPU's texture limit in the first frame's input only.
|
||||
/// The loupe must still know it in every later frame (it read the raw input, found nothing and
|
||||
@@ -2266,6 +2282,22 @@ mod tests {
|
||||
assert_eq!(film_label("DSC_0001"), "DSC_0001");
|
||||
assert_eq!(film_label(""), "");
|
||||
}
|
||||
|
||||
/// With the photo flipped, the crop box's corners reach the screen mirrored; the dimming
|
||||
/// outside the box pairs each photo corner with the box corner on the same side of the screen
|
||||
/// (it used to pair them in data order, so a flipped photo was dimmed inside the box).
|
||||
#[test]
|
||||
fn flipped_crop_box_corners_come_in_screen_order() {
|
||||
let screen = [pos2(10.0, 10.0), pos2(90.0, 10.0), pos2(90.0, 50.0), pos2(10.0, 50.0)];
|
||||
// data order (top-left, top-right, bottom-right, bottom-left) as each flip shows it
|
||||
let h = [screen[1], screen[0], screen[3], screen[2]];
|
||||
let v = [screen[3], screen[2], screen[1], screen[0]];
|
||||
let hv = [screen[2], screen[3], screen[0], screen[1]];
|
||||
assert_eq!(flipped_corners(&screen, false, false), screen);
|
||||
assert_eq!(flipped_corners(&h, true, false), screen);
|
||||
assert_eq!(flipped_corners(&v, false, true), screen);
|
||||
assert_eq!(flipped_corners(&hv, true, true), screen);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -250,7 +250,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts).
|
||||
| LR-CROP-AUTO | Auto straighten | P1 | ✅ | `cmd:crop.autoStraighten` | crop-angle leveling from detected horizon/plumb lines (consensus required) |
|
||||
| LR-CROP-ANGLE | Angle slider | P0 | ✅ | `ctl:crop.angle`, `crates/ui-egui/src/panels/right.rs` | the Straighten slider; an exact angle can also be typed in the Angle field under it (see LR-CROP-STRAIGHTEN) |
|
||||
| LR-CROP-ROTATE90 | Rotate 90° | P0 | ✅ | `cmd:photo.rotateLeft`, `cmd:photo.rotateRight` | |
|
||||
| LR-CROP-FLIP | Flip | P0 | ✅ | `cmd:photo.flipHorizontal`, `cmd:photo.flipVertical` | |
|
||||
| LR-CROP-FLIP | Flip | P0 | ✅ | `cmd:photo.flipHorizontal`, `cmd:photo.flipVertical`, `crates/pipeline/src/geometry.rs` | the crop tool's uncropped view shows the flips too, with the crop box and its dimming mirrored to match |
|
||||
| LR-CROP-OVERLAY | Crop overlays | P1 | ✅ | `cmd:view.cropOverlay`, `cmd:view.cropOverlayOrientation`, `crates/ui-egui/src/panels/crop_overlay.rs` | thirds, grid, golden ratio, diagonal, triangle, golden spiral (mirrored with ⇧O while cropping); no aspect-ratio overlays |
|
||||
| LR-CROP-ZOOM | Zoom while cropping | P1 | ✅ | `cmd:view.zoom100`, `cmd:view.zoomIn`, `crates/ui-egui/src/panels/detail.rs` | zoom levels apply with the crop tool open (verified: 100% shows the photo at native size with the crop frame) |
|
||||
| LR-CROP-GENEXPAND | Generative expand | OOS | 🚫 | | |
|
||||
|
||||
Reference in New Issue
Block a user