quality preset

This commit is contained in:
2026-05-28 14:18:10 +05:30
parent 413f06fb97
commit 5ada2d773b
5 changed files with 74 additions and 7 deletions

View File

@@ -28,6 +28,13 @@ export function ExtractMode({ username, settings }: Props) {
useEffect(() => {
frameSkipRef.current = Math.max(1, settings?.frame_skip ?? 1);
}, [settings?.frame_skip]);
// Preview-quality cap (in px height; 0 = original). Read per-draw via ref
// so a settings change triggers an immediate redraw of the current frame
// without rebuilding the playback effect.
const previewMaxHRef = useRef<number>(settings?.preview_max_height ?? 0);
useEffect(() => {
previewMaxHRef.current = settings?.preview_max_height ?? 0;
}, [settings?.preview_max_height]);
const [video, setVideo] = useState<VideoInfo | null>(null);
const [frameIdx, setFrameIdx] = useState(0);
const [playing, setPlaying] = useState(false);
@@ -120,20 +127,27 @@ export function ExtractMode({ username, settings }: Props) {
const ctx = canvas.getContext("2d");
if (!ctx) return;
// Backing store = displayed CSS rect × DPR, capped at video native
// resolution (anything above is upsampling for no benefit). Halves
// drawImage cost on 4K-into-1080p, which is the common slow-system
// case. Coords below are in image-pixel space — the transform handles
// the mapping to backing pixels, so overlay.ts is unchanged.
// Backing store = displayed CSS rect × DPR, capped at video native and
// optionally at the user's Preview-quality preset. Halves drawImage
// cost on 4K-into-1080p (the common slow-system case). Coords below
// are in image-pixel space — the transform maps to backing pixels so
// overlay.ts is unchanged.
const rect = canvas.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
let maxW = video.width;
let maxH = video.height;
const previewMaxH = previewMaxHRef.current;
if (previewMaxH > 0 && previewMaxH < video.height) {
maxH = previewMaxH;
maxW = Math.round(previewMaxH * (video.width / video.height));
}
const targetW = Math.max(
1,
Math.min(video.width, Math.round(rect.width * dpr))
Math.min(maxW, Math.round(rect.width * dpr))
);
const targetH = Math.max(
1,
Math.min(video.height, Math.round(rect.height * dpr))
Math.min(maxH, Math.round(rect.height * dpr))
);
if (canvas.width !== targetW) canvas.width = targetW;
if (canvas.height !== targetH) canvas.height = targetH;
@@ -249,6 +263,17 @@ export function ExtractMode({ username, settings }: Props) {
useEffect(() => clearBitmapCache, [clearBitmapCache]);
// Live re-render when the preview-quality cap changes so the user sees the
// resolution swap immediately on Settings save instead of having to seek.
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas || !video) return;
const idx = frameIdxRef.current;
const cached = getCachedBitmap(idx);
if (cached) drawBitmap(cached, idx);
// If not cached, the next pump invocation picks up the new cap naturally.
}, [settings?.preview_max_height, video, drawBitmap, getCachedBitmap]);
// Redraw on canvas resize: backing-store is sized from CSS rect × DPR, so
// a window resize/maximize would otherwise leave the old backing dimensions
// until the next decode landed. Cheap — just re-runs drawBitmap on the