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:
@@ -33,6 +33,7 @@ import { TONE_SKSL, getToneUniforms, toneUniformArray } from '../utils/toneShade
|
||||
import { CINEMA_SKSL, getCinemaUniforms, cinemaOffFlat } from '../utils/cinemaShader';
|
||||
import { polaroidLayout, wallframeLayout, WALLFRAME_PNG_W, WALLFRAME_PNG_H } from '../utils/frameUtils';
|
||||
import { applyPhotoRotation } from '../utils/skiaImage';
|
||||
import { detectTilt } from '../utils/horizon';
|
||||
|
||||
interface ViewfinderProps {
|
||||
mode: 'camera' | 'library';
|
||||
@@ -54,9 +55,6 @@ interface ViewfinderProps {
|
||||
// on top of that turn.
|
||||
photoRotation?: 0 | 90 | 180 | 270;
|
||||
photoStraighten?: number;
|
||||
// FRAME-tab AUTO: the device's lean in degrees, null while AUTO is off. Only
|
||||
// drives the level line — the photo arrives already counter-rotated.
|
||||
autoRoll?: number | null;
|
||||
// WATERMARK tab: double-tap ON the mark re-opens the text editor (App bumps
|
||||
// a nonce so the panel puts the caret back in its input).
|
||||
onWmEdit?: () => void;
|
||||
@@ -164,6 +162,14 @@ export interface ViewfinderHandle {
|
||||
* fixed-ratio crop is active.
|
||||
*/
|
||||
getCropRatioRect(): CropRect | null;
|
||||
/**
|
||||
* FRAME-tab AUTO straighten: tilt, in degrees, of the dominant near-
|
||||
* horizontal line in the library photo — the horizon it was shot with.
|
||||
* Null when the picture holds no line worth trusting, so App can leave the
|
||||
* fine angle alone. Read from the quarter-turned source WITHOUT the current
|
||||
* straighten, so pressing AUTO twice answers the same both times.
|
||||
*/
|
||||
detectHorizon(): number | null;
|
||||
}
|
||||
|
||||
const noiseEffect = Skia.RuntimeEffect.Make(`
|
||||
@@ -237,7 +243,6 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
wmEditing = false,
|
||||
photoRotation = 0,
|
||||
photoStraighten = 0,
|
||||
autoRoll = null,
|
||||
wmRotation = 0,
|
||||
wallLandscape = false,
|
||||
}: ViewfinderProps,
|
||||
@@ -338,6 +343,10 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
() => applyPhotoRotation(loadedImage, photoRotation, photoStraighten),
|
||||
[loadedImage, photoRotation, photoStraighten]
|
||||
);
|
||||
// Mirror of the two inputs of the above, for detectHorizon: the imperative
|
||||
// handle is captured once (deps [ref]) and must read the LIVE source.
|
||||
const horizonSrcRef = useRef({ image: loadedImage, quarter: photoRotation });
|
||||
horizonSrcRef.current = { image: loadedImage, quarter: photoRotation };
|
||||
// Wall-frame PNG artwork (project root). Loaded lazily so other frames pay
|
||||
// no asset cost; source switches to '' when unselected (useImage → null).
|
||||
const wallImage = useImage(selectedFrame === 'wallframe' ? require('../../wallframe.png') : '');
|
||||
@@ -580,6 +589,12 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
getCropRatioRect() {
|
||||
return cropRatioRectRef.current;
|
||||
},
|
||||
// AUTO straighten: the angle the picture asks for. Computed on demand
|
||||
// (one <=256px thumbnail, tens of ms) — never on a slider drag.
|
||||
detectHorizon() {
|
||||
const src = horizonSrcRef.current;
|
||||
return detectTilt(applyPhotoRotation(src.image, src.quarter, 0));
|
||||
},
|
||||
}),
|
||||
[ref]
|
||||
);
|
||||
@@ -2257,24 +2272,6 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
const cinemaOn = cinemaEffect != null && !!cinemaParams;
|
||||
const useShaderPass = !!imageFitRect && ((toneEffect != null && toneOn) || cinemaOn);
|
||||
|
||||
// FRAME-tab AUTO: a bubble level that follows the device's own lean. The photo
|
||||
// under it is already counter-rotated by the same angle, so this line is the
|
||||
// only place the raw tilt is visible. Amber once within a degree of level.
|
||||
const levelOverlay = autoRoll == null ? null : (
|
||||
<View pointerEvents="none" className="absolute inset-0 items-center justify-center">
|
||||
<View style={{ width: '55%', transform: [{ rotate: `${autoRoll}deg` }] }}>
|
||||
<View style={{ height: 4, backgroundColor: 'rgba(0,0,0,0.5)' }} />
|
||||
<View
|
||||
style={{
|
||||
height: 2,
|
||||
marginTop: -3,
|
||||
backgroundColor: Math.abs(autoRoll) <= 1 ? '#f59e0b' : '#ffffff',
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
// RETRO INSTANT (library): the whole picture is contain-fitted into the
|
||||
// card's photo window — nothing is masked or cropped, and the card is
|
||||
// centered on the black background like a physical print. The window keeps
|
||||
@@ -2350,7 +2347,6 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
onResponderMove={onLibTouchMove}
|
||||
onResponderRelease={onLibTouchEnd}
|
||||
/>
|
||||
{levelOverlay}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -2427,7 +2423,6 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
onResponderMove={onLibTouchMove}
|
||||
onResponderRelease={onLibTouchEnd}
|
||||
/>
|
||||
{levelOverlay}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -2570,7 +2565,6 @@ const Viewfinder = forwardRef<ViewfinderHandle, ViewfinderProps>(function Viewfi
|
||||
onResponderMove={onLibTouchMove}
|
||||
onResponderRelease={onLibTouchEnd}
|
||||
/>
|
||||
{levelOverlay}
|
||||
</>
|
||||
) : (
|
||||
<View className="items-center justify-center p-6">
|
||||
|
||||
Reference in New Issue
Block a user