UNPKG

@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
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 };