From 5ada2d773b52dc2577c6457c39ac0f7447320cae Mon Sep 17 00:00:00 2001 From: ravi Date: Thu, 28 May 2026 14:18:10 +0530 Subject: [PATCH] quality preset --- sam-tool-tauri/src-tauri/src/settings.rs | 6 +++ .../src/components/shell/SettingsModal.tsx | 25 ++++++++++++ sam-tool-tauri/src/index.css | 9 +++++ sam-tool-tauri/src/modes/ExtractMode.tsx | 39 +++++++++++++++---- sam-tool-tauri/src/types.ts | 2 + 5 files changed, 74 insertions(+), 7 deletions(-) diff --git a/sam-tool-tauri/src-tauri/src/settings.rs b/sam-tool-tauri/src-tauri/src/settings.rs index f04eb75..bd32e27 100644 --- a/sam-tool-tauri/src-tauri/src/settings.rs +++ b/sam-tool-tauri/src-tauri/src/settings.rs @@ -24,6 +24,11 @@ pub struct Settings { /// bulk extract (take every Nth frame in range). 1 = no decimation. #[serde(default = "default_frame_skip")] pub frame_skip: u64, + /// Maximum height (in pixels) of the canvas backing store during preview. + /// 0 = no cap beyond native resolution. Common presets: 2160 (4K), + /// 1440 (2K), 1080, 720, 480. Smaller → less draw cost. + #[serde(default)] + pub preview_max_height: u32, } fn default_sam_url() -> String { @@ -58,6 +63,7 @@ impl Default for Settings { cache_capacity: default_cache_capacity(), hwaccel: String::new(), frame_skip: default_frame_skip(), + preview_max_height: 0, } } } diff --git a/sam-tool-tauri/src/components/shell/SettingsModal.tsx b/sam-tool-tauri/src/components/shell/SettingsModal.tsx index 0188133..e34fcf8 100644 --- a/sam-tool-tauri/src/components/shell/SettingsModal.tsx +++ b/sam-tool-tauri/src/components/shell/SettingsModal.tsx @@ -301,6 +301,31 @@ export function SettingsModal({ open, onClose, onSaved }: Props) { /> + +
+ +
{error &&
{error}
} diff --git a/sam-tool-tauri/src/index.css b/sam-tool-tauri/src/index.css index 760920a..775d365 100644 --- a/sam-tool-tauri/src/index.css +++ b/sam-tool-tauri/src/index.css @@ -1606,6 +1606,15 @@ main { 0 1px 0 rgba(255, 255, 255, 0.04) inset; } +/* ExtractMode: canvas-frame must fill the stage at the video's aspect-ratio. + We can't rely on the 's intrinsic size anymore (its backing store + is JS-controlled and capped), so this rule supplies the real display size. + The :not(.zoomable) scope leaves AnnotateMode's zoom layout untouched. */ +.canvas-stage:not(.zoomable) .canvas-frame { + height: 100%; + max-width: 100%; +} + .canvas-frame canvas { width: 100%; height: 100%; diff --git a/sam-tool-tauri/src/modes/ExtractMode.tsx b/sam-tool-tauri/src/modes/ExtractMode.tsx index d7f33fe..c431bc0 100644 --- a/sam-tool-tauri/src/modes/ExtractMode.tsx +++ b/sam-tool-tauri/src/modes/ExtractMode.tsx @@ -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(settings?.preview_max_height ?? 0); + useEffect(() => { + previewMaxHRef.current = settings?.preview_max_height ?? 0; + }, [settings?.preview_max_height]); const [video, setVideo] = useState(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 diff --git a/sam-tool-tauri/src/types.ts b/sam-tool-tauri/src/types.ts index 9006d71..27b8994 100644 --- a/sam-tool-tauri/src/types.ts +++ b/sam-tool-tauri/src/types.ts @@ -97,6 +97,8 @@ export interface Settings { hwaccel: string; /** Decimation factor for playback advancement + bulk extract. 1 = no decimation. */ frame_skip: number; + /** Cap (height in px) on the canvas backing-store during preview. 0 = original. */ + preview_max_height: number; } export interface SamModelInfo {