Skip to content

Commit 7e4cfa7

Browse files
committed
feat(fov): use source focal length as reference FOV in Canvas
1 parent e6c7740 commit 7e4cfa7

2 files changed

Lines changed: 40 additions & 9 deletions

File tree

src/app/[locale]/fov-simulator/_components/Canvas.tsx

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import styles from './FovSimulator.module.css'
1313

1414
export type { OverlayOffsets } from './canvasTypes'
1515

16-
const REF_FOV = calcFOV(14, 1.0)
17-
18-
export function Canvas({ lenses, imageIndex, orientation, canvasRef, cleanCanvasRef, distance, showGuides, activeLens, offsets, onOffsetsChange, customImageSrc, sourceImageRef }: CanvasProps) {
16+
export function Canvas({ lenses, imageIndex, orientation, canvasRef, cleanCanvasRef, distance, showGuides, activeLens, offsets, onOffsetsChange, customImageSrc, sourceImageRef, sourceFocalLength }: CanvasProps) {
1917
const imageRef = useRef<HTMLImageElement | null>(null)
2018
const animFrameRef = useRef<number>(0)
2119
const drawnRectsRef = useRef<Rect[]>([])
2220

21+
const refFov = sourceFocalLength ? calcFOV(sourceFocalLength, 1.0) : calcFOV(14, 1.0)
22+
2323
const fovs = lenses.map((lens) => calcFOV(getSensor(lens.sensorId).cropFactor, 1).horizontal === 0 ? calcFOV(lens.focalLength, 1) : calcFOV(lens.focalLength, getSensor(lens.sensorId).cropFactor))
2424

2525
const {
@@ -31,13 +31,13 @@ export function Canvas({ lenses, imageIndex, orientation, canvasRef, cleanCanvas
3131
const w = canvas.width; const h = canvas.height
3232
const isPortrait = orientation === 'portrait'
3333
return fovs.map((fov, i) => {
34-
const ratioW = calcCropRatio(isPortrait ? fov.vertical : fov.horizontal, isPortrait ? REF_FOV.vertical : REF_FOV.horizontal)
35-
const ratioH = calcCropRatio(isPortrait ? fov.horizontal : fov.vertical, isPortrait ? REF_FOV.horizontal : REF_FOV.vertical)
34+
const ratioW = calcCropRatio(isPortrait ? fov.vertical : fov.horizontal, isPortrait ? refFov.vertical : refFov.horizontal)
35+
const ratioH = calcCropRatio(isPortrait ? fov.horizontal : fov.vertical, isPortrait ? refFov.horizontal : refFov.vertical)
3636
const rw = w * ratioW; const rh = h * ratioH
3737
const off = offsets[i] ?? { dx: 0, dy: 0 }
3838
return { x: (w - rw) / 2 + off.dx, y: (h - rh) / 2 + off.dy, w: rw, h: rh, color: LENS_COLORS[i], label: LENS_LABELS[i], index: i, focalLength: lenses[i].focalLength, fov }
3939
})
40-
}, [fovs, lenses, offsets, orientation])
40+
}, [fovs, lenses, offsets, orientation, refFov])
4141

4242
const draw = useCallback(() => {
4343
const canvas = canvasRef.current; const img = imageRef.current
@@ -52,15 +52,25 @@ export function Canvas({ lenses, imageIndex, orientation, canvasRef, cleanCanvas
5252

5353
const rects = computeRects(canvas)
5454
if (rects.length === 0) return
55-
drawOverlayBorders(ctx, rects, w, h, dpr)
5655

57-
if (showGuides && rects[activeLens]) {
56+
const widerIndices = new Set<number>()
57+
for (let i = 0; i < rects.length; i++) {
58+
if (rects[i].w > w * 1.01 || rects[i].h > h * 1.01) widerIndices.add(i)
59+
}
60+
61+
const visibleRects = rects.filter((_, i) => !widerIndices.has(i))
62+
if (visibleRects.length > 0) drawOverlayBorders(ctx, visibleRects, w, h, dpr)
63+
64+
if (showGuides && rects[activeLens] && !widerIndices.has(activeLens)) {
5865
const activeFov = fovs[activeLens]
5966
const verticalFOV = orientation === 'portrait' ? activeFov.horizontal : activeFov.vertical
6067
drawFramingGuides(ctx, rects[activeLens], calcFrameWidth(verticalFOV, distance), dpr)
6168
}
6269

63-
drawLensLabels(ctx, rects, lenses, dpr)
70+
drawLensLabels(ctx, visibleRects, lenses, dpr)
71+
72+
if (widerIndices.size > 0) drawWiderLabels(ctx, rects, widerIndices, dpr)
73+
6474
drawnRectsRef.current = rects
6575
cleanCanvas?.dispatchEvent(new Event('draw'))
6676
}, [canvasRef, cleanCanvasRef, computeRects, showGuides, activeLens, distance, fovs, orientation, lenses])
@@ -162,3 +172,23 @@ function drawLensLabels(ctx: CanvasRenderingContext2D, rects: Rect[], lenses: Le
162172
}
163173
}
164174
}
175+
176+
function drawWiderLabels(ctx: CanvasRenderingContext2D, rects: Rect[], widerIndices: Set<number>, dpr: number) {
177+
const fontSize = 13 * dpr
178+
ctx.font = `600 ${fontSize}px -apple-system, BlinkMacSystemFont, sans-serif`
179+
const padX = 10 * dpr; const padY = 6 * dpr
180+
let yOffset = 0
181+
for (const i of widerIndices) {
182+
const r = rects[i]
183+
const text = `${LENS_LABELS[i]}${r.focalLength}mm — wider than source`
184+
const textW = ctx.measureText(text).width
185+
const pillW = textW + padX * 2; const pillH = fontSize + padY * 2
186+
const px = (ctx.canvas.width - pillW) / 2
187+
const py = 16 * dpr + yOffset
188+
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'
189+
ctx.beginPath(); ctx.roundRect(px, py, pillW, pillH, 4 * dpr); ctx.fill()
190+
ctx.fillStyle = r.color
191+
ctx.fillText(text, px + padX, py + padY + fontSize * 0.8)
192+
yOffset += pillH + 4 * dpr
193+
}
194+
}

src/app/[locale]/fov-simulator/_components/canvasTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface CanvasProps {
3535
onOffsetsChange: React.Dispatch<React.SetStateAction<OverlayOffsets>>
3636
customImageSrc?: string | null
3737
sourceImageRef?: React.MutableRefObject<HTMLImageElement | null>
38+
sourceFocalLength?: number | null
3839
}
3940

4041
export const FRAMING_GUIDES = [

0 commit comments

Comments
 (0)