From 1af5d1fa7bbb83013937ed96f630feb1d4b1fbe0 Mon Sep 17 00:00:00 2001 From: ergunsh Date: Fri, 4 Jun 2021 18:54:44 +0200 Subject: [PATCH 1/7] Typescriptify libsquoosh's codecs and emscripten-utils --- libsquoosh/src/{codecs.js => codecs.ts} | 101 ++++++++--- ...mscripten-utils.js => emscripten-utils.ts} | 7 +- libsquoosh/src/image_data.ts | 8 +- libsquoosh/src/missing-types.d.ts | 166 ++++++++++++++++++ libsquoosh/tsconfig.json | 5 +- 5 files changed, 259 insertions(+), 28 deletions(-) rename libsquoosh/src/{codecs.js => codecs.ts} (80%) rename libsquoosh/src/{emscripten-utils.js => emscripten-utils.ts} (51%) create mode 100644 libsquoosh/src/missing-types.d.ts diff --git a/libsquoosh/src/codecs.js b/libsquoosh/src/codecs.ts similarity index 80% rename from libsquoosh/src/codecs.js rename to libsquoosh/src/codecs.ts index 61b90d7e..a0b694be 100644 --- a/libsquoosh/src/codecs.js +++ b/libsquoosh/src/codecs.ts @@ -1,5 +1,28 @@ import { promises as fsp } from 'fs'; -import { instantiateEmscriptenWasm, pathify } from './emscripten-utils.js'; +import { instantiateEmscriptenWasm, pathify } from './emscripten-utils'; + +interface RotateModuleInstance { + exports: { + memory: WebAssembly.Memory; + rotate(width: number, height: number, rotate: number): void; + }; +} + +interface ResizeWithAspectParams { + input_width: number; + input_height: number; + target_width: number; + target_height: number; +} + +declare global { + // Needed for being able to use ImageData as type in codec types + type ImageData = typeof import('./image_data'); + // Needed for being able to assign to `globalThis.ImageData` + var ImageData: ImageData['constructor']; +} + +import type { QuantizerModule } from '../../codecs/imagequant/imagequant'; // MozJPEG import mozEnc from '../../codecs/mozjpeg/enc/mozjpeg_node_enc.js'; @@ -51,16 +74,22 @@ const resizePromise = resize.default(fsp.readFile(pathify(resizeWasm))); // rotate import rotateWasm from 'asset-url:../../codecs/rotate/rotate.wasm'; +// TODO(ergunsh): Type definitions of some modules do not exist +// Figure out creating type definitions for them and remove `allowJs` rule +// We shouldn't need to use Promise below after getting type definitions for imageQuant // ImageQuant import imageQuant from '../../codecs/imagequant/imagequant_node.js'; import imageQuantWasm from 'asset-url:../../codecs/imagequant/imagequant_node.wasm'; -const imageQuantPromise = instantiateEmscriptenWasm(imageQuant, imageQuantWasm); +const imageQuantPromise: Promise = instantiateEmscriptenWasm( + imageQuant, + imageQuantWasm, +); // Our decoders currently rely on a `ImageData` global. -import ImageData from './image_data.js'; +import ImageData from './image_data'; globalThis.ImageData = ImageData; -function resizeNameToIndex(name) { +function resizeNameToIndex(name: string) { switch (name) { case 'triangle': return 0; @@ -80,25 +109,26 @@ function resizeWithAspect({ input_height, target_width, target_height, -}) { +}: ResizeWithAspectParams): { width: number; height: number } { if (!target_width && !target_height) { throw Error('Need to specify at least width or height when resizing'); } + if (target_width && target_height) { return { width: target_width, height: target_height }; } + if (!target_width) { return { width: Math.round((input_width / input_height) * target_height), height: target_height, }; } - if (!target_height) { - return { - width: target_width, - height: Math.round((input_height / input_width) * target_width), - }; - } + + return { + width: target_width, + height: Math.round((input_height / input_width) * target_width), + }; } export const preprocessors = { @@ -108,10 +138,22 @@ export const preprocessors = { instantiate: async () => { await resizePromise; return ( - buffer, - input_width, - input_height, - { width, height, method, premultiply, linearRGB }, + buffer: Uint8Array, + input_width: number, + input_height: number, + { + width, + height, + method, + premultiply, + linearRGB, + }: { + width: number; + height: number; + method: string; + premultiply: boolean; + linearRGB: boolean; + }, ) => { ({ width, height } = resizeWithAspect({ input_width, @@ -148,7 +190,12 @@ export const preprocessors = { description: 'Reduce the number of colors used (aka. paletting)', instantiate: async () => { const imageQuant = await imageQuantPromise; - return (buffer, width, height, { numColors, dither }) => + return ( + buffer: Uint8Array, + width: number, + height: number, + { numColors, dither }: { numColors: number; dither: number }, + ) => new ImageData( imageQuant.quantize(buffer, width, height, numColors, dither), width, @@ -164,13 +211,18 @@ export const preprocessors = { name: 'Rotate', description: 'Rotate image', instantiate: async () => { - return async (buffer, width, height, { numRotations }) => { + return async ( + buffer: Uint8Array, + width: number, + height: number, + { numRotations }: { numRotations: number }, + ) => { const degrees = (numRotations * 90) % 360; const sameDimensions = degrees == 0 || degrees == 180; const size = width * height * 4; - const { instance } = await WebAssembly.instantiate( - await fsp.readFile(pathify(rotateWasm)), - ); + const instance = ( + await WebAssembly.instantiate(await fsp.readFile(pathify(rotateWasm))) + ).instance as RotateModuleInstance; const { memory } = instance.exports; const additionalPagesNeeded = Math.ceil( (size * 2 - memory.buffer.byteLength + 8) / (64 * 1024), @@ -346,13 +398,18 @@ export const codecs = { await pngEncDecPromise; await oxipngPromise; return { - encode: (buffer, width, height, opts) => { + encode: ( + buffer: Uint8Array, + width: number, + height: number, + opts: { level: number }, + ) => { const simplePng = pngEncDec.encode( new Uint8Array(buffer), width, height, ); - return oxipng.optimise(simplePng, opts.level); + return oxipng.optimise(simplePng, opts.level, false); }, }; }, diff --git a/libsquoosh/src/emscripten-utils.js b/libsquoosh/src/emscripten-utils.ts similarity index 51% rename from libsquoosh/src/emscripten-utils.js rename to libsquoosh/src/emscripten-utils.ts index d0f301b7..255aa4cd 100644 --- a/libsquoosh/src/emscripten-utils.js +++ b/libsquoosh/src/emscripten-utils.ts @@ -1,13 +1,16 @@ import { fileURLToPath } from 'url'; -export function pathify(path) { +export function pathify(path: string): string { if (path.startsWith('file://')) { path = fileURLToPath(path); } return path; } -export function instantiateEmscriptenWasm(factory, path) { +export function instantiateEmscriptenWasm( + factory: EmscriptenWasm.ModuleFactory, + path: string, +): Promise { return factory({ locateFile() { return pathify(path); diff --git a/libsquoosh/src/image_data.ts b/libsquoosh/src/image_data.ts index 9125199f..73a3a389 100644 --- a/libsquoosh/src/image_data.ts +++ b/libsquoosh/src/image_data.ts @@ -1,9 +1,13 @@ export default class ImageData { - readonly data: Uint8ClampedArray; + readonly data: Uint8ClampedArray | Uint8Array; readonly width: number; readonly height: number; - constructor(data: Uint8ClampedArray, width: number, height: number) { + constructor( + data: Uint8ClampedArray | Uint8Array, + width: number, + height: number, + ) { this.data = data; this.width = width; this.height = height; diff --git a/libsquoosh/src/missing-types.d.ts b/libsquoosh/src/missing-types.d.ts new file mode 100644 index 00000000..fcbdbd5d --- /dev/null +++ b/libsquoosh/src/missing-types.d.ts @@ -0,0 +1,166 @@ +/// + +declare module 'asset-url:*' { + const value: string; + export default value; +} + +// Somehow TS picks up definitions from the module itself +// instead of using `asset-url:*`. It is probably related to +// specifity of the module declaration and these declarations below fix it +declare module 'asset-url:../../codecs/png/pkg/squoosh_png_bg.wasm' { + const value: string; + export default value; +} + +declare module 'asset-url:../../codecs/oxipng/pkg/squoosh_oxipng_bg.wasm' { + const value: string; + export default value; +} + +// These don't exist in NodeJS types so we're not able to use them but they are referenced in some emscripten and codec types +// Thus, we need to explicitly assign them to be `never` +// We're also not able to use the APIs that use these types +// So, if we want to use those APIs we need to supply its dependencies ourselves +// However, probably those APIs are more suited to be used in web (i.e. there can be other +// dependencies to web APIs that might not work in Node) +type RequestInfo = never; +type Response = never; +type WebGLRenderingContext = never; +type MessageEvent = never; + +type BufferSource = ArrayBufferView | ArrayBuffer; +type URL = import('url').URL; + +/** + * WebAssembly definitions are not available in `@types/node` yet, + * so these are copied from `lib.dom.d.ts` + */ +declare namespace WebAssembly { + interface CompileError {} + + var CompileError: { + prototype: CompileError; + new (): CompileError; + }; + + interface Global { + value: any; + valueOf(): any; + } + + var Global: { + prototype: Global; + new (descriptor: GlobalDescriptor, v?: any): Global; + }; + + interface Instance { + readonly exports: Exports; + } + + var Instance: { + prototype: Instance; + new (module: Module, importObject?: Imports): Instance; + }; + + interface LinkError {} + + var LinkError: { + prototype: LinkError; + new (): LinkError; + }; + + interface Memory { + readonly buffer: ArrayBuffer; + grow(delta: number): number; + } + + var Memory: { + prototype: Memory; + new (descriptor: MemoryDescriptor): Memory; + }; + + interface Module {} + + var Module: { + prototype: Module; + new (bytes: BufferSource): Module; + customSections(moduleObject: Module, sectionName: string): ArrayBuffer[]; + exports(moduleObject: Module): ModuleExportDescriptor[]; + imports(moduleObject: Module): ModuleImportDescriptor[]; + }; + + interface RuntimeError {} + + var RuntimeError: { + prototype: RuntimeError; + new (): RuntimeError; + }; + + interface Table { + readonly length: number; + get(index: number): Function | null; + grow(delta: number): number; + set(index: number, value: Function | null): void; + } + + var Table: { + prototype: Table; + new (descriptor: TableDescriptor): Table; + }; + + interface GlobalDescriptor { + mutable?: boolean; + value: ValueType; + } + + interface MemoryDescriptor { + initial: number; + maximum?: number; + } + + interface ModuleExportDescriptor { + kind: ImportExportKind; + name: string; + } + + interface ModuleImportDescriptor { + kind: ImportExportKind; + module: string; + name: string; + } + + interface TableDescriptor { + element: TableKind; + initial: number; + maximum?: number; + } + + interface WebAssemblyInstantiatedSource { + instance: Instance; + module: Module; + } + + type ImportExportKind = 'function' | 'global' | 'memory' | 'table'; + type TableKind = 'anyfunc'; + type ValueType = 'f32' | 'f64' | 'i32' | 'i64'; + type ExportValue = Function | Global | Memory | Table; + type Exports = Record; + type ImportValue = ExportValue | number; + type ModuleImports = Record; + type Imports = Record; + function compile(bytes: BufferSource): Promise; + // `compileStreaming` does not exist in NodeJS + // function compileStreaming(source: Response | Promise): Promise; + function instantiate( + bytes: BufferSource, + importObject?: Imports, + ): Promise; + function instantiate( + moduleObject: Module, + importObject?: Imports, + ): Promise; + // `instantiateStreaming` does not exist in NodeJS + // function instantiateStreaming(response: Response | PromiseLike, importObject?: Imports): Promise; + function validate(bytes: BufferSource): boolean; +} diff --git a/libsquoosh/tsconfig.json b/libsquoosh/tsconfig.json index b99cde25..8250ad36 100644 --- a/libsquoosh/tsconfig.json +++ b/libsquoosh/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../generic-tsconfig.json", "compilerOptions": { "lib": ["esnext"], - "types": ["node"] + "types": ["node"], + "allowJs": true }, - "include": ["src/**/*"] + "include": ["src/**/*", "../codecs/**/*"] } From a18ed360ebf77ffb5be1ed0b990e1fc8a11b622c Mon Sep 17 00:00:00 2001 From: ergunsh Date: Tue, 8 Jun 2021 20:08:10 +0200 Subject: [PATCH 2/7] Address review comments * Updated import to contain file extension `js` * Separated WebAssembly definitions from missing-types * Converted resizer.resize result to Uint8ClampedArray * Moved ResizeInstantiateOptions to an interface --- libsquoosh/src/WebAssembly.d.ts | 132 +++++++++++++++++++++++++++++ libsquoosh/src/codecs.ts | 40 +++++---- libsquoosh/src/image_data.ts | 8 +- libsquoosh/src/missing-types.d.ts | 133 ------------------------------ 4 files changed, 156 insertions(+), 157 deletions(-) create mode 100644 libsquoosh/src/WebAssembly.d.ts diff --git a/libsquoosh/src/WebAssembly.d.ts b/libsquoosh/src/WebAssembly.d.ts new file mode 100644 index 00000000..efa094bc --- /dev/null +++ b/libsquoosh/src/WebAssembly.d.ts @@ -0,0 +1,132 @@ +/** + * WebAssembly definitions are not available in `@types/node` yet, + * so these are copied from `lib.dom.d.ts` + */ +declare namespace WebAssembly { + interface CompileError {} + + var CompileError: { + prototype: CompileError; + new (): CompileError; + }; + + interface Global { + value: any; + valueOf(): any; + } + + var Global: { + prototype: Global; + new (descriptor: GlobalDescriptor, v?: any): Global; + }; + + interface Instance { + readonly exports: Exports; + } + + var Instance: { + prototype: Instance; + new (module: Module, importObject?: Imports): Instance; + }; + + interface LinkError {} + + var LinkError: { + prototype: LinkError; + new (): LinkError; + }; + + interface Memory { + readonly buffer: ArrayBuffer; + grow(delta: number): number; + } + + var Memory: { + prototype: Memory; + new (descriptor: MemoryDescriptor): Memory; + }; + + interface Module {} + + var Module: { + prototype: Module; + new (bytes: BufferSource): Module; + customSections(moduleObject: Module, sectionName: string): ArrayBuffer[]; + exports(moduleObject: Module): ModuleExportDescriptor[]; + imports(moduleObject: Module): ModuleImportDescriptor[]; + }; + + interface RuntimeError {} + + var RuntimeError: { + prototype: RuntimeError; + new (): RuntimeError; + }; + + interface Table { + readonly length: number; + get(index: number): Function | null; + grow(delta: number): number; + set(index: number, value: Function | null): void; + } + + var Table: { + prototype: Table; + new (descriptor: TableDescriptor): Table; + }; + + interface GlobalDescriptor { + mutable?: boolean; + value: ValueType; + } + + interface MemoryDescriptor { + initial: number; + maximum?: number; + } + + interface ModuleExportDescriptor { + kind: ImportExportKind; + name: string; + } + + interface ModuleImportDescriptor { + kind: ImportExportKind; + module: string; + name: string; + } + + interface TableDescriptor { + element: TableKind; + initial: number; + maximum?: number; + } + + interface WebAssemblyInstantiatedSource { + instance: Instance; + module: Module; + } + + type ImportExportKind = 'function' | 'global' | 'memory' | 'table'; + type TableKind = 'anyfunc'; + type ValueType = 'f32' | 'f64' | 'i32' | 'i64'; + type ExportValue = Function | Global | Memory | Table; + type Exports = Record; + type ImportValue = ExportValue | number; + type ModuleImports = Record; + type Imports = Record; + function compile(bytes: BufferSource): Promise; + // `compileStreaming` does not exist in NodeJS + // function compileStreaming(source: Response | Promise): Promise; + function instantiate( + bytes: BufferSource, + importObject?: Imports, + ): Promise; + function instantiate( + moduleObject: Module, + importObject?: Imports, + ): Promise; + // `instantiateStreaming` does not exist in NodeJS + // function instantiateStreaming(response: Response | PromiseLike, importObject?: Imports): Promise; + function validate(bytes: BufferSource): boolean; +} diff --git a/libsquoosh/src/codecs.ts b/libsquoosh/src/codecs.ts index a0b694be..e95cd227 100644 --- a/libsquoosh/src/codecs.ts +++ b/libsquoosh/src/codecs.ts @@ -1,5 +1,5 @@ import { promises as fsp } from 'fs'; -import { instantiateEmscriptenWasm, pathify } from './emscripten-utils'; +import { instantiateEmscriptenWasm, pathify } from './emscripten-utils.js'; interface RotateModuleInstance { exports: { @@ -15,6 +15,14 @@ interface ResizeWithAspectParams { target_height: number; } +interface ResizeInstantiateOptions { + width: number; + height: number; + method: string; + premultiply: boolean; + linearRGB: boolean; +} + declare global { // Needed for being able to use ImageData as type in codec types type ImageData = typeof import('./image_data'); @@ -147,13 +155,7 @@ export const preprocessors = { method, premultiply, linearRGB, - }: { - width: number; - height: number; - method: string; - premultiply: boolean; - linearRGB: boolean; - }, + }: ResizeInstantiateOptions, ) => { ({ width, height } = resizeWithAspect({ input_width, @@ -161,17 +163,19 @@ export const preprocessors = { target_width: width, target_height: height, })); + const resizeResult = resize.resize( + buffer, + input_width, + input_height, + width, + height, + resizeNameToIndex(method), + premultiply, + linearRGB, + ); return new ImageData( - resize.resize( - buffer, - input_width, - input_height, - width, - height, - resizeNameToIndex(method), - premultiply, - linearRGB, - ), + // ImageData does not accept Uint8Array so we convert it to a clamped array + new Uint8ClampedArray(resizeResult), width, height, ); diff --git a/libsquoosh/src/image_data.ts b/libsquoosh/src/image_data.ts index 73a3a389..9125199f 100644 --- a/libsquoosh/src/image_data.ts +++ b/libsquoosh/src/image_data.ts @@ -1,13 +1,9 @@ export default class ImageData { - readonly data: Uint8ClampedArray | Uint8Array; + readonly data: Uint8ClampedArray; readonly width: number; readonly height: number; - constructor( - data: Uint8ClampedArray | Uint8Array, - width: number, - height: number, - ) { + constructor(data: Uint8ClampedArray, width: number, height: number) { this.data = data; this.width = width; this.height = height; diff --git a/libsquoosh/src/missing-types.d.ts b/libsquoosh/src/missing-types.d.ts index fcbdbd5d..da972340 100644 --- a/libsquoosh/src/missing-types.d.ts +++ b/libsquoosh/src/missing-types.d.ts @@ -31,136 +31,3 @@ type MessageEvent = never; type BufferSource = ArrayBufferView | ArrayBuffer; type URL = import('url').URL; - -/** - * WebAssembly definitions are not available in `@types/node` yet, - * so these are copied from `lib.dom.d.ts` - */ -declare namespace WebAssembly { - interface CompileError {} - - var CompileError: { - prototype: CompileError; - new (): CompileError; - }; - - interface Global { - value: any; - valueOf(): any; - } - - var Global: { - prototype: Global; - new (descriptor: GlobalDescriptor, v?: any): Global; - }; - - interface Instance { - readonly exports: Exports; - } - - var Instance: { - prototype: Instance; - new (module: Module, importObject?: Imports): Instance; - }; - - interface LinkError {} - - var LinkError: { - prototype: LinkError; - new (): LinkError; - }; - - interface Memory { - readonly buffer: ArrayBuffer; - grow(delta: number): number; - } - - var Memory: { - prototype: Memory; - new (descriptor: MemoryDescriptor): Memory; - }; - - interface Module {} - - var Module: { - prototype: Module; - new (bytes: BufferSource): Module; - customSections(moduleObject: Module, sectionName: string): ArrayBuffer[]; - exports(moduleObject: Module): ModuleExportDescriptor[]; - imports(moduleObject: Module): ModuleImportDescriptor[]; - }; - - interface RuntimeError {} - - var RuntimeError: { - prototype: RuntimeError; - new (): RuntimeError; - }; - - interface Table { - readonly length: number; - get(index: number): Function | null; - grow(delta: number): number; - set(index: number, value: Function | null): void; - } - - var Table: { - prototype: Table; - new (descriptor: TableDescriptor): Table; - }; - - interface GlobalDescriptor { - mutable?: boolean; - value: ValueType; - } - - interface MemoryDescriptor { - initial: number; - maximum?: number; - } - - interface ModuleExportDescriptor { - kind: ImportExportKind; - name: string; - } - - interface ModuleImportDescriptor { - kind: ImportExportKind; - module: string; - name: string; - } - - interface TableDescriptor { - element: TableKind; - initial: number; - maximum?: number; - } - - interface WebAssemblyInstantiatedSource { - instance: Instance; - module: Module; - } - - type ImportExportKind = 'function' | 'global' | 'memory' | 'table'; - type TableKind = 'anyfunc'; - type ValueType = 'f32' | 'f64' | 'i32' | 'i64'; - type ExportValue = Function | Global | Memory | Table; - type Exports = Record; - type ImportValue = ExportValue | number; - type ModuleImports = Record; - type Imports = Record; - function compile(bytes: BufferSource): Promise; - // `compileStreaming` does not exist in NodeJS - // function compileStreaming(source: Response | Promise): Promise; - function instantiate( - bytes: BufferSource, - importObject?: Imports, - ): Promise; - function instantiate( - moduleObject: Module, - importObject?: Imports, - ): Promise; - // `instantiateStreaming` does not exist in NodeJS - // function instantiateStreaming(response: Response | PromiseLike, importObject?: Imports): Promise; - function validate(bytes: BufferSource): boolean; -} From 5707eeff411f1976492968f319692219e9d76fe3 Mon Sep 17 00:00:00 2001 From: ergunsh Date: Tue, 8 Jun 2021 20:28:22 +0200 Subject: [PATCH 3/7] Return Uint8ClampedArray in resize operation --- codecs/resize/pkg/squoosh_resize.d.ts | 4 ++-- codecs/resize/pkg/squoosh_resize.js | 16 +++++++++---- codecs/resize/pkg/squoosh_resize_bg.wasm | Bin 37053 -> 37052 bytes codecs/resize/pkg/squoosh_resize_bg.wasm.d.ts | 7 ++++++ codecs/resize/src/lib.rs | 7 +++--- libsquoosh/src/codecs.ts | 22 ++++++++---------- libsquoosh/src/missing-types.d.ts | 5 ++++ 7 files changed, 40 insertions(+), 21 deletions(-) create mode 100644 codecs/resize/pkg/squoosh_resize_bg.wasm.d.ts diff --git a/codecs/resize/pkg/squoosh_resize.d.ts b/codecs/resize/pkg/squoosh_resize.d.ts index d0f4ca97..56114c7f 100644 --- a/codecs/resize/pkg/squoosh_resize.d.ts +++ b/codecs/resize/pkg/squoosh_resize.d.ts @@ -9,9 +9,9 @@ * @param {number} typ_idx * @param {boolean} premultiply * @param {boolean} color_space_conversion -* @returns {Uint8Array} +* @returns {Uint8ClampedArray} */ -export function resize(input_image: Uint8Array, input_width: number, input_height: number, output_width: number, output_height: number, typ_idx: number, premultiply: boolean, color_space_conversion: boolean): Uint8Array; +export function resize(input_image: Uint8Array, input_width: number, input_height: number, output_width: number, output_height: number, typ_idx: number, premultiply: boolean, color_space_conversion: boolean): Uint8ClampedArray; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; diff --git a/codecs/resize/pkg/squoosh_resize.js b/codecs/resize/pkg/squoosh_resize.js index ec3bfdeb..2e6a0dd4 100644 --- a/codecs/resize/pkg/squoosh_resize.js +++ b/codecs/resize/pkg/squoosh_resize.js @@ -26,8 +26,16 @@ function getInt32Memory0() { return cachegetInt32Memory0; } -function getArrayU8FromWasm0(ptr, len) { - return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); +let cachegetUint8ClampedMemory0 = null; +function getUint8ClampedMemory0() { + if (cachegetUint8ClampedMemory0 === null || cachegetUint8ClampedMemory0.buffer !== wasm.memory.buffer) { + cachegetUint8ClampedMemory0 = new Uint8ClampedArray(wasm.memory.buffer); + } + return cachegetUint8ClampedMemory0; +} + +function getClampedArrayU8FromWasm0(ptr, len) { + return getUint8ClampedMemory0().subarray(ptr / 1, ptr / 1 + len); } /** * @param {Uint8Array} input_image @@ -38,7 +46,7 @@ function getArrayU8FromWasm0(ptr, len) { * @param {number} typ_idx * @param {boolean} premultiply * @param {boolean} color_space_conversion -* @returns {Uint8Array} +* @returns {Uint8ClampedArray} */ export function resize(input_image, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion) { try { @@ -48,7 +56,7 @@ export function resize(input_image, input_width, input_height, output_width, out wasm.resize(retptr, ptr0, len0, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion); var r0 = getInt32Memory0()[retptr / 4 + 0]; var r1 = getInt32Memory0()[retptr / 4 + 1]; - var v1 = getArrayU8FromWasm0(r0, r1).slice(); + var v1 = getClampedArrayU8FromWasm0(r0, r1).slice(); wasm.__wbindgen_free(r0, r1 * 1); return v1; } finally { diff --git a/codecs/resize/pkg/squoosh_resize_bg.wasm b/codecs/resize/pkg/squoosh_resize_bg.wasm index f9ceede2e2333bc34d42a55a24372a79930e29df..b910c97b050df20c1024224d8b3dfab092e6d9e8 100644 GIT binary patch delta 372 zcmdnHkZI3CrVZw7d<#|`U=Yw_;8tK%U~^nB*_Ew;v2*h_wkURF8E&AA93M!gpHEPX zv2*f5m1#hZg=!O!vw64m86D0Es}C?ZN;LW9v@vRg$rqrv7&73wUBJ`4;Dd_a7JL4bh^Nap~tG!j1-$QDB4 zX9L;1Nc;jIn-huO3S)k^aR?hzGW@C6EsGeeXc0QrGHz9%CCgDjAS@x6e2 zB_zHrkgpBo!}Qm)Ffed~5N@tpj%R~yI&a;Eksa56Z8!hw4R>tu;u8OA-6O?zV| NEA(k^_US8R0sxRSQG);g delta 397 zcmdn9kZJEirVZw7e1BFQU=Yw_;8tK%U~~L4*_Ew;v19W#wkURF8E&AA93M!gpHEPX zv19T=m1#hZg=!O!vw64m86D0BpgE3`j&oLRo{%BV#B_J{=9O7poIqKVlDo`2YYmtf z92*)AFmQj;oGe?R!Pqm|t)iQ;XY-{Bb(TbL1_lN`AU?t%z`zBhvw>I|iJt>x3nB5d zfNWkQem;=RiNtRKvIUX&t(&82rTGJOLtk9>;$gtVFuaF4 Vec { +) -> Clamped> { let typ = match typ_idx { 0 => Type::Triangle, 1 => Type::Catrom, @@ -91,7 +92,7 @@ pub fn resize( typ, ); resizer.resize(input_image.as_slice(), output_image.as_mut_slice()); - return output_image; + return Clamped(output_image); } // Otherwise, we convert to f32 images to keep the @@ -138,5 +139,5 @@ pub fn resize( .clamp(0.0, 255.0) as u8; } - return output_image; + return Clamped(output_image); } diff --git a/libsquoosh/src/codecs.ts b/libsquoosh/src/codecs.ts index e95cd227..57c6665d 100644 --- a/libsquoosh/src/codecs.ts +++ b/libsquoosh/src/codecs.ts @@ -163,19 +163,17 @@ export const preprocessors = { target_width: width, target_height: height, })); - const resizeResult = resize.resize( - buffer, - input_width, - input_height, - width, - height, - resizeNameToIndex(method), - premultiply, - linearRGB, - ); return new ImageData( - // ImageData does not accept Uint8Array so we convert it to a clamped array - new Uint8ClampedArray(resizeResult), + resize.resize( + buffer, + input_width, + input_height, + width, + height, + resizeNameToIndex(method), + premultiply, + linearRGB, + ), width, height, ); diff --git a/libsquoosh/src/missing-types.d.ts b/libsquoosh/src/missing-types.d.ts index da972340..4319a188 100644 --- a/libsquoosh/src/missing-types.d.ts +++ b/libsquoosh/src/missing-types.d.ts @@ -18,6 +18,11 @@ declare module 'asset-url:../../codecs/oxipng/pkg/squoosh_oxipng_bg.wasm' { export default value; } +declare module 'asset-url:../../codecs/resize/pkg/squoosh_resize_bg.wasm' { + const value: string; + export default value; +} + // These don't exist in NodeJS types so we're not able to use them but they are referenced in some emscripten and codec types // Thus, we need to explicitly assign them to be `never` // We're also not able to use the APIs that use these types From 1b59c3f47a53b054d02c0d8083405ca63ae91d8a Mon Sep 17 00:00:00 2001 From: ergunsh Date: Wed, 9 Jun 2021 18:11:30 +0200 Subject: [PATCH 4/7] Keep `js` extension while importing module & types --- libsquoosh/src/codecs.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsquoosh/src/codecs.ts b/libsquoosh/src/codecs.ts index 57c6665d..e9619fbe 100644 --- a/libsquoosh/src/codecs.ts +++ b/libsquoosh/src/codecs.ts @@ -25,12 +25,12 @@ interface ResizeInstantiateOptions { declare global { // Needed for being able to use ImageData as type in codec types - type ImageData = typeof import('./image_data'); + type ImageData = typeof import('./image_data.js'); // Needed for being able to assign to `globalThis.ImageData` var ImageData: ImageData['constructor']; } -import type { QuantizerModule } from '../../codecs/imagequant/imagequant'; +import type { QuantizerModule } from '../../codecs/imagequant/imagequant.js'; // MozJPEG import mozEnc from '../../codecs/mozjpeg/enc/mozjpeg_node_enc.js'; @@ -94,7 +94,7 @@ const imageQuantPromise: Promise = instantiateEmscriptenWasm( ); // Our decoders currently rely on a `ImageData` global. -import ImageData from './image_data'; +import ImageData from './image_data.js'; globalThis.ImageData = ImageData; function resizeNameToIndex(name: string) { From 5e14444b13427bad5b4d5c6d401eb5a9f83954fe Mon Sep 17 00:00:00 2001 From: Alexandre Desroches <69808183+alex-drocks@users.noreply.github.com> Date: Fri, 11 Jun 2021 09:36:30 -0400 Subject: [PATCH 5/7] Update README.md To close typo in example code as reported in issue #1051 --- libsquoosh/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsquoosh/README.md b/libsquoosh/README.md index 6b9c0d2b..9692196d 100644 --- a/libsquoosh/README.md +++ b/libsquoosh/README.md @@ -39,9 +39,9 @@ The returned `image` object is a representation of the original image, that you When an image has been ingested, you can start preprocessing it and encoding it to other formats. This example will resize the image and then encode it to a `.jpg` and `.jxl` image: ```js -await image.decoded; //Wait until the image is decoded before running preprocessors +await image.decoded; //Wait until the image is decoded before running preprocessors. -const preprocessOptions: { +const preprocessOptions = { resize: { enabled: true, width: 100, @@ -50,7 +50,7 @@ const preprocessOptions: { } await image.preprocess(preprocessOptions); -const encodeOptions: { +const encodeOptions = { mozjpeg: {}, //an empty object means 'use default settings' jxl: { quality: 90, From 828f9a5eebd172158d540d849546de73f3a9e343 Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Tue, 15 Jun 2021 10:45:53 +0100 Subject: [PATCH 6/7] Fix memory leaks (#1054) --- .../Compress/Output/custom-els/TwoUp/index.ts | 33 ++++++++++++------- src/client/lazy-app/Compress/index.tsx | 1 + 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/client/lazy-app/Compress/Output/custom-els/TwoUp/index.ts b/src/client/lazy-app/Compress/Output/custom-els/TwoUp/index.ts index 6b5b5715..116a3fd9 100644 --- a/src/client/lazy-app/Compress/Output/custom-els/TwoUp/index.ts +++ b/src/client/lazy-app/Compress/Output/custom-els/TwoUp/index.ts @@ -34,6 +34,8 @@ export default class TwoUp extends HTMLElement { */ private _everConnected = false; + private _resizeObserver?: ResizeObserver; + constructor() { super(); this._handle.className = styles.twoUpHandle; @@ -45,13 +47,6 @@ export default class TwoUp extends HTMLElement { childList: true, }); - // Watch for element size changes. - if ('ResizeObserver' in window) { - new ResizeObserver(() => this._resetPosition()).observe(this); - } else { - window.addEventListener('resize', () => this._resetPosition()); - } - // Watch for pointers on the handle. const pointerTracker: PointerTracker = new PointerTracker(this._handle, { start: (_, event) => { @@ -68,8 +63,6 @@ export default class TwoUp extends HTMLElement { ); }, }); - - window.addEventListener('keydown', (event) => this._onKeyDown(event)); } connectedCallback() { @@ -84,12 +77,28 @@ export default class TwoUp extends HTMLElement { } `}`; + // Watch for element size changes. + if ('ResizeObserver' in window) { + this._resizeObserver = new ResizeObserver(() => this._resetPosition()); + this._resizeObserver.observe(this); + } else { + window.addEventListener('resize', this._onResize); + } + + window.addEventListener('keydown', this._onKeyDown); + if (!this._everConnected) { this._resetPosition(); this._everConnected = true; } } + disconnectedCallback() { + window.removeEventListener('keydown', this._onKeyDown); + window.removeEventListener('resize', this._onResize); + if (this._resizeObserver) this._resizeObserver.disconnect(); + } + attributeChangedCallback(name: string) { if (name === orientationAttr) { this._resetPosition(); @@ -97,7 +106,7 @@ export default class TwoUp extends HTMLElement { } // KeyDown event handler - private _onKeyDown(event: KeyboardEvent) { + private _onKeyDown = (event: KeyboardEvent) => { const target = event.target; if (target instanceof HTMLElement && target.closest('input')) return; @@ -122,7 +131,9 @@ export default class TwoUp extends HTMLElement { this._relativePosition = this._position / bounds[dimensionAxis]; this._setPosition(); } - } + }; + + private _onResize = () => this._resetPosition(); private _resetPosition() { // Set the initial position of the handle. diff --git a/src/client/lazy-app/Compress/index.tsx b/src/client/lazy-app/Compress/index.tsx index 4793a5ee..a7d7464a 100644 --- a/src/client/lazy-app/Compress/index.tsx +++ b/src/client/lazy-app/Compress/index.tsx @@ -377,6 +377,7 @@ export default class Compress extends Component { componentWillUnmount(): void { updateDocumentTitle({ loading: false }); + this.widthQuery.removeListener(this.onMobileWidthChange); this.mainAbortController.abort(); for (const controller of this.sideAbortControllers) { controller.abort(); From 1d292468b07ef8af81c24b00f4ad0710ce0080e4 Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Wed, 16 Jun 2021 10:13:46 +0100 Subject: [PATCH 7/7] Everything supports ResizeObserver now --- .../Compress/Output/custom-els/TwoUp/index.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/client/lazy-app/Compress/Output/custom-els/TwoUp/index.ts b/src/client/lazy-app/Compress/Output/custom-els/TwoUp/index.ts index 116a3fd9..24041912 100644 --- a/src/client/lazy-app/Compress/Output/custom-els/TwoUp/index.ts +++ b/src/client/lazy-app/Compress/Output/custom-els/TwoUp/index.ts @@ -78,12 +78,8 @@ export default class TwoUp extends HTMLElement { `}`; // Watch for element size changes. - if ('ResizeObserver' in window) { - this._resizeObserver = new ResizeObserver(() => this._resetPosition()); - this._resizeObserver.observe(this); - } else { - window.addEventListener('resize', this._onResize); - } + this._resizeObserver = new ResizeObserver(() => this._resetPosition()); + this._resizeObserver.observe(this); window.addEventListener('keydown', this._onKeyDown); @@ -95,7 +91,6 @@ export default class TwoUp extends HTMLElement { disconnectedCallback() { window.removeEventListener('keydown', this._onKeyDown); - window.removeEventListener('resize', this._onResize); if (this._resizeObserver) this._resizeObserver.disconnect(); } @@ -133,8 +128,6 @@ export default class TwoUp extends HTMLElement { } }; - private _onResize = () => this._resetPosition(); - private _resetPosition() { // Set the initial position of the handle. requestAnimationFrame(() => {