Skip to content

Commit

Permalink
Merge branch 'main' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
flaki committed Mar 19, 2024
2 parents a59ccb4 + ad16091 commit bf6fd7a
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 23 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "fauxdom-with-canvas",
"description": "A fast and lightweight HTML5 parser and DOM with built-in canvas",
"version": "0.1.1",
"version": "0.1.2",
"author": "Flaki <[email protected]>",
"contributors": [
"Joe Stenger <[email protected]>",
Expand Down Expand Up @@ -50,10 +50,13 @@
"prepare": "npm exec tsc && rollup -c --silent"
},
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-wasm": "^6.2.2",
"compressing": "^1.5.0",
"jest": "^29.3.0",
"rollup": "^2.79.1",
"rollup-plugin-strip-code": "^0.2.7",
"squoosh": "https://github.com/GoogleChromeLabs/squoosh/archive/refs/tags/v1.12.0.tar.gz",
"typescript": "^5.3.3"
},
"jest": {
Expand All @@ -70,8 +73,5 @@
"<rootDir>/debug",
"<rootDir>/src"
]
},
"dependencies": {
"@rollup/plugin-terser": "^0.4.4"
}
}
30 changes: 19 additions & 11 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import terser from "@rollup/plugin-terser";
import stripCode from "rollup-plugin-strip-code";
import { wasm } from "@rollup/plugin-wasm";
import {spawn} from "child_process";
import {zip} from "compressing";
import * as fs from "fs";
Expand All @@ -9,6 +10,8 @@ import * as pkg from "./package.json";

let DEBUG = true;

let EXTERNALS = [ "node:fs/promises" ];

spawn( process.execPath, ["./scripts/entities.js"] );

export default args =>
Expand All @@ -27,24 +30,28 @@ export default args =>
start_comment: "@START_BROWSER_ONLY",
end_comment: "@END_BROWSER_ONLY"
} ),
modulePlugins = [debugStripper, browserStripper],
iifePlugins = [debugStripper, unitTestStripper],
wasmPlugin = wasm({
sync: [ "node_modules/squoosh/codecs/resize/pkg/squoosh_resize_bg.wasm" ]
}),
modulePlugins = [debugStripper, browserStripper, wasmPlugin],
iifePlugins = [debugStripper, unitTestStripper, wasmPlugin],
output = [
{
onwarn,
input: "src/document.js",
plugins: modulePlugins,
external: EXTERNALS,
output: [
module( "esm" ),
module( "cjs" )
//module( "cjs" )
]
},
{
onwarn,
input: "src/document.js",
plugins: iifePlugins,
output: module( "iife" )
}
// {
// onwarn,
// input: "src/document.js",
// plugins: iifePlugins,
// output: module( "iife" )
// }
];

if ( !DEBUG )
Expand All @@ -57,8 +64,9 @@ export default args =>
output.push( {
onwarn,
input: "src/document.js",
plugins: [browserStripper],
output: module( "cjs", "tests." )
plugins: [browserStripper, wasmPlugin],
external: EXTERNALS,
output: module( "esm", "tests." )
} );
iifePlugins.push( terser( {compress: false, mangle: false, output: {beautify: true}, safari10: true} ) );
}
Expand Down
120 changes: 112 additions & 8 deletions src/js-canvas/RenderingContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { HTMLCanvasElement } from "./HTMLCanvasElement.js";

import { CANVAS_DATA } from "./HTMLCanvasElement.js";
import { ImageData } from "./ImageData.js";
import { resizeImage } from "./WasmResize.js"

// Partial types via https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts
export type RenderingContext = CanvasRenderingContext2D | ImageBitmapRenderingContext
Expand Down Expand Up @@ -37,19 +38,52 @@ interface RGBAColor {
a?: number
}

const FILL_STYLE: unique symbol = Symbol("fill-style");
interface Context2DState {
fillStyle: string
scaleX: number
scaleY: number
translateX: number
translateY: number
}

const STATE: unique symbol = Symbol("context2d-state");

export class CanvasRenderingContext2D implements CanvasRect, CanvasDrawImage, CanvasImageData {
readonly canvas: HTMLCanvasElement;

private [FILL_STYLE]: string;
private [STATE]: Context2DState;

reset() {
this[STATE] = {
fillStyle: "#000",
scaleX: 1,
scaleY: 1,
translateX: 0,
translateY: 0,
};
}

get fillStyle(): string {
return this[FILL_STYLE];
return this[STATE].fillStyle;
}
set fillStyle(newStyle: string) {
console.log(`${this}→fillStyle = ${newStyle}`);
this[FILL_STYLE] = newStyle;
this[STATE].fillStyle = newStyle;
}

get transformActive(): boolean {
const active = this[STATE].scaleX !== 1 || this[STATE].scaleY !== 1 || this[STATE].translateX !== 0 || this[STATE].translateY !== 0;

if (active) {
const activeTransforms = [];
if (this[STATE].scaleX !== 1) activeTransforms.push(`scaleX: ${this[STATE].scaleX}`);
if (this[STATE].scaleY !== 1) activeTransforms.push(`scaleY: ${this[STATE].scaleY}`);
if (this[STATE].translateX !== 0) activeTransforms.push(`translateX: ${this[STATE].translateX}`);
if (this[STATE].translateY !== 0) activeTransforms.push(`translateY: ${this[STATE].translateY}`);
console.log(`${this}: context has active matrix transforms: ${activeTransforms.join(', ')}`);
}

return active;
}

// CanvasRect
Expand All @@ -58,6 +92,10 @@ export class CanvasRenderingContext2D implements CanvasRect, CanvasDrawImage, Ca
}

fillRect(x: number, y: number, w: number, h: number): void {
if (this[STATE].scaleX !== 1 || this[STATE].scaleY !== 1 || this[STATE].translateX !== 0 || this[STATE].translateY !== 0) {
console.log(`Warning: ${this}→fillRect( ${Array.from(arguments).join(', ')} ) canvas transform matrix not supported: ${Object.values(this[STATE]).map(([k,v]) => k+': '+v).join(', ')}`);
}

const { r, g, b, a } = this.fillStyleRGBA;
const alpha = a*255|0;

Expand Down Expand Up @@ -98,7 +136,7 @@ export class CanvasRenderingContext2D implements CanvasRect, CanvasDrawImage, Ca
this.canvas = parentCanvas;

// defaults
this.fillStyle = "#000";
this.reset();
}

// CanvasDrawImage
Expand All @@ -109,29 +147,61 @@ export class CanvasRenderingContext2D implements CanvasRect, CanvasDrawImage, Ca
if (image instanceof globalThis.HTMLCanvasElement) {
w1 = w1 ?? image.width;
h1 = h1 ?? image.height;
x2 = x2 ?? 0;
y2 = y2 ?? 0;

if (w1 !== w2 || h1 !== h2) {
console.log(`${this} Not implemented: image scaling in drawImage( <${image.constructor.name}> ${Array.from(arguments).join(', ')} )`);
return;
}

const srcImage = image.getContext("2d").getImageData(x1, y1, w1, h1);
let srcImage = image.getContext("2d").getImageData(x1, y1, w1, h1);

// Scaling/translation needed
if (this.transformActive) {
// This is slightly inaccurate but we don't do subpixel drawing
const targetWidth = this[STATE].scaleX * w1 |0;
const targetHeight = this[STATE].scaleY * h1 |0;

x2 = x2 + this[STATE].translateX |0;
y2 = y2 + this[STATE].translateY |0;

srcImage = resizeImage(srcImage, targetWidth, targetHeight);
w1 = srcImage.width;
h1 = srcImage.height;

console.log(`${this}→drawImage(): source image resized to: ${w1}x${h1} (${srcImage.data.length/4} pixels)`);
console.log(`${this}→drawImage(): drawing to translated coordinates: ( ${x2}, ${y2} )`);
}

const srcPixels = srcImage.data;
const dstPixels = this.canvas[CANVAS_DATA];
const canvasW = this.canvas.width;
const canvasH = this.canvas.height;
const rows = h1;
const cols = w1;

let ntp = 0;
let oob = 0;
for (let row = 0; row < rows; ++row) {
for (let col = 0; col < cols; ++col) {
// Index of the destination canvas pixel should be within bounds
const di = ((y2 + row) * canvasW + x2 + col) * 4;

if (di < 0 || di >= dstPixels.length) {
++oob;
continue;
}

// source pixel
const si = ((y1 + row) * srcImage.width + x1 + col) * 4;
const sr = srcPixels[ si ];
const sg = srcPixels[ si+1 ];
const sb = srcPixels[ si+2 ];
const sa = srcPixels[ si+3 ];
if (sa > 0) ++ntp;

// destination pixel
const di = ((y2 + row) * srcImage.width + x2 + col) * 4;
const dr = dstPixels[ di ];
const dg = dstPixels[ di+1 ];
const db = dstPixels[ di+2 ];
Expand All @@ -148,6 +218,8 @@ export class CanvasRenderingContext2D implements CanvasRect, CanvasDrawImage, Ca
}
}
console.log(`${this}→drawImage( <${image.constructor.name}> ${Array.from(arguments).join(', ')} )`);
console.log(`${this}→drawImage(): number of non-transparent source pixels drawn: ${ntp} (${ntp/(srcPixels.length/4)*100|0}%)`);
console.log(`${this}→drawImage(): skipped drawing of ${oob} out-of-bounds pixels on the canvas`);
return;
}

Expand Down Expand Up @@ -233,14 +305,46 @@ export class CanvasRenderingContext2D implements CanvasRect, CanvasDrawImage, Ca
setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void;
setTransform(transform?: DOMMatrix2DInit): void;
setTransform(matrixOrA?: any, b?, c?, d?, e?, f?) {
console.log(`${this} Not implemented: context2d.setTransform( ${Array.from(arguments).join(', ')} )`);
// Expand calls using a DOMMatrix2D object
if (typeof matrixOrA === 'object') {
if ('a' in matrixOrA || 'b' in matrixOrA || 'c' in matrixOrA || 'd' in matrixOrA || 'e' in matrixOrA || 'f' in matrixOrA ||
'm11' in matrixOrA || 'm12' in matrixOrA || 'm21' in matrixOrA || 'm22' in matrixOrA || 'm31' in matrixOrA || 'm32' in matrixOrA) {
return this.setTransform(
matrixOrA.a ?? matrixOrA.m11, matrixOrA.b ?? matrixOrA.m12, matrixOrA.c ?? matrixOrA.m21,
matrixOrA.dx ?? matrixOrA.m22, matrixOrA.e ?? matrixOrA.m31, matrixOrA.f ?? matrixOrA.m32
);
}
} else {
const a = matrixOrA;

if ( b !== 0 || c !== 0) {
console.log(`${this} Not implemented: context2d.setTransform( ${Array.from(arguments).join(', ')} ) skew/rotate transforms`);
}

this.scale(a,d);
this.translate(e,f);

console.log(`${this}→setTransform( ${Array.from(arguments).join(', ')} )`);
}
}
scale(xScale: number, yScale: number) {
this[STATE].scaleX = xScale;
this[STATE].scaleY = yScale;
}
translate(x: number, y: number) {
this[STATE].translateX = x;
this[STATE].translateY = y;
}

// Stringifies the context object with its canvas & unique ID to ease debugging
get [Symbol.toStringTag]() {
return `${this.canvas[Symbol.toStringTag]}::context2d`;
}

private setPixel(x,y,r,g,b,a) {

}

// https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
private get fillStyleRGBA(): RGBAColor {
let c;
Expand Down
88 changes: 88 additions & 0 deletions src/js-canvas/WasmResize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// @ts-nocheck
// Based on squoosh_resize_bg.js at v0.12.0
// import * as wasm from './squoosh_resize_bg.wasm';

import wasmInit from '../../node_modules/squoosh/codecs/resize/pkg/squoosh_resize_bg.wasm';
// import { readFile } from 'node:fs/promises';
// const wasmFile = await WebAssembly.compile(
// await readFile(new URL('../../node_modules/squoosh/codecs/resize/pkg/squoosh_resize_bg.wasm', import.meta.url)),
// );
// const wasmInstance = await WebAssembly.instantiate(wasmFile, {});
// const wasm = wasmInstance.exports;
const wasmInstance = wasmInit({});
const wasm = wasmInstance.exports;
console.log('Wasm init:', wasmInstance, wasm);

let cachegetUint8Memory0 = null;
function getUint8Memory0() {
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory0;
}

let WASM_VECTOR_LEN = 0;

function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1);
getUint8Memory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}

let cachegetInt32Memory0 = null;
function getInt32Memory0() {
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32Memory0;
}

function getArrayU8FromWasm0(ptr, len) {
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
}
/**
* @param {Uint8Array} input_image
* @param {number} input_width
* @param {number} input_height
* @param {number} output_width
* @param {number} output_height
* @param {number} typ_idx
* @param {boolean} premultiply
* @param {boolean} color_space_conversion
* @returns {Uint8Array}
*/
export function resize(input_image, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion) {
var ptr0 = passArray8ToWasm0(input_image, wasm.__wbindgen_malloc);
var len0 = WASM_VECTOR_LEN;
wasm.resize(8, ptr0, len0, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion);
var r0 = getInt32Memory0()[8 / 4 + 0];
var r1 = getInt32Memory0()[8 / 4 + 1];
var v1 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v1;
}

export function resizeImage(image: ImageData, output_width: number, output_height: number): ImageData {
const input_image = image.data
const input_width = image.width
const input_height = image.height

// https://github.com/GoogleChromeLabs/squoosh/blob/dev/codecs/resize/src/lib.rs
// 0 => Type::Triangle,
// 1 => Type::Catrom,
// 2 => Type::Mitchell,
// 3 => Type::Lanczos3,
const typ_idx = 3

const premultiply = true
const color_space_conversion = false

const output_image = resize(input_image, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion);

return {
width: output_width|0,
height: output_height|0,
data: output_image
}
}

0 comments on commit bf6fd7a

Please sign in to comment.