Show captured border width overlay in picture CSS test preview
Backend: send border_width in WS metadata and frame_dims (width, height) as a separate JSON message on first JPEG frame. Frontend: render semi-transparent green overlay rectangles on each active edge showing the sampling region depth, plus a small px label. Overlays are proportionally sized based on border_width relative to frame dimensions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -822,6 +822,7 @@ async def test_color_strip_ws(
|
|||||||
indices = [(idx + offset) % total for idx in indices]
|
indices = [(idx + offset) % total for idx in indices]
|
||||||
edges.append({"edge": seg.edge, "indices": indices})
|
edges.append({"edge": seg.edge, "indices": indices})
|
||||||
meta["edges"] = edges
|
meta["edges"] = edges
|
||||||
|
meta["border_width"] = cal.border_width
|
||||||
if is_composite and hasattr(source, "layers"):
|
if is_composite and hasattr(source, "layers"):
|
||||||
# Send layer info for composite preview
|
# Send layer info for composite preview
|
||||||
enabled_layers = [l for l in source.layers if l.get("enabled", True)]
|
enabled_layers = [l for l in source.layers if l.get("enabled", True)]
|
||||||
@@ -850,6 +851,7 @@ async def test_color_strip_ws(
|
|||||||
_frame_live = stream.live_stream
|
_frame_live = stream.live_stream
|
||||||
_last_aux_time = 0.0
|
_last_aux_time = 0.0
|
||||||
_AUX_INTERVAL = 0.08 # send JPEG preview / brightness updates ~12 FPS
|
_AUX_INTERVAL = 0.08 # send JPEG preview / brightness updates ~12 FPS
|
||||||
|
_frame_dims_sent = False # send frame dimensions once with first JPEG
|
||||||
|
|
||||||
# Stream binary RGB frames at ~20 Hz
|
# Stream binary RGB frames at ~20 Hz
|
||||||
while True:
|
while True:
|
||||||
@@ -904,8 +906,16 @@ async def test_color_strip_ws(
|
|||||||
# Ensure 3-channel RGB (some engines may produce BGRA)
|
# Ensure 3-channel RGB (some engines may produce BGRA)
|
||||||
if img.ndim == 3 and img.shape[2] == 4:
|
if img.ndim == 3 and img.shape[2] == 4:
|
||||||
img = img[:, :, :3]
|
img = img[:, :, :3]
|
||||||
# Downscale for bandwidth
|
|
||||||
h, w = img.shape[:2]
|
h, w = img.shape[:2]
|
||||||
|
# Send frame dimensions once so client can compute border overlay
|
||||||
|
if not _frame_dims_sent:
|
||||||
|
_frame_dims_sent = True
|
||||||
|
await websocket.send_text(_json.dumps({
|
||||||
|
"type": "frame_dims",
|
||||||
|
"width": w,
|
||||||
|
"height": h,
|
||||||
|
}))
|
||||||
|
# Downscale for bandwidth
|
||||||
scale = min(960 / w, 540 / h, 1.0)
|
scale = min(960 / w, 540 / h, 1.0)
|
||||||
if scale < 1.0:
|
if scale < 1.0:
|
||||||
new_w = max(1, int(w * scale))
|
new_w = max(1, int(w * scale))
|
||||||
|
|||||||
@@ -215,6 +215,19 @@
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.css-test-border-label {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4px;
|
||||||
|
right: 4px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-family: monospace;
|
||||||
|
padding: 1px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
.css-test-rect-label {
|
.css-test-rect-label {
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: rgba(255, 255, 255, 0.85);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
|||||||
@@ -2612,6 +2612,12 @@ function _cssTestConnect(sourceId, ledCount, fps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle frame dimensions — render border-width overlay
|
||||||
|
if (msg.type === 'frame_dims' && _cssTestMeta) {
|
||||||
|
_cssTestRenderBorderOverlay(msg.width, msg.height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Initial metadata
|
// Initial metadata
|
||||||
_cssTestMeta = msg;
|
_cssTestMeta = msg;
|
||||||
const isPicture = _cssTestMeta.edges && _cssTestMeta.edges.length > 0;
|
const isPicture = _cssTestMeta.edges && _cssTestMeta.edges.length > 0;
|
||||||
@@ -2913,6 +2919,61 @@ function _cssTestRenderRect(rgbBytes, edges) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _cssTestRenderBorderOverlay(frameW, frameH) {
|
||||||
|
const screen = document.getElementById('css-test-rect-screen');
|
||||||
|
if (!screen || !_cssTestMeta) return;
|
||||||
|
|
||||||
|
// Remove any previous border overlay
|
||||||
|
screen.querySelectorAll('.css-test-border-overlay').forEach(el => el.remove());
|
||||||
|
|
||||||
|
const bw = _cssTestMeta.border_width;
|
||||||
|
if (!bw || bw <= 0) return;
|
||||||
|
|
||||||
|
const edges = _cssTestMeta.edges || [];
|
||||||
|
const activeEdges = new Set(edges.map(e => e.edge));
|
||||||
|
|
||||||
|
// Compute border as percentage of frame dimensions
|
||||||
|
const bwPctH = (bw / frameH * 100).toFixed(2); // % for top/bottom
|
||||||
|
const bwPctW = (bw / frameW * 100).toFixed(2); // % for left/right
|
||||||
|
|
||||||
|
const overlayStyle = 'position:absolute;pointer-events:none;background:rgba(var(--primary-color-rgb, 76,175,80),0.18);border:1px solid rgba(var(--primary-color-rgb, 76,175,80),0.4);';
|
||||||
|
|
||||||
|
if (activeEdges.has('top')) {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'css-test-border-overlay';
|
||||||
|
el.style.cssText = `${overlayStyle}top:0;left:0;right:0;height:${bwPctH}%;`;
|
||||||
|
el.title = `${t('calibration.border_width')} ${bw}px`;
|
||||||
|
screen.appendChild(el);
|
||||||
|
}
|
||||||
|
if (activeEdges.has('bottom')) {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'css-test-border-overlay';
|
||||||
|
el.style.cssText = `${overlayStyle}bottom:0;left:0;right:0;height:${bwPctH}%;`;
|
||||||
|
el.title = `${t('calibration.border_width')} ${bw}px`;
|
||||||
|
screen.appendChild(el);
|
||||||
|
}
|
||||||
|
if (activeEdges.has('left')) {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'css-test-border-overlay';
|
||||||
|
el.style.cssText = `${overlayStyle}top:0;bottom:0;left:0;width:${bwPctW}%;`;
|
||||||
|
el.title = `${t('calibration.border_width')} ${bw}px`;
|
||||||
|
screen.appendChild(el);
|
||||||
|
}
|
||||||
|
if (activeEdges.has('right')) {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'css-test-border-overlay';
|
||||||
|
el.style.cssText = `${overlayStyle}top:0;bottom:0;right:0;width:${bwPctW}%;`;
|
||||||
|
el.title = `${t('calibration.border_width')} ${bw}px`;
|
||||||
|
screen.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show border width label
|
||||||
|
const label = document.createElement('div');
|
||||||
|
label.className = 'css-test-border-overlay css-test-border-label';
|
||||||
|
label.textContent = `${bw}px`;
|
||||||
|
screen.appendChild(label);
|
||||||
|
}
|
||||||
|
|
||||||
function _cssTestRenderTicks(edges) {
|
function _cssTestRenderTicks(edges) {
|
||||||
const canvas = document.getElementById('css-test-rect-ticks');
|
const canvas = document.getElementById('css-test-rect-ticks');
|
||||||
const rectEl = document.getElementById('css-test-rect');
|
const rectEl = document.getElementById('css-test-rect');
|
||||||
|
|||||||
Reference in New Issue
Block a user