Only clearing the used area on the canvas, because clearing the whole area every frame is slow.

This commit is contained in:
David Schulte
2024-07-15 18:13:54 +02:00
parent a552563179
commit 7037d5dff2
3 changed files with 154 additions and 1 deletions
+12 -1
View File
@@ -6,6 +6,7 @@ import {CombinedBinaryReader} from "./utils/combinedBinaryReader";
import {PaletteDefinitionSegment} from "./pgs/paletteDefinitionSegment";
import {ObjectDefinitionSegment} from "./pgs/objectDefinitionSegment";
import {WindowDefinition} from "./pgs/windowDefinitionSegment";
import {Rect} from "./utils/rect";
/**
* This handles the low-level PGS loading and rendering. This renderer can operate inside the web worker without being
@@ -58,6 +59,9 @@ export class PgsRendererInternal {
private canvas?: OffscreenCanvas | HTMLCanvasElement;
private context?: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;
// We keep track of the dirty area on the canvas. Clearing the whole canvas is slow when only a small area was used.
private readonly dirtyArea = new Rect();
/**
* Sets the canvas to render to.
* @param canvas The canvas to render to.
@@ -96,7 +100,10 @@ export class PgsRendererInternal {
// Clear the canvas on invalid indices. It is possible to seek to a position before the first subtitle while
// a later subtile is on screen. This subtitle must be clear, even there is no valid new subtitle data.
// Ignoring the render would keep the previous subtitle on screen.
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
if (!this.dirtyArea.empty) {
this.context.clearRect(this.dirtyArea.x, this.dirtyArea.y, this.dirtyArea.width, this.dirtyArea.height);
this.dirtyArea.reset();
}
if (index < 0 || index >= this.displaySets.length) {
return;
}
@@ -149,6 +156,10 @@ export class PgsRendererInternal {
const pixelData = this.getPixelDataFromComposition(compositionObject, palette, ctxObjects);
if (pixelData) {
this.context.drawImage(pixelData, window.horizontalPosition, window.verticalPosition);
// Mark this area as dirty.
this.dirtyArea.union(window.horizontalPosition, window.verticalPosition,
pixelData.width, pixelData.height);
}
}
}
+79
View File
@@ -0,0 +1,79 @@
/**
* A simple rectangular class.
*/
export class Rect {
/**
* Gets if the rect is still empty and doesn't contain any area.
*/
public empty: boolean = true;
/**
* Gets the x coordinate of the rectangular area if not empty.
*/
public x: number = 0;
/**
* Gets the y coordinate of the rectangular area if not empty.
*/
public y: number = 0;
/**
* Gets the width of the rectangular area if not empty.
*/
public width: number = 0;
/**
* Gets the height of the rectangular area if not empty.
*/
public height: number = 0;
/**
* Clears the rectangular.
*/
public reset() {
this.empty = true;
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
/**
* Grows this rectangular area to include the given area.
* @param x The x coordinate of the new area.
* @param y The y coordinate of the new area.
* @param width The width of the new area. Negative values are not supported.
* @param height The height of the new area. Negative values are not supported.
*/
public set(x: number, y: number, width: number = 0, height: number = 0) {
this.empty = false;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
* Grows this rectangular area to include the given area.
* @param x The x coordinate of the new area to include.
* @param y The y coordinate of the new area to include.
* @param width The width of the new area to include. Negative values are not supported.
* @param height The height of the new area to include. Negative values are not supported.
*/
public union(x: number, y: number, width: number = 0, height: number = 0) {
if (this.empty) {
// First sub-rect added
this.empty = false;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
} else {
// Grow the rectangular area to fit the new sub-rect
if (x < this.x) { this.width += this.x - x; this.x = x; }
if (y < this.y) { this.height += this.y - y; this.y = y; }
if (x + width > this.x + this.width) { this.width = x + width - this.x; }
if (y + height > this.y + this.height) { this.height = y + height - this.y; }
}
}
}
+63
View File
@@ -0,0 +1,63 @@
import {Rect} from "../src/utils/rect";
test('reset rect', () => {
const rect = new Rect();
rect.set(1, 1, 2, 2);
expect(rect.empty).toBe(false);
rect.reset();
expect(rect.empty).toBe(true);
});
test('grow rect in positive x direction', () => {
const rect = new Rect();
rect.set(1, 1, 2, 2);
rect.union(5, 1, 2, 2);
expect(rect.empty).toBe(false);
expect(rect.x).toBe(1);
expect(rect.y).toBe(1);
expect(rect.width).toBe(6);
expect(rect.height).toBe(2);
});
test('grow rect in negative x direction', () => {
const rect = new Rect();
rect.set(5, 1, 2, 2);
rect.union(1, 1, 2, 2);
expect(rect.empty).toBe(false);
expect(rect.x).toBe(1);
expect(rect.y).toBe(1);
expect(rect.width).toBe(6);
expect(rect.height).toBe(2);
});
test('grow rect in positive y direction', () => {
const rect = new Rect();
rect.set(1, 1, 2, 2);
rect.union(1, 5, 2, 2);
expect(rect.empty).toBe(false);
expect(rect.x).toBe(1);
expect(rect.y).toBe(1);
expect(rect.width).toBe(2);
expect(rect.height).toBe(6);
});
test('grow rect in negative y direction', () => {
const rect = new Rect();
rect.set(1, 5, 2, 2);
rect.union(1, 1, 2, 2);
expect(rect.empty).toBe(false);
expect(rect.x).toBe(1);
expect(rect.y).toBe(1);
expect(rect.width).toBe(2);
expect(rect.height).toBe(6);
});