jassub
Version:
The Fastest JavaScript SSA/ASS Subtitle Renderer For Browsers
69 lines • 3.07 kB
JavaScript
// fallback for browsers that don't support GPU acceleration
import { IDENTITY_MATRIX, resolveColorMatrix } from "../util.js";
import { CanvasRendererBase } from "./canvas-base.js";
// clamp to 0-255 with rounding, matching rgba8unorm GPU behaviour
const clamp = (v) => (v < 0 ? 0 : v > 1 ? 255 : Math.round(v * 255));
export class Canvas2DRenderer extends CanvasRendererBase {
ctx;
bufferCanvas = new OffscreenCanvas(1, 1);
bufferCtx = this.bufferCanvas.getContext('2d', {
alpha: true,
desynchronized: true,
willReadFrequently: false
});
colorMatrix = IDENTITY_MATRIX;
constructor(canvas) {
super(canvas);
this.ctx = canvas.getContext('2d', {
alpha: true,
desynchronized: true,
willReadFrequently: false
});
if (!this.ctx || !this.bufferCtx)
throw new Error('Could not get 2D context');
}
// this should be an svg filter which is cheaper, but not supported
// https://issues.chromium.org/u/1/issues/40910142
setColorMatrix(subtitleColorSpace, videoColorSpace) {
this.colorMatrix = resolveColorMatrix(subtitleColorSpace, videoColorSpace);
}
// this is horribly inefficient, but it's a fallback for systems without a GPU, this is the least of their problems
render(images, heap) {
this.applyScheduledResize();
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (const img of images) {
if (img.w <= 0 || img.h <= 0)
continue;
const imageData = new ImageData(img.w, img.h);
const pixels = new Uint32Array(imageData.data.buffer);
// libass 0xRRGGBBAA → ImageData little-endian RGBA, apply colour matrix per image
const m = this.colorMatrix;
const r = ((img.color >>> 24) & 0xff) / 255;
const g = ((img.color >>> 16) & 0xff) / 255;
const bl = ((img.color >>> 8) & 0xff) / 255;
const cr = clamp(m[0] * r + m[3] * g + m[6] * bl);
const cg = clamp(m[1] * r + m[4] * g + m[7] * bl);
const cb = clamp(m[2] * r + m[5] * g + m[8] * bl);
const color = (cb << 16) | (cg << 8) | cr;
const alpha = (255 - (img.color & 255)) / 255;
const stride = img.stride;
const h = img.h;
const w = img.w;
for (let y = h + 1, pos = img.bitmap, res = 0; --y; pos += stride) {
for (let z = 0; z < w; ++z, ++res) {
const k = heap[pos + z];
if (k !== 0)
pixels[res] = (Math.round(alpha * k) << 24) | color;
}
}
this.bufferCanvas.width = w;
this.bufferCanvas.height = h;
this.bufferCtx.putImageData(imageData, 0, 0);
this.ctx.drawImage(this.bufferCanvas, img.dst_x, img.dst_y);
}
}
destroy() {
this.ctx = this.bufferCtx = this.bufferCanvas = null;
}
}
//# sourceMappingURL=2d-renderer.js.map