| 186 | * @returns {Promise<string | File>} Resolves to base64 string or File |
| 187 | */ |
| 188 | export function compressImage( |
| 189 | file, |
| 190 | size = { width: 350, height: 140 }, |
| 191 | convert = "base64" |
| 192 | ) { |
| 193 | return new Promise((resolve, reject) => { |
| 194 | if (!file || !file.type.startsWith("image/")) { |
| 195 | return reject(new Error("Invalid file: must be an image")); |
| 196 | } |
| 197 | |
| 198 | // PNG ignores quality (lossless); JPEG uses it |
| 199 | const encQuality = 0.8; |
| 200 | const { width: targetW, height: targetH } = size; |
| 201 | |
| 202 | // No background provided → transparent PNG (alpha preserved) |
| 203 | const mimeType = "image/png"; |
| 204 | const fileExt = ".png"; |
| 205 | |
| 206 | const img = new Image(); |
| 207 | const objectUrl = URL.createObjectURL(file); |
| 208 | |
| 209 | img.onload = () => { |
| 210 | URL.revokeObjectURL(objectUrl); |
| 211 | |
| 212 | const canvas = document.createElement("canvas"); |
| 213 | canvas.width = targetW; |
| 214 | canvas.height = targetH; |
| 215 | const ctx = canvas.getContext("2d"); |
| 216 | |
| 217 | // Keep all pixels fully transparent — PNG will preserve alpha |
| 218 | ctx.clearRect(0, 0, targetW, targetH); |
| 219 | |
| 220 | const srcW = img.naturalWidth; |
| 221 | const srcH = img.naturalHeight; |
| 222 | |
| 223 | // "contain" — fit entirely inside canvas, pad the rest |
| 224 | const scale = Math.min(targetW / srcW, targetH / srcH); |
| 225 | const drawW = srcW * scale; |
| 226 | const drawH = srcH * scale; |
| 227 | |
| 228 | // Center inside canvas |
| 229 | const drawX = (targetW - drawW) / 2; |
| 230 | const drawY = (targetH - drawH) / 2; |
| 231 | |
| 232 | ctx.drawImage(img, 0, 0, srcW, srcH, drawX, drawY, drawW, drawH); |
| 233 | |
| 234 | if (convert === "base64") { |
| 235 | resolve(canvas.toDataURL(mimeType, encQuality)); |
| 236 | } else { |
| 237 | canvas.toBlob( |
| 238 | (blob) => { |
| 239 | if (!blob) return reject(new Error("Image conversion failed")); |
| 240 | const compressedFile = new File( |
| 241 | [blob], |
| 242 | file.name.replace(/\.[^.]+$/, "") + "_compressed" + fileExt, |
| 243 | { type: mimeType, lastModified: Date.now() } |
| 244 | ); |
| 245 | resolve(compressedFile); |