Skip to content

Commit

Permalink
Initial work on renderer tests and integration tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
ILOVEPIE committed Apr 21, 2024
1 parent 5fe7170 commit b5194a7
Show file tree
Hide file tree
Showing 22 changed files with 309 additions and 8 deletions.
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const config = {
"external": {}
},
testEnvironment: "jsdom",
testEnvironmentOptions: {
url: "https://localhost/src/__tests__/"
},
testPathIgnorePatterns: ["includes", "test-constants", "test-utils"],
sandboxInjectedGlobals: [
"Math",
Expand Down
2 changes: 1 addition & 1 deletion scripts/defines/tools-defines.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/sh
export CLOSURE_LATEST="https://repo1.maven.org/maven2/com/google/javascript/closure-compiler/v20231112/closure-compiler-v20231112.jar" #"http://dl.google.com/closure-compiler/compiler-latest.tar.gz"
export NPM_PACKAGES="source-map@latest prettier@latest pretty-quick@latest eslint@latest eslint-config-prettier@latest lint-staged@latest jest@latest jsdom@latest jest-environment-jsdom@latest jsdoc-to-markdown@latest http-server@latest"
export NPM_PACKAGE_JSON='{"devDependencies":{"eslint":"latest","eslint-config-prettier":"latest","http-server":"latest","jest":"latest","jest-environment-jsdom":"latest","jsdoc-to-markdown":"latest","libass-wasm":"latest","lint-staged":"latest","mime":"latest","prettier":"latest","pretty-quick":"latest","source-map":"latest","ssim.js":"latest","node-canvas-webgl":"latest"},"overrides":{"gl@<8.0.2":"8.0.2"}}'
3 changes: 2 additions & 1 deletion scripts/helpers/execute-node.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

if [ ! -d "$TOOL_BIN_DIR/node_tools" ]; then
mkdir "$TOOL_BIN_DIR/node_tools" > /dev/null 2>&1
printf '%s\n' $NPM_PACKAGES | xargs npm install --prefix "$TOOL_BIN_DIR/node_tools" --save-dev --save-exact
printf "%s\n" "$NPM_PACKAGE_JSON" > "$TOOL_BIN_DIR/node_tools/package.json"
npm install --prefix "$TOOL_BIN_DIR/node_tools" --save-dev --save-exact
fi

NODE_TOOLS_BINDIR="$TOOL_BIN_DIR/node_tools/node_modules/.bin"
Expand Down
8 changes: 5 additions & 3 deletions src/__tests__/parser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@ const ssaEventKeys = [
"Text"
];

const loadFile = (file) => {
const loadFile = (function(){
const { readFileSync } = require('fs');
return readFileSync(__dirname+"/testfiles/" + file,null);
}
return function(file){
return readFileSync(__dirname+"/testfiles/" + file,null);
}
})();

describe("Parser", () => {
describe("#load",() => {
Expand Down
154 changes: 154 additions & 0 deletions src/__tests__/renderer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
{
const { mockDOM } = require('node-canvas-webgl');
mockDOM(window);
}
global = globalThis;
require('../util.js')
require('../global-constants.js')
require('../color.js');
require('../style.js');
require('../lib/codepage.js');
require('../text-server.js');
require('../style-override.js');
require('../subtitle-event.js');
require("../subtitle-tags.js");
require("../subtitle-parser.js");
require("../scheduler.js");
require("../shader.js");
require("../font-server.js");
require("../canvas-2d-text-renderer.js");
require("../canvas-2d-shape-renderer.js");
require("../lib/BSpline.js");
require("../lib/earcut.js");
require("../renderer-main.js");

sabre.getScriptPath = function(){
return __dirname+"/../";
};

const opentype = require("./test-modules/opentype.compat.min.js");

const loadFile = (function(){
const { readFileSync } = require('fs');
return function(file){
return readFileSync(__dirname+"/testfiles/" + file,null);
}
})();

const loadFileAsDataURI = (function(file,mime){
const data = loadFile(file);
return "data:"+mime+";base64,"+data.toString('base64');
});

const compareImages = (function(){
const compare = require("./test-utils/image-comparison.utils.js");
return (function(){
function canvasToImageData(inputCanvas){
const wantedColorSpace = (window.matchMedia && window.matchMedia("(color-gamut: p3)").matches ? "display-p3" : "srgb");
if(inputCanvas.width > 0 && inputCanvas.height > 0){
ctx = inputCanvas.getContext("2d");
const imageData = ctx.getImageData(0,0,inputCanvas.width,inputCanvas.height,{colorSpace:wantedColorSpace});
return imageData;
}else{
throw new Error("Canvas too small to compare.");
}
}
return function(canvas1,canvas2){
const imageData1 = canvasToImageData(canvas1);
const imageData2 = canvasToImageData(canvas2);
return compare(imageData1,imageData2);
}
})();
})();

describe("Subtitle Renderer", () => {

describe("Canvas2DTextRenderer", () => {

});

describe("Canvas2DShapeRenderer", () => {

});

describe("Integration Tests", () => {

const SubtitlesOctopus = require("libass-wasm");

const octopus_options_template = Object.freeze({
workerUrl: require.resolve('libass-wasm/dist/js/subtitles-octopus-worker.js'),
legacyWorkerUrl: require.resolve('libass-wasm/dist/js/subtitles-octopus-worker-legacy.js'),
});

let canvas;
let octopus_canvas;

const fontsList = [
"fonts/OpenSans-Light.ttf",
"fonts/OpenSans-Regular.ttf",
"fonts/OpenSans-Medium.ttf",
"fonts/OpenSans-SemiBold.ttf",
"fonts/OpenSans-Bold.ttf",
"fonts/OpenSans-ExtraBold.ttf",
"fonts/OpenSans-LightItalic.ttf",
"fonts/OpenSans-Italic.ttf",
"fonts/OpenSans-MediumItalic.ttf",
"fonts/OpenSans-SemiBoldItalic.ttf",
"fonts/OpenSans-BoldItalic.ttf",
"fonts/OpenSans-ExtraBoldItalic.ttf",
"fonts/Rosario-Regular.otf"
];
const fontUrls = fontsList.map(font => loadFileAsDataURI(font,(font.endsWith(".otf") ? "font/otf" : "font/ttf")));
const fonts = fontsList.map(font => opentype.parse(loadFile(font)));

beforeEach(() => {
canvas = document.createElement('canvas');
canvas.width = 640;
canvas.height = 480;

octopus_canvas = document.createElement('canvas');
octopus_canvas.width = 640;
octopus_canvas.height = 480;
});

describe("Basic Tests", () => {
test("Can we initialize the renderer?", () => {
const renderer = external.SABRERenderer({
fonts: fonts,
subtitles: loadFile("tag_tests.ass"),
colorSpace:external.VideoColorSpaces.AUTOMATIC,
resolution:[640,480],
nativeResolution:[640,480]
});
expect(renderer.checkReadyToRender()).toBe(true);
});
});
/*
describe("Comparison Tests", () => {
let testBuffer;
let testUri;
let sabre, octopus;
beforeEach(() => {
const octopus_options = Object.freeze(Object.assign({
subUrl: testUri
},octopus_options_template));
octopus = new SubtitlesOctopus(octopus_options);
});
afterEach(() => {
octopus.dispose();
});
describe("Tag Tests", () => {
beforeAll(() => {
testBuffer = loadFile("./testfiles/tag_tests.ass");
testUri = loadFile("./testfiles/tag_tests.ass");
});
});
});
*/
});
});
3 changes: 3 additions & 0 deletions src/__tests__/test-modules/opentype.compat.min.js

Large diffs are not rendered by default.

129 changes: 129 additions & 0 deletions src/__tests__/test-utils/image-comparison.utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
const ssim = require('ssim.js');

function multiplyMatrixByVector(matrix, vector) {
let result = [0, 0, 0];
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
result[i] += matrix[i * 3 + j] * vector[j];
}
}
return result;
}

function sRGBtoRGB(sR,sG,sB){
const color = [sR,sG,sB];
for(let i = 0; i < 3; i++){
if(color[i] <= 0.04045){
color[i] /= 12.92;
}else{
color[i] = Math.pow((color[i]+0.055)/1.055,2.4);
}
}
return color;
}

const DP3toLinearDP3 = sRGBtoRGB;

function sRGBtoXYZ(sR,sG,sB){
const RGBtoXYZMatrix = [0.4360413,0.3851129,0.1430458,0.2224845,0.7169051,0.0606104,0.0139202,0.0970672,0.7139126];
const RGB = sRGBtoRGB(sR,sG,sB);
return multiplyMatrixByVector(RGBtoXYZMatrix,RGB);
}

function DP3toXYZ(wR,wG,wB){
const LinearDP3toXYZMatrix = [0.5151187,0.2919778,0.1571035,0.2411892,0.6922441,0.0665668,-0.0010505,0.0418791,0.7840713];
const LinearDP3 = DP3toLinearDP3(wR,wG,wB)
return multiplyMatrixByVector(LinearDP3toXYZMatrix,LinearDP3)
}

function imageDataToXYZ(d){
const data = d.data;
const colorSpace = d.colorSpace || "srgb";
const length = d.width*d.height*3;
const output = new Array(length);
let j = 0;
for(let i = 0; i < d.width*d.height*4; i+=4){
let XYZ;
switch(colorSpace){
default:
console.warn("Unrecognized color space '"+colorSpace+"' assuming srgb, colors may be wrong.");
case "srgb":
XYZ = sRGBtoXYZ(data[i]/255,data[i+1]/255,data[i+2]/255);
break;
case "display-p3":
XYZ = DP3toXYZ(data[i]/255,data[i+1]/255,data[i+2]/255);
break;
}
output[j] = XYZ[0];
output[j+1] = XYZ[1];
output[j+2] = XYZ[2];
j+=3;
}
return output;
}

function diffArray(a,b){
const maxLength = Math.min(a.length, b.length);
const output = new Array(maxLength);
for(let i = 0; i < maxLength; i++){
output[i] = (a[i]??0)-(b[i]??0);
}
return output;
}

function selectArrayFromArray(a,n,x){
const offset = typeof(x) === "number" ? x : 0;
const length = Math.trunc(a.length/n);
const output = new Array(length);
for(let i = 0; i < length; i++){
output[i] = a[(i*n)+offset];
}
return output;
}

function psnr(image1,image2) {
const image1xyz = imageDataToXYZ(image1);
const image2xyz = imageDataToXYZ(image2);

let mse;
{
const xyzDiffSquared = diffArray(image1xyz,image2xyz).map((value) => Math.pow(value,2));
const mseComponentLength = Math.trunc(xyzDiffSquared.length/3);
// Calculate the mean squared error for each channel
const mseX = selectArrayFromArray(xyzDiffSquared,3).reduce((sum,value) => sum+value,0)/mseComponentLength;
const mseY = selectArrayFromArray(xyzDiffSquared,3,1).reduce((sum,value) => sum+value,0)/mseComponentLength;
const mseZ = selectArrayFromArray(xyzDiffSquared,3,2).reduce((sum,value) => sum+value,0)/mseComponentLength;
// Calculate the mean squared error across all channels
mse = (mseX + mseY + mseZ) / 3;
}

// Maximum possible pixel value (we are using floating point values)
const maxPixelValue = 1.0;

// Return PSNR value.
return 20 * Math.log10(maxPixelValue / Math.sqrt(mse));
}

function xyzssim (image1, image2) {
const image1xyz = imageDataToXYZ(image1);
const image2xyz = imageDataToXYZ(image2);
const image1_x_channel = selectArrayFromArray(image1xyz,3);
const image1_y_channel = selectArrayFromArray(image1xyz,3,1);
const image1_z_channel = selectArrayFromArray(image1xyz,3,2);
const image2_x_channel = selectArrayFromArray(image2xyz,3);
const image2_y_channel = selectArrayFromArray(image2xyz,3,1);
const image2_z_channel = selectArrayFromArray(image2xyz,3,2);
const ssim_x = ssim.ssim({width:image1.width,height:image1.height,data:image1_x_channel}, {width:image2.width,height:image2.height,data:image2_x_channel}).mssim;
const ssim_y = ssim.ssim({width:image1.width,height:image1.height,data:image1_y_channel}, {width:image2.width,height:image2.height,data:image2_y_channel}).mssim;
const ssim_z = ssim.ssim({width:image1.width,height:image1.height,data:image1_z_channel}, {width:image2.width,height:image2.height,data:image2_z_channel}).mssim;
return (ssim_x + ssim_y + ssim_z) / 3;
}

function compare(a,b){
if(a && b){
return {psnr: psnr(a,b), ssim: xyzssim(a,b)};
}
throw new Error("No images to compare.");
}

module.exports = compare;
Binary file added src/__tests__/testfiles/fonts/OpenSans-Bold.ttf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added src/__tests__/testfiles/fonts/OpenSans-Light.ttf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added src/__tests__/testfiles/fonts/Rosario-Regular.otf
Binary file not shown.
4 changes: 2 additions & 2 deletions src/__tests__/testfiles/tag_tests.ass
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ YCbCr Matrix: None

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1
Style: Bold,Arial,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,1,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1
Style: Default,Open Sans,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1
Style: Bold,Open Sans,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,1,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Expand Down
11 changes: 10 additions & 1 deletion src/shader.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,16 @@ const ShaderPrototype = Object.create(Object, {
this._textures = {};
this._attributes = {};
let xmlhttp = null;
if (
if (typeof module !== 'undefined' && module.exports) {
const fs = require('fs');
let response = fs.readFileSync(vertexUrl, "utf8");
shaderlog[vertexUrl] = response;
this.vertSrc = response;
response = fs.readFileSync(fragmentUrl, "utf8");
shaderlog[fragmentUrl] = response;
this.fragSrc = response;
return;
}else if (
typeof global.localStorage === "undefined" ||
typeof expire === "undefined" ||
expire <= 0
Expand Down

0 comments on commit b5194a7

Please sign in to comment.