diff --git a/package.json b/package.json
index bcbcd48b7..7c2abfa3b 100644
--- a/package.json
+++ b/package.json
@@ -188,7 +188,8 @@
"vkbeautify": "^0.99.3",
"xpath": "0.0.34",
"xregexp": "^5.1.2",
- "zlibjs": "^0.3.1"
+ "zlibjs": "^0.3.1",
+ "@jimp/wasm-webp": "^1.6.0"
},
"scripts": {
"start": "npx grunt dev",
@@ -208,4 +209,4 @@
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
"setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
}
-}
+}
\ No newline at end of file
diff --git a/src/core/operations/ConvertImageFormat.mjs b/src/core/operations/ConvertImageFormat.mjs
index 5a8cb6f4a..8cb8bade3 100644
--- a/src/core/operations/ConvertImageFormat.mjs
+++ b/src/core/operations/ConvertImageFormat.mjs
@@ -8,7 +8,82 @@ import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { isImage } from "../lib/FileType.mjs";
import { toBase64 } from "../lib/Base64.mjs";
-import { Jimp, JimpMime, PNGFilterType } from "jimp";
+import { Jimp as BaseJimp, JimpMime, PNGFilterType } from "jimp";
+import webp from "@jimp/wasm-webp";
+
+/**
+ * Configure Jimp with WebP support
+ */
+const Jimp = new BaseJimp({
+ plugins: [webp],
+ formats: [webp]
+});
+
+function canTranscodeViaCanvas() {
+ return (
+ typeof globalThis !== "undefined" &&
+ typeof globalThis.Blob !== "undefined" &&
+ typeof globalThis.createImageBitmap === "function" &&
+ (typeof globalThis.OffscreenCanvas !== "undefined" ||
+ (typeof globalThis.document !== "undefined" &&
+ typeof globalThis.document.createElement === "function"))
+ );
+}
+
+async function transcodeViaCanvas(input, inputMime, outputMime, quality) {
+ const {Blob: BlobCtor, createImageBitmap: createImageBitmapFn, OffscreenCanvas: OffscreenCanvasCtor, document} =
+ globalThis;
+
+ const inputBytes = input instanceof ArrayBuffer ? new Uint8Array(input) : input;
+ const inputBlob = new BlobCtor([inputBytes], { type: inputMime });
+
+ const bitmap = await createImageBitmapFn(inputBlob);
+ try {
+ let canvas;
+ if (typeof OffscreenCanvasCtor !== "undefined") {
+ canvas = new OffscreenCanvasCtor(bitmap.width, bitmap.height);
+ } else {
+ if (!document) throw new Error("Canvas API not available");
+ canvas = document.createElement("canvas");
+ canvas.width = bitmap.width;
+ canvas.height = bitmap.height;
+ }
+
+ const ctx = canvas.getContext("2d");
+ if (!ctx) throw new Error("Unable to initialise canvas context");
+
+ if (outputMime === "image/jpeg") {
+ ctx.fillStyle = "#ffffff";
+ ctx.fillRect(0, 0, bitmap.width, bitmap.height);
+ }
+
+ ctx.drawImage(bitmap, 0, 0);
+
+ let outputBlob;
+ if (typeof canvas.convertToBlob === "function") {
+ outputBlob = await canvas.convertToBlob({
+ type: outputMime,
+ quality: quality ? quality / 100 : undefined,
+ });
+ } else if (typeof canvas.toBlob === "function") {
+ outputBlob = await new Promise((resolve, reject) => {
+ canvas.toBlob(
+ blob => (blob ? resolve(blob) : reject(new Error("Canvas encode failed"))),
+ outputMime,
+ quality ? quality / 100 : undefined
+ );
+ });
+ } else {
+ throw new Error("Canvas encoding not supported");
+ }
+
+ return await outputBlob.arrayBuffer();
+ } finally {
+ if (bitmap && typeof bitmap.close === "function") {
+ bitmap.close();
+ }
+ }
+}
/**
* Convert Image Format operation
@@ -23,7 +98,7 @@ class ConvertImageFormat extends Operation {
this.name = "Convert Image Format";
this.module = "Image";
this.description =
- "Converts an image between different formats. Supported formats: