From a37b3b8f23ce47b70d83582d0f13a40474ad7ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Gueraud?= Date: Sat, 7 Feb 2026 17:18:05 +1100 Subject: [PATCH 1/2] app: add overlay mode --- src/components/Renderer.svelte | 636 ++++++++++++++++++++++++++--- src/components/Renderer/2d.ts | 51 +++ src/components/Renderer/overlay.ts | 122 ++++++ src/components/VideoOverlay.svelte | 208 ++++++++++ src/components/View.svelte | 1 + 5 files changed, 960 insertions(+), 58 deletions(-) create mode 100644 src/components/Renderer/overlay.ts create mode 100644 src/components/VideoOverlay.svelte diff --git a/src/components/Renderer.svelte b/src/components/Renderer.svelte index 8c3113e..8417456 100644 --- a/src/components/Renderer.svelte +++ b/src/components/Renderer.svelte @@ -1,9 +1,13 @@ + +
+ + + {#if overlayItems.length > 0} + + {/if} +
diff --git a/src/components/View.svelte b/src/components/View.svelte index cdaac22..04af3ea 100644 --- a/src/components/View.svelte +++ b/src/components/View.svelte @@ -271,6 +271,7 @@ {/if} +
Date: Sat, 7 Feb 2026 17:32:10 +1100 Subject: [PATCH 2/2] app: add colors options for overlay --- src/components/Renderer.svelte | 65 +++++++++++++++++++++++++----- src/components/Renderer/2d.ts | 11 +++-- src/components/VideoOverlay.svelte | 14 +++++-- 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/components/Renderer.svelte b/src/components/Renderer.svelte index 8417456..531f90d 100644 --- a/src/components/Renderer.svelte +++ b/src/components/Renderer.svelte @@ -178,6 +178,8 @@ let overlayFieldsEnabled = new SavedState>('overlayFields', DEFAULT_OVERLAY_FIELDS); let overlayPosition = new SavedState('overlayPosition', DEFAULT_OVERLAY_POSITION); let overlayFieldPositions = new SavedState('overlayFieldPositions', {}); + let overlayBackgroundColor = new SavedState('overlayBackgroundColor', 'rgba(15, 23, 42, 0.85)'); + let overlayTextColor = new SavedState('overlayTextColor', '#e2e8f0'); const overlayItemsForPreview = $derived.by(() => { const row = overlayRows[overlaySelectedRowIndex] ?? overlayRows[0]; @@ -327,7 +329,11 @@ const pos = overlayFieldPositions.v[def.id] ?? defaultPositions[i]!; return { label: def.label, value: def.getValue(row), x: pos.x, y: pos.y }; }); - drawVideoOverlayHud(ctx, vw, vh, { items: hudItems }); + drawVideoOverlayHud(ctx, vw, vh, { + items: hudItems, + backgroundColor: overlayBackgroundColor.v, + textColor: overlayTextColor.v, + }); const frame = new VideoFrame(canvas, { timestamp: i * frameDurationMicros }); encoder.encode(frame, { keyFrame: i % 30 === 0 }); frame.close(); @@ -1028,14 +1034,17 @@
- (inputFps.v = e.currentTarget.value)} - /> +
+ (inputFps.v = e.currentTarget.value)} + /> +

Match your source video’s frame rate (e.g. 24, 25, 30) for 1:1 quality.

+
Ride start = video time βˆ’ offset
+
+
+ + + (overlayBackgroundColor.v = e.currentTarget.value)} + title="Pick background color" + /> +
+
+ + + (overlayTextColor.v = e.currentTarget.value)} + title="Pick text color" + /> +
+
{/if} @@ -1092,6 +1137,8 @@ setSelectedRowIndex={(i) => (overlaySelectedRowIndex = i)} timeOffset={overlayTimeOffset} overlayItems={overlayItemsForPreview} + overlayBackgroundColor={overlayBackgroundColor.v} + overlayTextColor={overlayTextColor.v} onPositionChange={(id, x, y) => { overlayFieldPositions.v = { ...overlayFieldPositions.v, [id]: { x, y } }; }} diff --git a/src/components/Renderer/2d.ts b/src/components/Renderer/2d.ts index 6ce2f5a..cf1c323 100644 --- a/src/components/Renderer/2d.ts +++ b/src/components/Renderer/2d.ts @@ -543,6 +543,10 @@ function drawFootpad(params: FootpadParams) { export interface VideoOverlayHudOptions { /** Each item has label, value, and position in 0–1 (relative to canvas size) */ items: { label: string; value: string; x: number; y: number }[]; + /** Background color for each field box (CSS color, e.g. rgba(15,23,42,0.85)) */ + backgroundColor?: string; + /** Text color for label and value (CSS color) */ + textColor?: string; } /** Draw overlay HUD on a canvas (e.g. over a video frame). Used when exporting video with burned-in stats. */ @@ -552,7 +556,7 @@ export function drawVideoOverlayHud( height: number, options: VideoOverlayHudOptions, ): void { - const { items } = options; + const { items, backgroundColor = 'rgba(15, 23, 42, 0.85)', textColor = colors.fg } = options; if (items.length === 0) return; // Use smaller padding so export box size matches preview (preview uses ~16px / tailwind px-4) @@ -573,7 +577,7 @@ export function drawVideoOverlayHud( if (py + boxHeight > height) py = height - boxHeight; if (py < 0) py = 0; - ctx.fillStyle = 'rgba(15, 23, 42, 0.85)'; + ctx.fillStyle = backgroundColor; ctx.strokeStyle = 'rgba(71, 85, 105, 0.6)'; ctx.lineWidth = 2; ctx.beginPath(); @@ -581,11 +585,10 @@ export function drawVideoOverlayHud( ctx.fill(); ctx.stroke(); - ctx.fillStyle = colors.fgSubtle; + ctx.fillStyle = textColor; ctx.textAlign = 'left'; ctx.fillText(label, px + pad * 0.5, py + boxHeight / 2); - ctx.fillStyle = colors.fg; ctx.textAlign = 'right'; ctx.fillText(value, px + boxWidth - pad * 0.5, py + boxHeight / 2); } diff --git a/src/components/VideoOverlay.svelte b/src/components/VideoOverlay.svelte index 26b30d3..e774d70 100644 --- a/src/components/VideoOverlay.svelte +++ b/src/components/VideoOverlay.svelte @@ -22,6 +22,10 @@ timeOffset?: number; /** Overlay items with position (x, y in 0–1) */ overlayItems?: OverlayItem[]; + /** Background color for all overlay fields (CSS color) */ + overlayBackgroundColor?: string; + /** Text color for all overlay fields (CSS color) */ + overlayTextColor?: string; /** Called when user drags a field to a new position */ onPositionChange?: (id: string, x: number, y: number) => void; } @@ -33,6 +37,8 @@ setSelectedRowIndex, timeOffset = 0, overlayItems = [], + overlayBackgroundColor = 'rgba(15, 23, 42, 0.85)', + overlayTextColor = '#e2e8f0', onPositionChange, }: Props = $props(); @@ -187,10 +193,10 @@ {@const x = isDragging ? dragX : item.x} {@const y = isDragging ? dragY : item.y}
onPositionChange && startDrag(e, item)} @@ -199,8 +205,8 @@ onpointercancel={endDrag} onpointerleave={(e) => e.buttons === 0 && endDrag(e)} > - {item.label} - {item.value} + {item.label} + {item.value}
{/each}