- Replace queue.shift() with index pointer in resolveNodeStreamPreview to reduce O(n) complexity. - Introduce MaskCanvas type and caching mechanisms for prepared mask canvases in ops.ts. - Enhance fitCanvas to check for prepared mask canvases and mark them accordingly. - Update extractCanvasField to utilize caching for performance improvements. - Refactor renderCompPreview to handle alpha compositing more efficiently. - Implement syncVideoToTick function to manage video playback timing based on render ticks. - Modify ensureVideoFrameCanvas to support tick-based synchronization for video frames. - Change waveform and vectorscope data types from Uint16Array to Uint32Array to prevent overflow. - Update types to include new properties for interaction handling and static render caching.
165 lines
5.9 KiB
JavaScript
165 lines
5.9 KiB
JavaScript
function clamp01(x) {
|
|
return Math.max(0, Math.min(1, x));
|
|
}
|
|
function luma(r, g, b, lw) {
|
|
return lw[0] * r + lw[1] * g + lw[2] * b;
|
|
}
|
|
function computeScopes(imageData, opts) {
|
|
const { width: W, height: H, data } = imageData;
|
|
const lw = opts?.lumaWeights ?? [0.2126, 0.7152, 0.0722];
|
|
const step = Math.max(1, Math.floor(opts?.sampleStep ?? 2));
|
|
const hist = new Uint32Array(256);
|
|
const waveW = Math.max(64, Math.floor(opts?.waveWidth ?? 256));
|
|
const waveH = Math.max(64, Math.floor(opts?.waveHeight ?? 64));
|
|
const waveform = new Uint32Array(waveW * waveH);
|
|
const waveformR = new Uint32Array(waveW * waveH);
|
|
const waveformG = new Uint32Array(waveW * waveH);
|
|
const waveformB = new Uint32Array(waveW * waveH);
|
|
const vecSize = Math.max(64, Math.floor(opts?.vectorscopeSize ?? 96));
|
|
const vectorscope = new Uint32Array(vecSize * vecSize);
|
|
for (let y = 0; y < H; y += step) {
|
|
const wy = Math.floor(y / Math.max(1, H - 1) * (waveW - 1));
|
|
for (let x = 0; x < W; x += step) {
|
|
const i = (y * W + x) * 4;
|
|
const r = data[i] / 255;
|
|
const g = data[i + 1] / 255;
|
|
const b = data[i + 2] / 255;
|
|
const Y = clamp01(luma(r, g, b, lw));
|
|
const bin = Math.max(0, Math.min(255, Y * 255 | 0));
|
|
hist[bin] += 1;
|
|
const wx = Math.floor(x / Math.max(1, W - 1) * (waveW - 1));
|
|
const py = Math.max(0, Math.min(waveH - 1, (1 - Y) * (waveH - 1) | 0));
|
|
waveform[py * waveW + wx] += 1;
|
|
const pr = Math.max(0, Math.min(waveH - 1, (1 - r) * (waveH - 1) | 0));
|
|
const pg = Math.max(0, Math.min(waveH - 1, (1 - g) * (waveH - 1) | 0));
|
|
const pb = Math.max(0, Math.min(waveH - 1, (1 - b) * (waveH - 1) | 0));
|
|
waveformR[pr * waveW + wx] += 1;
|
|
waveformG[pg * waveW + wx] += 1;
|
|
waveformB[pb * waveW + wx] += 1;
|
|
const cb = -0.168736 * r - 0.331264 * g + 0.5 * b;
|
|
const cr = 0.5 * r - 0.418688 * g - 0.081312 * b;
|
|
const vx = Math.max(0, Math.min(vecSize - 1, (cb + 0.5) * (vecSize - 1) | 0));
|
|
const vy = Math.max(0, Math.min(vecSize - 1, (0.5 - cr) * (vecSize - 1) | 0));
|
|
vectorscope[vy * vecSize + vx] += 1;
|
|
}
|
|
}
|
|
return { hist, waveform, waveformR, waveformG, waveformB, waveW, waveH, vectorscope, vecSize };
|
|
}
|
|
function drawHistogram(ctx, W, H, hist) {
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
ctx.clearRect(0, 0, W, H);
|
|
ctx.fillStyle = "rgba(0,0,0,0.35)";
|
|
ctx.fillRect(0, 0, W, H);
|
|
let maxV = 1;
|
|
for (let i = 0; i < 256; i++) maxV = Math.max(maxV, hist[i]);
|
|
ctx.strokeStyle = "rgba(255,255,255,0.7)";
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
for (let i = 0; i < 256; i++) {
|
|
const x = i / 255 * (W - 1);
|
|
const y = H - hist[i] / maxV * (H - 2) - 1;
|
|
if (i === 0) ctx.moveTo(x, y);
|
|
else ctx.lineTo(x, y);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
function drawWaveform(ctx, W, H, wf, wfW, wfH) {
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
ctx.clearRect(0, 0, W, H);
|
|
ctx.fillStyle = "rgba(0,0,0,0.35)";
|
|
ctx.fillRect(0, 0, W, H);
|
|
let maxV = 1;
|
|
for (let i = 0; i < wf.length; i++) maxV = Math.max(maxV, wf[i]);
|
|
const img = ctx.createImageData(W, H);
|
|
const d = img.data;
|
|
for (let y = 0; y < H; y++) {
|
|
const sy = Math.min(wfH - 1, Math.floor(y / Math.max(1, H - 1) * (wfH - 1)));
|
|
for (let x = 0; x < W; x++) {
|
|
const sx = Math.min(wfW - 1, Math.floor(x / Math.max(1, W - 1) * (wfW - 1)));
|
|
const v = wf[sy * wfW + sx] / maxV;
|
|
const a = Math.max(0, Math.min(255, v * 255 | 0));
|
|
const idx = (y * W + x) * 4;
|
|
d[idx] = 220;
|
|
d[idx + 1] = 220;
|
|
d[idx + 2] = 220;
|
|
d[idx + 3] = a;
|
|
}
|
|
}
|
|
ctx.putImageData(img, 0, 0);
|
|
}
|
|
function drawRgbWaveform(ctx, W, H, wr, wg, wb, wfW, wfH) {
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
ctx.clearRect(0, 0, W, H);
|
|
ctx.fillStyle = "rgba(0,0,0,0.35)";
|
|
ctx.fillRect(0, 0, W, H);
|
|
let maxV = 1;
|
|
for (let i = 0; i < wr.length; i++) maxV = Math.max(maxV, wr[i], wg[i], wb[i]);
|
|
const img = ctx.createImageData(W, H);
|
|
const d = img.data;
|
|
for (let y = 0; y < H; y++) {
|
|
const sy = Math.min(wfH - 1, Math.floor(y / Math.max(1, H - 1) * (wfH - 1)));
|
|
for (let x = 0; x < W; x++) {
|
|
const sx = Math.min(wfW - 1, Math.floor(x / Math.max(1, W - 1) * (wfW - 1)));
|
|
const sIdx = sy * wfW + sx;
|
|
const ar = Math.max(0, Math.min(255, wr[sIdx] / maxV * 255 | 0));
|
|
const ag = Math.max(0, Math.min(255, wg[sIdx] / maxV * 255 | 0));
|
|
const ab = Math.max(0, Math.min(255, wb[sIdx] / maxV * 255 | 0));
|
|
const idx = (y * W + x) * 4;
|
|
if (ar >= ag && ar >= ab) {
|
|
d[idx] = 255;
|
|
d[idx + 1] = 80;
|
|
d[idx + 2] = 80;
|
|
} else if (ag >= ar && ag >= ab) {
|
|
d[idx] = 80;
|
|
d[idx + 1] = 255;
|
|
d[idx + 2] = 80;
|
|
} else {
|
|
d[idx] = 80;
|
|
d[idx + 1] = 160;
|
|
d[idx + 2] = 255;
|
|
}
|
|
d[idx + 3] = Math.max(ar, ag, ab);
|
|
}
|
|
}
|
|
ctx.putImageData(img, 0, 0);
|
|
}
|
|
function drawVectorscope(ctx, S, data, size) {
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
ctx.clearRect(0, 0, S, S);
|
|
ctx.fillStyle = "rgba(0,0,0,0.35)";
|
|
ctx.fillRect(0, 0, S, S);
|
|
let maxV = 1;
|
|
for (let i = 0; i < data.length; i++) maxV = Math.max(maxV, data[i]);
|
|
const img = ctx.createImageData(S, S);
|
|
const d = img.data;
|
|
for (let y = 0; y < S; y++) {
|
|
const sy = Math.min(size - 1, Math.floor(y / Math.max(1, S - 1) * (size - 1)));
|
|
for (let x = 0; x < S; x++) {
|
|
const sx = Math.min(size - 1, Math.floor(x / Math.max(1, S - 1) * (size - 1)));
|
|
const v = data[sy * size + sx] / maxV;
|
|
const a = Math.max(0, Math.min(255, v * 255 | 0));
|
|
const idx = (y * S + x) * 4;
|
|
d[idx] = 180;
|
|
d[idx + 1] = 220;
|
|
d[idx + 2] = 255;
|
|
d[idx + 3] = a;
|
|
}
|
|
}
|
|
ctx.putImageData(img, 0, 0);
|
|
ctx.strokeStyle = "rgba(255,255,255,0.25)";
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
ctx.moveTo(S / 2, 0);
|
|
ctx.lineTo(S / 2, S);
|
|
ctx.moveTo(0, S / 2);
|
|
ctx.lineTo(S, S / 2);
|
|
ctx.stroke();
|
|
}
|
|
export {
|
|
computeScopes,
|
|
drawHistogram,
|
|
drawRgbWaveform,
|
|
drawVectorscope,
|
|
drawWaveform
|
|
};
|