From 1c5b44f9a132a7c93adac60a4c771c102d6c4aae Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 16 Aug 2021 16:51:23 -0400 Subject: [PATCH 1/9] Add loadFile parameter to ImagePool --- libsquoosh/README.md | 15 +++++++-------- libsquoosh/src/index.ts | 21 ++++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/libsquoosh/README.md b/libsquoosh/README.md index cdb05650..80217af5 100644 --- a/libsquoosh/README.md +++ b/libsquoosh/README.md @@ -16,7 +16,7 @@ You can start using the libSquoosh by adding these lines to the top of your JS p ```js import { ImagePool } from '@squoosh/lib'; -const imagePool = new ImagePool(); +const imagePool = new ImagePool((url) => fs.readFile(url)); ``` This will create an image pool with an underlying processing pipeline that you can use to ingest and encode images. The ImagePool constructor takes one argument that defines how many parallel operations it is allowed to run at any given time. By default, this number is set to the amount of CPU cores available in the system it is running on. @@ -26,11 +26,11 @@ This will create an image pool with an underlying processing pipeline that you c You can ingest a new image like so: ```js -const imagePath = 'path/to/image.png'; +const imagePath = 'file://path/to/image.png'; const image = imagePool.ingestImage(imagePath); ``` -The `ingestImage` function can take anything the node [`readFile`][readfile] function can take, including a buffer and `FileHandle`. +The `ingestImage` function will accept a URL path and call the `loadFile()` function defined in the `ImagePool`. The returned `image` object is a representation of the original image, that you can now preprocess, encode, and extract information about. @@ -39,7 +39,7 @@ 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 = { //When both width and height are specified, the image resized to specified size. @@ -47,7 +47,7 @@ const preprocessOptions = { enabled: true, width: 100, height: 50, - } + }, /* //When either width or height is specified, the image resized to specified size keeping aspect ratio. resize: { @@ -55,7 +55,7 @@ const preprocessOptions = { width: 100, } */ -} +}; await image.preprocess(preprocessOptions); const encodeOptions = { @@ -63,9 +63,8 @@ const encodeOptions = { jxl: { quality: 90, }, -} +}; await image.encode(encodeOptions); - ``` The default values for each option can be found in the [`codecs.ts`][codecs.ts] file under `defaultEncoderOptions`. Every unspecified value will use the default value specified there. _Better documentation is needed here._ diff --git a/libsquoosh/src/index.ts b/libsquoosh/src/index.ts index 48c15091..93041c2c 100644 --- a/libsquoosh/src/index.ts +++ b/libsquoosh/src/index.ts @@ -1,5 +1,4 @@ import { isMainThread } from 'worker_threads'; -import { cpus } from 'os'; import { promises as fsp } from 'fs'; import { codecs as encoders, preprocessors } from './codecs.js'; @@ -169,12 +168,12 @@ function handleJob(params: JobMessage) { * Represents an ingested image. */ class Image { - public file: FileLike; + public file: ArrayBuffer; public workerPool: WorkerPool; public decoded: Promise<{ bitmap: ImageData }>; public encodedWith: { [key: string]: any }; - constructor(workerPool: WorkerPool, file: FileLike) { + constructor(workerPool: WorkerPool, file: ArrayBuffer) { this.file = file; this.workerPool = workerPool; this.decoded = workerPool.dispatchJob({ operation: 'decode', file }); @@ -251,22 +250,26 @@ class Image { */ class ImagePool { public workerPool: WorkerPool; + public loadFile: (path: URL) => Promise; /** * Create a new pool. - * @param {number} [threads] - Number of concurrent image processes to run in the pool. Defaults to the number of CPU cores in the system. + * @param {(path: URL) => Promise} [loadFile] - A function that loads a file from a URL. + * @param {number} [threads] - Number of concurrent image processes to run in the pool. */ - constructor(threads: number) { - this.workerPool = new WorkerPool(threads || cpus().length, __filename); + constructor(loadFile: (path: URL) => Promise, threads: number) { + this.loadFile = loadFile; + this.workerPool = new WorkerPool(threads, __filename); } /** * Ingest an image into the image pool. - * @param {FileLike} image - The image or path to the image that should be ingested and decoded. + * @param {URL} url - The URL path to the image that should be ingested and decoded. * @returns {Image} - A custom class reference to the decoded image. */ - ingestImage(image: FileLike): Image { - return new Image(this.workerPool, image); + ingestImage(url: URL): Image { + const file = await this.loadFile(url); + return new Image(this.workerPool, file); } /** From e5806507d4bbe7b0c60e8bb7cf5247e9c0bca01a Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 20 Aug 2021 15:11:14 -0400 Subject: [PATCH 2/9] Remove Node.js API surface in libsquoosh --- cli/src/index.js | 10 ++++++---- libsquoosh/README.md | 8 +++++--- libsquoosh/src/index.ts | 30 +++++++----------------------- 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/cli/src/index.js b/cli/src/index.js index 331d31b7..16971208 100755 --- a/cli/src/index.js +++ b/cli/src/index.js @@ -75,7 +75,9 @@ async function getInputFiles(paths) { for (const inputPath of paths) { const files = (await fsp.lstat(inputPath)).isDirectory() - ? (await fsp.readdir(inputPath, {withFileTypes: true})).filter(dirent => dirent.isFile()).map(dirent => path.join(inputPath, dirent.name)) + ? (await fsp.readdir(inputPath, { withFileTypes: true })) + .filter((dirent) => dirent.isFile()) + .map((dirent) => path.join(inputPath, dirent.name)) : [inputPath]; for (const file of files) { try { @@ -101,7 +103,7 @@ async function getInputFiles(paths) { async function processFiles(files) { files = await getInputFiles(files); - const imagePool = new ImagePool(); + const imagePool = new ImagePool(cpus().length); const results = new Map(); const progress = progressTracker(results); @@ -116,7 +118,7 @@ async function processFiles(files) { let decoded = 0; let decodedFiles = await Promise.all( files.map(async (file) => { - const image = imagePool.ingestImage(file); + const image = imagePool.ingestImage(fsp.readFile(file)); await image.decoded; results.set(image, { file, @@ -178,7 +180,7 @@ async function processFiles(files) { const outputPath = path.join( program.opts().outputDir, path.basename(originalFile, path.extname(originalFile)) + - program.opts().suffix + program.opts().suffix, ); for (const output of Object.values(image.encodedWith)) { const outputFile = `${outputPath}.${(await output).extension}`; diff --git a/libsquoosh/README.md b/libsquoosh/README.md index 80217af5..f3c84563 100644 --- a/libsquoosh/README.md +++ b/libsquoosh/README.md @@ -16,7 +16,8 @@ You can start using the libSquoosh by adding these lines to the top of your JS p ```js import { ImagePool } from '@squoosh/lib'; -const imagePool = new ImagePool((url) => fs.readFile(url)); +import { cpus } from 'os'; +const imagePool = new ImagePool(cpus().length); ``` This will create an image pool with an underlying processing pipeline that you can use to ingest and encode images. The ImagePool constructor takes one argument that defines how many parallel operations it is allowed to run at any given time. By default, this number is set to the amount of CPU cores available in the system it is running on. @@ -26,11 +27,12 @@ This will create an image pool with an underlying processing pipeline that you c You can ingest a new image like so: ```js -const imagePath = 'file://path/to/image.png'; +import fs from 'fs/promises'; +const file = await fs.readFile('./path/to/image.png'); const image = imagePool.ingestImage(imagePath); ``` -The `ingestImage` function will accept a URL path and call the `loadFile()` function defined in the `ImagePool`. +The `ingestImage` function can accept any [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) whether that is from `readFile()` or `fetch()`. The returned `image` object is a representation of the original image, that you can now preprocess, encode, and extract information about. diff --git a/libsquoosh/src/index.ts b/libsquoosh/src/index.ts index 93041c2c..21620829 100644 --- a/libsquoosh/src/index.ts +++ b/libsquoosh/src/index.ts @@ -1,5 +1,4 @@ import { isMainThread } from 'worker_threads'; -import { promises as fsp } from 'fs'; import { codecs as encoders, preprocessors } from './codecs.js'; import WorkerPool from './worker_pool.js'; @@ -9,26 +8,15 @@ import type ImageData from './image_data'; export { ImagePool, encoders, preprocessors }; type EncoderKey = keyof typeof encoders; type PreprocessorKey = keyof typeof preprocessors; -type FileLike = Buffer | ArrayBuffer | string | ArrayBufferView; async function decodeFile({ file, }: { - file: FileLike; + file: ArrayBuffer; }): Promise<{ bitmap: ImageData; size: number }> { - let buffer; - if (ArrayBuffer.isView(file)) { - buffer = Buffer.from(file.buffer); - file = 'Binary blob'; - } else if (file instanceof ArrayBuffer) { + let buffer: Buffer; + if (file instanceof ArrayBuffer) { buffer = Buffer.from(file); - file = 'Binary blob'; - } else if ((file as unknown) instanceof Buffer) { - // TODO: Check why we need type assertions here. - buffer = (file as unknown) as Buffer; - file = 'Binary blob'; - } else if (typeof file === 'string') { - buffer = await fsp.readFile(file); } else { throw Error('Unexpected input type'); } @@ -40,7 +28,7 @@ async function decodeFile({ detectors.some((detector) => detector.exec(firstChunkString)), )?.[0] as EncoderKey | undefined; if (!key) { - throw Error(`${file} has an unsupported format`); + throw Error(`File has an unsupported format`); } const encoder = encoders[key]; const mod = await encoder.dec(); @@ -250,25 +238,21 @@ class Image { */ class ImagePool { public workerPool: WorkerPool; - public loadFile: (path: URL) => Promise; /** * Create a new pool. - * @param {(path: URL) => Promise} [loadFile] - A function that loads a file from a URL. * @param {number} [threads] - Number of concurrent image processes to run in the pool. */ - constructor(loadFile: (path: URL) => Promise, threads: number) { - this.loadFile = loadFile; + constructor(threads: number) { this.workerPool = new WorkerPool(threads, __filename); } /** * Ingest an image into the image pool. - * @param {URL} url - The URL path to the image that should be ingested and decoded. + * @param {ArrayBuffer} file - The image that should be ingested and decoded. * @returns {Image} - A custom class reference to the decoded image. */ - ingestImage(url: URL): Image { - const file = await this.loadFile(url); + ingestImage(file: ArrayBuffer): Image { return new Image(this.workerPool, file); } From 10996cdbcae327cdfdf6a555b09ce52d4cafdac5 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 20 Aug 2021 16:08:01 -0400 Subject: [PATCH 3/9] Add missing import --- cli/src/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/src/index.js b/cli/src/index.js index 16971208..f91473f0 100755 --- a/cli/src/index.js +++ b/cli/src/index.js @@ -4,6 +4,7 @@ import { program } from 'commander/esm.mjs'; import JSON5 from 'json5'; import path from 'path'; import { promises as fsp } from 'fs'; +import { cpus } from 'os'; import ora from 'ora'; import kleur from 'kleur'; From 30140c2b7a117f555497d3f2dc8d32956f2d0f44 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 20 Aug 2021 18:35:29 -0400 Subject: [PATCH 4/9] Fix CLI bug --- cli/src/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/src/index.js b/cli/src/index.js index f91473f0..c402afff 100755 --- a/cli/src/index.js +++ b/cli/src/index.js @@ -119,7 +119,8 @@ async function processFiles(files) { let decoded = 0; let decodedFiles = await Promise.all( files.map(async (file) => { - const image = imagePool.ingestImage(fsp.readFile(file)); + const buffer = await fsp.readFile(file); + const image = imagePool.ingestImage(buffer); await image.decoded; results.set(image, { file, From bdb5b1637212202fc9489941ffd358524c138a1c Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 20 Aug 2021 18:36:23 -0400 Subject: [PATCH 5/9] Remove Buffer from decodeFile() --- libsquoosh/src/index.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/libsquoosh/src/index.ts b/libsquoosh/src/index.ts index 21620829..51844378 100644 --- a/libsquoosh/src/index.ts +++ b/libsquoosh/src/index.ts @@ -14,13 +14,8 @@ async function decodeFile({ }: { file: ArrayBuffer; }): Promise<{ bitmap: ImageData; size: number }> { - let buffer: Buffer; - if (file instanceof ArrayBuffer) { - buffer = Buffer.from(file); - } else { - throw Error('Unexpected input type'); - } - const firstChunk = buffer.slice(0, 16); + const array = new Uint8Array(file); + const firstChunk = array.slice(0, 16); const firstChunkString = Array.from(firstChunk) .map((v) => String.fromCodePoint(v)) .join(''); @@ -32,10 +27,10 @@ async function decodeFile({ } const encoder = encoders[key]; const mod = await encoder.dec(); - const rgba = mod.decode(new Uint8Array(buffer)); + const rgba = mod.decode(array); return { bitmap: rgba, - size: buffer.length, + size: array.length, }; } From 202d0bc088fa41ab3dae47da9123bceeee831259 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 20 Aug 2021 18:48:30 -0400 Subject: [PATCH 6/9] Generalize file to ArrayLike --- libsquoosh/src/index.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libsquoosh/src/index.ts b/libsquoosh/src/index.ts index 51844378..2e1c8f5c 100644 --- a/libsquoosh/src/index.ts +++ b/libsquoosh/src/index.ts @@ -12,7 +12,7 @@ type PreprocessorKey = keyof typeof preprocessors; async function decodeFile({ file, }: { - file: ArrayBuffer; + file: ArrayBuffer | ArrayLike; }): Promise<{ bitmap: ImageData; size: number }> { const array = new Uint8Array(file); const firstChunk = array.slice(0, 16); @@ -151,12 +151,15 @@ function handleJob(params: JobMessage) { * Represents an ingested image. */ class Image { - public file: ArrayBuffer; + public file: ArrayBuffer | ArrayLike; public workerPool: WorkerPool; public decoded: Promise<{ bitmap: ImageData }>; public encodedWith: { [key: string]: any }; - constructor(workerPool: WorkerPool, file: ArrayBuffer) { + constructor( + workerPool: WorkerPool, + file: ArrayBuffer | ArrayLike, + ) { this.file = file; this.workerPool = workerPool; this.decoded = workerPool.dispatchJob({ operation: 'decode', file }); @@ -244,10 +247,10 @@ class ImagePool { /** * Ingest an image into the image pool. - * @param {ArrayBuffer} file - The image that should be ingested and decoded. + * @param {ArrayBuffer | ArrayLike} file - The image that should be ingested and decoded. * @returns {Image} - A custom class reference to the decoded image. */ - ingestImage(file: ArrayBuffer): Image { + ingestImage(file: ArrayBuffer | ArrayLike): Image { return new Image(this.workerPool, file); } From 3ea0d88c4f2df487a1906225975db2450d3998d0 Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 2 Sep 2021 16:36:37 -0400 Subject: [PATCH 7/9] Apply suggestions from atjn Co-authored-by: Anton --- libsquoosh/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsquoosh/README.md b/libsquoosh/README.md index f3c84563..52affed7 100644 --- a/libsquoosh/README.md +++ b/libsquoosh/README.md @@ -20,7 +20,7 @@ import { cpus } from 'os'; const imagePool = new ImagePool(cpus().length); ``` -This will create an image pool with an underlying processing pipeline that you can use to ingest and encode images. The ImagePool constructor takes one argument that defines how many parallel operations it is allowed to run at any given time. By default, this number is set to the amount of CPU cores available in the system it is running on. +This will create an image pool with an underlying processing pipeline that you can use to ingest and encode images. The ImagePool constructor takes one argument that defines how many parallel operations it is allowed to run at any given time. ## Ingesting images @@ -29,10 +29,10 @@ You can ingest a new image like so: ```js import fs from 'fs/promises'; const file = await fs.readFile('./path/to/image.png'); -const image = imagePool.ingestImage(imagePath); +const image = imagePool.ingestImage(file); ``` -The `ingestImage` function can accept any [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) whether that is from `readFile()` or `fetch()`. +The `ingestImage` function can accept any [`ArrayBuffer`][arraybuffer] whether that is from `readFile()` or `fetch()`. The returned `image` object is a representation of the original image, that you can now preprocess, encode, and extract information about. From c21d3714a82ed077c299c2caac26dd82e84ca837 Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 2 Sep 2021 16:38:22 -0400 Subject: [PATCH 8/9] Fix link in readme --- libsquoosh/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/libsquoosh/README.md b/libsquoosh/README.md index 52affed7..1492af9c 100644 --- a/libsquoosh/README.md +++ b/libsquoosh/README.md @@ -170,3 +170,4 @@ const encodeOptions: { [codecs.ts]: https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.ts [butteraugli]: https://github.com/google/butteraugli [readfile]: https://nodejs.org/api/fs.html#fs_fspromises_readfile_path_options +[arraybuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer From bdfdaf53afed5999b01998effc8bcd6267e34e61 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 8 Sep 2021 17:04:46 -0400 Subject: [PATCH 9/9] Remove unused readme link per atjn Co-authored-by: Anton --- libsquoosh/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/libsquoosh/README.md b/libsquoosh/README.md index 1492af9c..7f4dbb82 100644 --- a/libsquoosh/README.md +++ b/libsquoosh/README.md @@ -169,5 +169,4 @@ const encodeOptions: { [squoosh]: https://squoosh.app [codecs.ts]: https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.ts [butteraugli]: https://github.com/google/butteraugli -[readfile]: https://nodejs.org/api/fs.html#fs_fspromises_readfile_path_options [arraybuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer