@awesome-compressor/browser-compress-image
Version:
🚀 A powerful, lightweight browser image compression library with TypeScript support. Compress JPEG, PNG, GIF images with multiple output formats (Blob, File, Base64, ArrayBuffer) and zero dependencies.
256 lines (254 loc) • 9.24 kB
JavaScript
import { __esm, __export } from "./chunk-BaU5PcSi.js";
//#region src/tools/compressWithJsquash.ts
var compressWithJsquash_exports = {};
__export(compressWithJsquash_exports, {
configureWasmLoading: () => configureWasmLoading,
default: () => compressWithJsquash,
diagnoseJsquashAvailability: () => diagnoseJsquashAvailability,
downloadWasmFiles: () => downloadWasmFiles,
ensureWasmLoaded: () => ensureWasmLoaded,
isWebAssemblySupported: () => isWebAssemblySupported
});
function configureWasmLoading(config) {
wasmConfig = {
...wasmConfig,
...config
};
}
function isWebAssemblySupported() {
return typeof WebAssembly !== "undefined" && typeof WebAssembly.instantiate === "function";
}
function isBrowser() {
return typeof window !== "undefined" && typeof document !== "undefined";
}
async function importJsquashModule(format) {
if (!isBrowser()) throw new Error(`JSQuash not supported in Node.js environment for ${format}`);
try {
const cdnUrl = `https://unpkg.com/@jsquash/${format}@latest?module`;
return await import(
/* @vite-ignore */
cdnUrl);
} catch (cdnError) {
console.error(`CDN import failed for ${format}:`, cdnError);
throw cdnError;
}
}
async function ensureWasmLoaded(format) {
if (wasmLoadPromises.has(format)) return wasmLoadPromises.get(format);
const loadPromise = (async () => {
try {
if (wasmConfig.useLocal) try {
await loadLocalWasm(format);
return;
} catch (localError) {
console.warn(`Local WASM loading failed for ${format}, falling back to CDN:`, localError);
}
await importJsquashModule(format);
} catch (error) {
wasmLoadPromises.delete(format);
if (error instanceof Error && error.message.includes("magic word")) {
console.error(`WASM loading failed for ${format}: Invalid WASM file or CDN issue`, error);
throw new Error(`Failed to load ${format} WASM module. This might be due to network issues or CDN problems.`);
}
console.error(`Failed to initialize WASM for ${format}:`, error);
throw new Error(`Failed to initialize ${format} support: ${error instanceof Error ? error.message : String(error)}`);
}
})();
wasmLoadPromises.set(format, loadPromise);
return loadPromise;
}
async function diagnoseJsquashAvailability() {
const result = {
wasmSupported: isWebAssemblySupported(),
availableFormats: [],
errors: []
};
if (!result.wasmSupported) return result;
const formats = [
"avif",
"jpeg",
"jxl",
"png",
"webp"
];
for (const format of formats) try {
await ensureWasmLoaded(format);
result.availableFormats.push(format);
} catch (error) {
result.errors.push({
format,
error: error instanceof Error ? error.message : String(error)
});
}
return result;
}
async function downloadWasmFiles(formats = [
"avif",
"jpeg",
"jxl",
"png",
"webp"
], targetDir = "/wasm/") {
const results = [];
for (const format of formats) try {
const packageName = `@jsquash/${format}`;
const wasmFileName = wasmFiles[format];
const cdnUrl = `https://unpkg.com/${packageName}/codec/${wasmFileName}`;
console.log(`正在下载 ${format} WASM 文件: ${cdnUrl}`);
const response = await fetch(cdnUrl);
if (!response.ok) throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
const wasmBytes = await response.arrayBuffer();
const magic = new Uint8Array(wasmBytes.slice(0, 4));
if (magic[0] !== 0 || magic[1] !== 97 || magic[2] !== 115 || magic[3] !== 109) throw new Error("Invalid WASM file format");
const blob = new Blob([wasmBytes], { type: "application/wasm" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${format}_${wasmFileName}`;
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
results.push({
format,
success: true
});
console.log(`✅ ${format} WASM 文件下载成功`);
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
results.push({
format,
success: false,
error: errorMsg
});
console.error(`❌ ${format} WASM 文件下载失败:`, errorMsg);
}
return results;
}
async function loadLocalWasm(format) {
if ("serviceWorker" in navigator) {
const cacheName = "jsquash-wasm-cache";
const wasmFileName = wasmFiles[format];
const localUrl$1 = `${wasmConfig.baseUrl}${wasmFileName}`;
try {
const cache = await caches.open(cacheName);
const cachedResponse = await cache.match(localUrl$1);
if (cachedResponse) {
console.log(`从缓存加载 ${format} WASM 文件`);
return;
}
} catch (error) {
console.warn("Cache API not available:", error);
}
}
const localUrl = `${wasmConfig.baseUrl}${wasmFiles[format]}`;
const response = await fetch(localUrl);
if (!response.ok) throw new Error(`Local WASM file not found: ${localUrl}`);
const wasmBytes = await response.arrayBuffer();
const magic = new Uint8Array(wasmBytes.slice(0, 4));
if (magic[0] !== 0 || magic[1] !== 97 || magic[2] !== 115 || magic[3] !== 109) throw new Error(`Invalid local WASM file: ${localUrl}`);
console.log(`✅ 本地 ${format} WASM 文件验证成功`);
}
function getOutputFormat(fileType) {
if (fileType.includes("png")) return "png";
if (fileType.includes("webp")) return "webp";
if (fileType.includes("avif")) return "avif";
if (fileType.includes("jxl")) return "jxl";
return "jpeg";
}
async function fileToImageData(file) {
return new Promise((resolve, reject) => {
const img = new Image();
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
resolve(imageData);
};
img.onerror = () => reject(/* @__PURE__ */ new Error("Failed to load image"));
img.src = URL.createObjectURL(file);
});
}
async function compressWithJsquash(file, options) {
const { quality, targetWidth, targetHeight, maxWidth, maxHeight } = options;
const outputFormat = getOutputFormat(file.type);
try {
await ensureWasmLoaded(outputFormat);
const jsquashModule = await importJsquashModule(outputFormat);
const imageData = await fileToImageData(file);
let processedImageData = imageData;
if (targetWidth || targetHeight || maxWidth || maxHeight) processedImageData = await resizeImageData(imageData, targetWidth || maxWidth, targetHeight || maxHeight);
let compressedBuffer;
switch (outputFormat) {
case "avif": {
compressedBuffer = await jsquashModule.encode(processedImageData, { quality: Math.round(quality * 100) });
break;
}
case "jpeg": {
compressedBuffer = await jsquashModule.encode(processedImageData, { quality: Math.round(quality * 100) });
break;
}
case "jxl": {
compressedBuffer = await jsquashModule.encode(processedImageData, { quality: Math.round(quality * 100) });
break;
}
case "png": {
compressedBuffer = await jsquashModule.encode(processedImageData);
break;
}
case "webp": {
compressedBuffer = await jsquashModule.encode(processedImageData, { quality: Math.round(quality * 100) });
break;
}
default: throw new Error(`Unsupported output format: ${outputFormat}`);
}
const mimeType = `image/${outputFormat === "jxl" ? "jxl" : outputFormat}`;
const compressedBlob = new Blob([compressedBuffer], { type: mimeType });
if (compressedBlob.size >= file.size * .98) return file;
return compressedBlob;
} catch (error) {
console.error("JSQuash compression failed:", error);
return file;
}
}
async function resizeImageData(imageData, targetWidth, targetHeight) {
if (!targetWidth && !targetHeight) return imageData;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const originalWidth = imageData.width;
const originalHeight = imageData.height;
let newWidth = targetWidth || originalWidth;
let newHeight = targetHeight || originalHeight;
if (targetWidth && !targetHeight) newHeight = Math.round(originalHeight * targetWidth / originalWidth);
else if (targetHeight && !targetWidth) newWidth = Math.round(originalWidth * targetHeight / originalHeight);
const tempCanvas = document.createElement("canvas");
const tempCtx = tempCanvas.getContext("2d");
tempCanvas.width = originalWidth;
tempCanvas.height = originalHeight;
tempCtx.putImageData(imageData, 0, 0);
canvas.width = newWidth;
canvas.height = newHeight;
ctx.drawImage(tempCanvas, 0, 0, newWidth, newHeight);
return ctx.getImageData(0, 0, newWidth, newHeight);
}
var wasmLoadPromises, wasmConfig, wasmFiles;
var init_compressWithJsquash = __esm({ "src/tools/compressWithJsquash.ts"() {
wasmLoadPromises = /* @__PURE__ */ new Map();
wasmConfig = {
baseUrl: "/wasm/",
useLocal: false
};
wasmFiles = {
avif: "squoosh_avif_bg.wasm",
jpeg: "mozjpeg_bg.wasm",
jxl: "jxl_bg.wasm",
png: "squoosh_png_bg.wasm",
webp: "squoosh_webp_bg.wasm"
};
} });
//#endregion
export { compressWithJsquash, compressWithJsquash_exports, configureWasmLoading, diagnoseJsquashAvailability, downloadWasmFiles, ensureWasmLoaded, init_compressWithJsquash, isWebAssemblySupported };