Files
Rosary/includes/photo.php
T
pguzman d1615a9e01 Fix crop editor's drag/zoom math not matching its own preview
Reported: dragging toward an image edge (e.g. to reveal a cropped-off
head) stopped short of what the reference preview showed was possible —
the two panels visibly disagreed.

Root cause: transform-origin was set to match the pan position (X%,Y%)
instead of staying at the CSS default (50% 50%, the box's own center).
CSS object-position places the *unzoomed* crop; transform:scale then
magnifies around transform-origin. Tying that origin to X/Y meant zoom
dragged its own anchor point toward whichever edge you'd panned to,
instead of always magnifying what's actually centered in the frame —
harmless near the middle (why initial testing looked fine) but
increasingly wrong the closer you drag to an edge, exactly where you'd
need to go to reach a cropped head. A second, smaller error was in how
the reference-rectangle preview converted focal position to natural-image
coordinates (didn't account for the zoom-independent anchor point).

Fixed both the crop editor's own math and includes/photo.php's
photo_crop_style() (used for every final render — cards, previews) to
drop the origin back to the CSS default and use the correct geometry.
clampFocal() also simplifies to a plain [0,100] clamp — under real
object-position semantics that's always a valid, fully-covered crop at
any zoom >= 1, no image-dimension-dependent math needed.

Verified in a standalone test harness: dragging now reaches all the way
to an image's edges, zoom stays synced between the editor's live frame
and its reference-rectangle preview, and the applied result matches the
editor's preview exactly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 10:28:40 -07:00

31 lines
1.3 KiB
PHP

<?php
/**
* includes/photo.php — shared helper for the photo reposition/zoom feature.
*/
/**
* Build the inline style="" value that applies a session/novena_group row's
* stored crop (photo_focal_x/photo_focal_y/photo_zoom) to an <img> using
* object-fit: cover. Falls back to 50/50/1 (dead center, no zoom — today's
* default behavior) when the row predates this feature or the values are
* otherwise missing, so old photos render exactly as they always have.
*/
function photo_crop_style(array $row): string {
$x = isset($row['photo_focal_x']) && $row['photo_focal_x'] !== null ? (float)$row['photo_focal_x'] : 50.0;
$y = isset($row['photo_focal_y']) && $row['photo_focal_y'] !== null ? (float)$row['photo_focal_y'] : 50.0;
$zoom = isset($row['photo_zoom']) && $row['photo_zoom'] !== null ? (float)$row['photo_zoom'] : 1.0;
$x = max(0, min(100, $x));
$y = max(0, min(100, $y));
$zoom = max(1, min(3, $zoom));
// transform-origin is deliberately left at its CSS default (50% 50% —
// the box's own center), not tied to $x/$y: object-position places the
// unzoomed crop, and the scale transform then magnifies around whatever
// is currently centered, matching the crop editor exactly.
return sprintf(
'object-position:%.2f%% %.2f%%;transform:scale(%.3f);',
$x, $y, $zoom
);
}