fix(4x3): force frame stream to 4:3 sensor aspect via targetResolution
This commit is contained in:
@@ -155,6 +155,35 @@ export default function Viewfinder({
|
|||||||
// enough to pick the device up — no manual polling needed.
|
// enough to pick the device up — no manual polling needed.
|
||||||
const device = useCameraDevice('back');
|
const device = useCameraDevice('back');
|
||||||
|
|
||||||
|
// CameraX negotiates the CameraFrameOutput aspect on its own (default 16:9 on
|
||||||
|
// this device), which center-crops the 4:3 sensor — so the ratio band could
|
||||||
|
// never display 100% of the sensor pixels. Force the frame stream to a 4:3
|
||||||
|
// (sensor aspect) resolution supported for 'stream' outputs; the photo output
|
||||||
|
// keeps its own full-res 4:3 size and FOV, so the viewfinder then matches the
|
||||||
|
// captured photo exactly. Best supported 4:3 size closest to 1280x960
|
||||||
|
// (720p-class, keeps the frame pipeline light); CameraX may still round it.
|
||||||
|
const frameTarget = useMemo(() => {
|
||||||
|
if (!device) return undefined;
|
||||||
|
try {
|
||||||
|
let best: { width: number; height: number } | undefined;
|
||||||
|
let bestScore = Infinity;
|
||||||
|
for (const s of device.getSupportedResolutions('stream')) {
|
||||||
|
const w = s.width;
|
||||||
|
const h = s.height;
|
||||||
|
if (!(w > 0 && h > 0)) continue;
|
||||||
|
if (Math.abs(w / h - 4 / 3) > 0.03) continue;
|
||||||
|
const score = Math.abs(Math.sqrt(w * h) - Math.sqrt(1280 * 960));
|
||||||
|
if (score < bestScore) {
|
||||||
|
bestScore = score;
|
||||||
|
best = { width: w, height: h };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best ?? undefined;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}, [device]);
|
||||||
|
|
||||||
const skiaImage = useImage(libraryImageUri || '');
|
const skiaImage = useImage(libraryImageUri || '');
|
||||||
// Wall-frame PNG artwork (project root). Loaded lazily so other frames pay
|
// Wall-frame PNG artwork (project root). Loaded lazily so other frames pay
|
||||||
// no asset cost; source switches to '' when unselected (useImage → null).
|
// no asset cost; source switches to '' when unselected (useImage → null).
|
||||||
@@ -629,7 +658,8 @@ export default function Viewfinder({
|
|||||||
' ratio=' + aspectRatio +
|
' ratio=' + aspectRatio +
|
||||||
' frame=' + selectedFrame +
|
' frame=' + selectedFrame +
|
||||||
' view=' + vw + 'x' + vh +
|
' view=' + vw + 'x' + vh +
|
||||||
' feed=' + (cameraPhotoRect ? JSON.stringify([1, vw / vh, cameraPhotoRect.x / vw, cameraPhotoRect.y / vh, cameraPhotoRect.w / vw, cameraPhotoRect.h / vh]) : 'null')
|
' feed=' + (cameraPhotoRect ? JSON.stringify([1, vw / vh, cameraPhotoRect.x / vw, cameraPhotoRect.y / vh, cameraPhotoRect.w / vw, cameraPhotoRect.h / vh]) : 'null') +
|
||||||
|
' frameTarget=' + (frameTarget ? frameTarget.width + 'x' + frameTarget.height : 'null')
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [region, regionBand, aspectRatio, selectedFrame, cameraPhotoRect]);
|
}, [region, regionBand, aspectRatio, selectedFrame, cameraPhotoRect]);
|
||||||
@@ -956,6 +986,7 @@ export default function Viewfinder({
|
|||||||
isActive={appActive}
|
isActive={appActive}
|
||||||
outputs={cameraOutputs}
|
outputs={cameraOutputs}
|
||||||
pixelFormat="yuv"
|
pixelFormat="yuv"
|
||||||
|
targetResolution={frameTarget}
|
||||||
onStarted={() => setCameraActive(true)}
|
onStarted={() => setCameraActive(true)}
|
||||||
onFrame={handleFrame}
|
onFrame={handleFrame}
|
||||||
onError={onSessionError}
|
onError={onSessionError}
|
||||||
|
|||||||
Reference in New Issue
Block a user