UNPKG

jassub

Version:

The Fastest JavaScript SSA/ASS Subtitle Renderer For Browsers

81 lines (65 loc) • 2.85 kB
// fallback for browsers that don't support GPU acceleration import { IDENTITY_MATRIX, resolveColorMatrix, type ASSImage } from '../util.ts' import { CanvasRendererBase } from './canvas-base.ts' // clamp to 0-255 with rounding, matching rgba8unorm GPU behaviour const clamp = (v: number) => (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: Float32Array = IDENTITY_MATRIX constructor (canvas: OffscreenCanvas) { 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?: 'BT601' | 'BT709' | 'SMPTE240M' | 'FCC', videoColorSpace?: 'BT601' | 'BT709') { 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: ASSImage[], heap: Uint8Array): void { 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! } }