feat(frame): read AUTO straighten from the photo, not the accelerometer
EXIF Orientation only knows 0/90/180/270, so a library still carries no record of how the camera was held. The sensor had nothing to offer. AUTO now measures the dominant line in the picture itself: src/utils/horizon.ts runs a shear-projection search (coarse 1 deg over -45..45, then a 0.5 deg refine) on a <=256px thumbnail, bails when no line's score beats 3x the median, and returns the tilt in degrees. App applies photoStraighten = -tilt, so preview and export share one number exactly as the slider did. Removes expo-sensors wiring, the horizonRoll state, effectiveStraighten and the bubble-level overlay (autoRoll prop) from App/AdjustmentPanel/ Viewfinder. expo-sensors stays in package.json.
This commit is contained in:
@@ -89,8 +89,9 @@ interface AdjustmentPanelProps {
|
||||
photoStraighten: number;
|
||||
onPickRotation: (deg: 0 | 90 | 180 | 270) => void;
|
||||
onStraighten: (deg: number) => void;
|
||||
autoHorizon: boolean;
|
||||
onAutoHorizon: (on: boolean) => void;
|
||||
// FRAME-tab AUTO: ask App to read the tilt of the photo itself and set the
|
||||
// STRAIGHTEN angle that cancels it. A one-shot, not a mode.
|
||||
onAutoStraighten: () => void;
|
||||
// FRAME tab (WALL FRAME only): hang the artwork landscape instead of the
|
||||
// default portrait rotation.
|
||||
wallLandscape: boolean;
|
||||
@@ -122,9 +123,6 @@ interface AdjustmentPanelProps {
|
||||
// FRAME → WATERMARK sub-panel is open: App needs this to arm the mark's
|
||||
// drag/pinch layer on the image (the old `activeTab === 'watermark'`).
|
||||
onWmPanelChange: (open: boolean) => void;
|
||||
// The ROTATE strip (or its STRAIGHTEN row) is open: App needs this to keep
|
||||
// the image's bubble-level line on screen only while that strip is up.
|
||||
onRotatePanelChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
interface ChipDef {
|
||||
@@ -225,8 +223,7 @@ export default function AdjustmentPanel({
|
||||
photoStraighten,
|
||||
onPickRotation,
|
||||
onStraighten,
|
||||
autoHorizon,
|
||||
onAutoHorizon,
|
||||
onAutoStraighten,
|
||||
wallLandscape,
|
||||
onToggleWallLandscape,
|
||||
onToggleGeotag,
|
||||
@@ -241,7 +238,6 @@ export default function AdjustmentPanel({
|
||||
wmRotation,
|
||||
onRotateWm,
|
||||
onWmPanelChange,
|
||||
onRotatePanelChange,
|
||||
}: AdjustmentPanelProps) {
|
||||
// Double-tap on the mark in the viewfinder bumps wmEditNonce -> focus the
|
||||
// input again so the text can be retyped after the keyboard was dismissed.
|
||||
@@ -277,12 +273,6 @@ export default function AdjustmentPanel({
|
||||
useEffect(() => {
|
||||
onWmPanelChange(openGroup === 'wm');
|
||||
}, [openGroup]);
|
||||
// The bubble-level line on the image belongs to the ROTATE strip: it shows
|
||||
// while that strip (or its STRAIGHTEN row) is open and leaves with the next
|
||||
// chip, so it never lingers over the photo after the user moved on.
|
||||
useEffect(() => {
|
||||
onRotatePanelChange(openGroup === 'rotate' || openParam === 'straighten');
|
||||
}, [openGroup, openParam]);
|
||||
// CROP is a plain-frame tool: the moment the selected frame drops it the chip
|
||||
// unmounts, so the ratio strip must close with it (no orphan CROP row).
|
||||
useEffect(() => {
|
||||
@@ -593,12 +583,12 @@ export default function AdjustmentPanel({
|
||||
],
|
||||
onPick: (v) => onUpdateCropRatio(v as CropRatio),
|
||||
},
|
||||
// ROTATE: the quarter turns, the fine STRAIGHTEN slider and AUTO horizon in
|
||||
// one strip. Its RESET undoes this parameter only (turn + angle + auto).
|
||||
// ROTATE: the quarter turns, the fine STRAIGHTEN slider and the AUTO read
|
||||
// in one strip. Its RESET undoes this parameter only (turn + angle).
|
||||
rotate: {
|
||||
label: 'ROTATE', off: '0',
|
||||
// 'straighten' while its slider row is open, so that chip highlights.
|
||||
value: openParam === 'straighten' ? 'straighten' : autoHorizon ? 'auto' : String(photoRotation),
|
||||
value: openParam === 'straighten' ? 'straighten' : String(photoRotation),
|
||||
options: [
|
||||
{ v: 'reset', d: 'RESET' },
|
||||
{ v: 'auto', d: 'AUTO' },
|
||||
@@ -612,15 +602,13 @@ export default function AdjustmentPanel({
|
||||
if (v === 'reset') {
|
||||
onPickRotation(0);
|
||||
onStraighten(0);
|
||||
onAutoHorizon(false);
|
||||
} else if (v === 'straighten') {
|
||||
toggleParam('straighten');
|
||||
} else if (v === 'auto') {
|
||||
// AUTO is "snap to the sensor horizon": the manual angle is dropped
|
||||
// first, so enabling it with a hand-set offset really levels the
|
||||
// photo (re-tapping with it on still switches it back off).
|
||||
if (!autoHorizon) onStraighten(0);
|
||||
onAutoHorizon(!autoHorizon);
|
||||
// AUTO reads the photo and REPLACES the fine angle with the one
|
||||
// that levels its dominant line, so pressing it twice is the same
|
||||
// as pressing it once.
|
||||
onAutoStraighten();
|
||||
} else {
|
||||
onPickRotation(Number(v) as 0 | 90 | 180 | 270);
|
||||
}
|
||||
@@ -671,19 +659,17 @@ export default function AdjustmentPanel({
|
||||
label: wbTempLabel(),
|
||||
});
|
||||
|
||||
// ROTATE's chip names every part of the angle it applies: the turn (dropped
|
||||
// when it is 0 and AUTO is doing the work), the horizon mode, the fine angle.
|
||||
// ROTATE's chip names every part of the angle it applies: the turn and the
|
||||
// fine angle AUTO (or the slider) left on it.
|
||||
const rotateChip = (): ChipDef => {
|
||||
const parts = [
|
||||
photoRotation !== 0 || !autoHorizon ? `ROTATE ${photoRotation}` : 'ROTATE',
|
||||
autoHorizon ? 'AUTO' : null,
|
||||
`ROTATE ${photoRotation}`,
|
||||
photoStraighten ? `${photoStraighten > 0 ? '+' : ''}${photoStraighten}°` : null,
|
||||
];
|
||||
return {
|
||||
...groupChip('rotate'),
|
||||
label: parts.filter(Boolean).join(' · '),
|
||||
amberValue:
|
||||
openGroup !== 'rotate' && (photoRotation !== 0 || photoStraighten !== 0 || autoHorizon),
|
||||
amberValue: openGroup !== 'rotate' && (photoRotation !== 0 || photoStraighten !== 0),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user