mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-02 06:27:53 -06:00
Crop: the spiral guide is complete at every aspect ratio
The squares were carved off the crop rectangle itself, so the guide
depended on the crop's shape. A square crop lost everything after the
first arc -- the first subtraction leaves no rectangle to carve, and the
loop stops -- and other ratios kept different amounts. Counting the
quarter-circle arcs:
crop before after
1:1 1 10
phi:1 10 10
3:2 3 10
16:9 4 10
4:5 3 10
Only a golden-ratio rectangle subdivides into squares forever, so that
is the one shape that yields a complete spiral. The construction now
uses a PHI x 1 rectangle whatever the crop is, and the normalisation
already at the end of the function stretches it to fit.
That distorts the curve on a crop of another ratio, which is the price
of the guide staying complete, and is the approach the report proposes.
The portrait transpose and the orientation flips are unchanged, so a
portrait crop still gets a portrait spiral and all four orientations
still mirror it.
Fixes #2393.
This commit is contained in:
@@ -45,12 +45,22 @@ pub fn paths(kind: CropOverlay, orient: u8, w: f32, h: f32) -> Vec<Vec<(f32, f32
|
||||
}
|
||||
}
|
||||
|
||||
/// The golden spiral: squares carved off a `w × h` rectangle (left, top, right, bottom, …), a
|
||||
/// The golden spiral: squares carved off a golden-ratio rectangle (left, top, right, bottom, …), a
|
||||
/// quarter circle in each, joined into one curve; plus the square boundaries.
|
||||
///
|
||||
/// The squares come off a `PHI × 1` rectangle whatever the crop's shape is, and the normalisation
|
||||
/// at the end stretches the result to fit. Carving them off the crop itself made the guide depend
|
||||
/// on the crop: a square crop lost everything after the first arc, because the first subtraction
|
||||
/// left no rectangle to carve, and other ratios gave different numbers of arcs (#2393). Only a
|
||||
/// golden-ratio rectangle subdivides into squares forever, so it is the one shape that gives a
|
||||
/// complete spiral; stretching it distorts the curve on a crop of another ratio, which is the
|
||||
/// price of the guide staying complete and is what the report asks for.
|
||||
fn spiral(w: f32, h: f32) -> Vec<Vec<(f32, f32)>> {
|
||||
// A `PHI × 1` rectangle less a `1 × 1` square is `1 × 1/PHI`, the same shape again.
|
||||
const PHI: f32 = std::f32::consts::GOLDEN_RATIO;
|
||||
// work landscape; transpose back for portrait crops
|
||||
let portrait = h > w;
|
||||
let (rw, rh) = if portrait { (h, w) } else { (w, h) };
|
||||
let (rw, rh) = (PHI, 1.0);
|
||||
let (mut x, mut y, mut cw, mut ch) = (0.0f32, 0.0f32, rw, rh);
|
||||
let mut curve = Vec::new();
|
||||
let mut squares = Vec::new();
|
||||
@@ -128,6 +138,27 @@ mod tests {
|
||||
assert_eq!(paths(CropOverlay::Triangle, 1, w, h)[0], vec![(1.0, 0.0), (0.0, 1.0)]);
|
||||
}
|
||||
|
||||
/// The spiral is the same complete guide at every crop shape. Carving the squares off the
|
||||
/// crop itself made it depend on the crop: a square kept one arc of ten, 3:2 kept three and
|
||||
/// 16:9 four, so changing the crop truncated the guide (#2393).
|
||||
#[test]
|
||||
fn the_spiral_is_complete_at_every_aspect_ratio() {
|
||||
let shapes = [(1.0f32, 1.0f32), (1.618, 1.0), (1.0, 1.618), (3.0, 2.0), (2.0, 3.0), (16.0, 9.0), (9.0, 16.0), (4.0, 5.0)];
|
||||
let (squares, points) = {
|
||||
let p = paths(CropOverlay::Spiral, 0, 1.618, 1.0);
|
||||
(p.len() - 1, p.last().unwrap().len())
|
||||
};
|
||||
assert!(squares >= 8, "the golden rectangle itself gives a long spiral: {squares}");
|
||||
for (w, h) in shapes {
|
||||
for o in 0..4 {
|
||||
let p = paths(CropOverlay::Spiral, o, w, h);
|
||||
assert_eq!(p.len() - 1, squares, "{w}x{h} orient {o}: {} squares, not {squares}", p.len() - 1);
|
||||
assert_eq!(p.last().unwrap().len(), points, "{w}x{h} orient {o}: a shorter curve");
|
||||
assert!(in_unit(&p), "{w}x{h} orient {o}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spiral_is_one_continuous_curve() {
|
||||
for (w, h) in [(1.618, 1.0), (1.0, 1.618), (3.0, 2.0)] {
|
||||
|
||||
Reference in New Issue
Block a user