feat(frame): CROP ratios in the library editor, and the framed-photo fixes
CROP chip on the FRAME tab for the library's plain frames: NONE, FREE (drag the box) and the fixed ratios 1:1, 2:3, 3:2, 3:4, 4:3, 16:9. The preview draws the keep-rectangle over the photo and the export honours it pixel-for-pixel, pinching and dragging inside the band to pick the framing. - Viewfinder: build frameRect from the normalised rect so the Skia canvas never sees undefined width/height (nothing to commit until now), keep the watermark drag target across the WATERMARK tab so a zoomed photo no longer resets, and derive the crop band + export rect from the photo fit rect. - exportEngine: crop by fraction rect (or the ratio fallback) before the frame is composited. - Leaving a plain frame clears the crop, and the ratio strip closes with the CROP chip it belongs to.
This commit is contained in:
@@ -4,7 +4,7 @@ import Slider from '@react-native-community/slider';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import { Trash2 } from 'lucide-react-native';
|
||||
import { Skia } from '@shopify/react-native-skia';
|
||||
import { Recipe, ColorAdjustments, CustomWatermark, GpsWatermark, FrameId, BaseFilter } from '../types';
|
||||
import { Recipe, ColorAdjustments, CustomWatermark, GpsWatermark, FrameId, BaseFilter, CropRatio } from '../types';
|
||||
import { FRAMES } from '../utils/frameUtils';
|
||||
import { PARAM_DEFS, ContinuousParamDef } from '../utils/paramDefs';
|
||||
import { FILM_SIMS, filmSimToRecipe } from '../utils/defaultRecipes';
|
||||
@@ -28,7 +28,8 @@ type GroupKey =
|
||||
| 'wmFont'
|
||||
| 'gpsColor'
|
||||
| 'gpsName'
|
||||
| 'gpsTime';
|
||||
| 'gpsTime'
|
||||
| 'crop';
|
||||
|
||||
interface AdjustmentPanelProps {
|
||||
activeTab: TabId;
|
||||
@@ -36,6 +37,12 @@ interface AdjustmentPanelProps {
|
||||
currentRecipeId: string;
|
||||
adjustments: ColorAdjustments;
|
||||
selectedFrame: FrameId;
|
||||
// FRAME tab CROP. Only meaningful on the library's plain frames (the camera
|
||||
// composes through its ratio band, polaroid/wall through their own window),
|
||||
// so App hides the chip everywhere else.
|
||||
cropRatio: CropRatio;
|
||||
onUpdateCropRatio: (ratio: CropRatio) => void;
|
||||
cropEnabled?: boolean;
|
||||
useGeotag: boolean;
|
||||
// RAW (DNG sidecar) / aspect-ratio / metering-mode now live in the gear
|
||||
// Settings sheet (gear between library picker and shutter) — no longer
|
||||
@@ -150,6 +157,9 @@ export default function AdjustmentPanel({
|
||||
currentRecipeId,
|
||||
adjustments,
|
||||
selectedFrame,
|
||||
cropRatio,
|
||||
onUpdateCropRatio,
|
||||
cropEnabled,
|
||||
useGeotag,
|
||||
openParam,
|
||||
onOpenParamChange,
|
||||
@@ -203,6 +213,11 @@ export default function AdjustmentPanel({
|
||||
setShowRecipes(false);
|
||||
setCreateVisible(false);
|
||||
}, [activeTab]);
|
||||
// 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(() => {
|
||||
if (!cropEnabled) setOpenGroup((g) => (g === 'crop' ? null : g));
|
||||
}, [cropEnabled]);
|
||||
|
||||
const toggleParam = (key: string) => {
|
||||
haptic();
|
||||
@@ -416,6 +431,22 @@ export default function AdjustmentPanel({
|
||||
options: [{ v: 'on', d: 'ON' }, { v: 'off', d: 'OFF' }],
|
||||
onPick: (v) => onUpdateGpsWm({ ...gpsWm, showName: v === 'on' }),
|
||||
},
|
||||
crop: {
|
||||
label: 'CROP', off: 'none', value: cropRatio,
|
||||
// 'free' keeps an explicit user-dragged rectangle; the rest center-crop
|
||||
// the photo to the fixed shape the export writes.
|
||||
options: [
|
||||
{ v: 'none', d: 'NONE' },
|
||||
{ v: 'free', d: 'FREE' },
|
||||
{ v: '1:1', d: '1:1' },
|
||||
{ v: '2:3', d: '2:3' },
|
||||
{ v: '3:2', d: '3:2' },
|
||||
{ v: '3:4', d: '3:4' },
|
||||
{ v: '4:3', d: '4:3' },
|
||||
{ v: '16:9', d: '16:9' },
|
||||
],
|
||||
onPick: (v) => onUpdateCropRatio(v as CropRatio),
|
||||
},
|
||||
gpsTime: {
|
||||
label: 'TIME', off: 'ON',
|
||||
value: gpsWm.showTime === false ? 'OFF' : 'ON',
|
||||
@@ -542,6 +573,7 @@ export default function AdjustmentPanel({
|
||||
selectedFrame,
|
||||
(val) => onUpdateFrame(val as FrameId)
|
||||
)}
|
||||
{cropEnabled && renderChip(groupChip('crop'))}
|
||||
{renderChip({
|
||||
key: 'rotate',
|
||||
label: photoRotation === 0 ? 'ROTATE 90' : 'ROTATE 0',
|
||||
|
||||
Reference in New Issue
Block a user