-
-
Save Aarbel/c657991441dce71ef6a33dc3e781d117 to your computer and use it in GitHub Desktop.
Compress image in the browser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import loadImage from "blueimp-load-image"; | |
import { readAndCompressImage } from "browser-image-resizer"; | |
import { captureException } from "../sentry"; | |
const ONE_MB_IN_BYTE = 1048576; | |
interface Options { | |
/** @default Number.POSITIVE_INFINITY */ | |
maxSizeMB?: number; | |
/** @default undefined */ | |
maxWidthOrHeight?: number; | |
/** A function takes one progress argument (progress from 0 to 100) */ | |
onProgress?: (progress: number) => void; | |
} | |
/* convert a canvas to a blob */ | |
async function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> { | |
const blob: Blob | null = await new Promise((resolve) => | |
canvas.toBlob(resolve) | |
); | |
if (!blob) { | |
throw new Error("canvasToBlob failed"); | |
} | |
return blob; | |
} | |
/* convert a blob to a File object */ | |
async function blobToFile( | |
blob: Blob, | |
fileName: string, | |
fileDate: number | |
): Promise<File> { | |
return new File([blob], fileName, { | |
lastModified: fileDate, | |
type: blob.type, | |
}); | |
} | |
// Function here to serve as a guard to run compression only when necessary | |
// also useful to syncronously know if compressions will be done on list of files | |
// and adapt the UI accordingly | |
function mustCompress(file: File, options?: Options): boolean { | |
const maxSizeInBytes = options?.maxSizeMB | |
? // Convert the size from MB to Bytes | |
options.maxSizeMB * 1024 * 1024 | |
: ONE_MB_IN_BYTE; | |
if ( | |
// If the file is an image | |
file.type.startsWith("image/") && | |
// We trigger the compression logic only if the picture size is greater than our accepted maxMb size | |
file.size > maxSizeInBytes | |
) { | |
return true; | |
} | |
return false; | |
} | |
const DEFAULT_COMPRESSION_OPTIONS: Options = { | |
maxSizeMB: 1, | |
maxWidthOrHeight: 1024, | |
}; | |
/* WARNING : this doesn't seem to work on wkwebview *? | |
/* Resize and compresses an image file without big quality loss (which is the case for many canvas | |
resizing libraries), and returns a file and optionnaly return a displayable base64 image */ | |
async function compressImage(srcFile: File, options?: Options) { | |
/* | |
Using both blueimp-load-image and browser-image-resizer to cumulate two advantages of libraries: | |
image rotation with advanced exif and mobile rotations (blueimp-load-image) and image compression | |
with advanced image weight reduction (browser-image-resizer) | |
Waiting for answer in these two issues to only use one library (best would be blueimp-load-image): | |
- https://github.com/blueimp/JavaScript-Load-Image/issues/120 | |
- https://github.com/ericnograles/browser-image-resizer/issues/27 | |
*/ | |
const compressionOptions = { ...DEFAULT_COMPRESSION_OPTIONS, ...options }; | |
try { | |
if (mustCompress(srcFile, compressionOptions)) { | |
compressionOptions.onProgress?.(0); | |
const loadImageResult = await loadImage(srcFile, { | |
canvas: true, | |
crossOrigin: "anonymous", | |
downsamplingRatio: 1, | |
maxHeight: compressionOptions.maxWidthOrHeight, | |
maxWidth: compressionOptions.maxWidthOrHeight, | |
orientation: true, | |
}); | |
compressionOptions.onProgress?.(25); | |
const blobImage = await canvasToBlob( | |
loadImageResult.image as HTMLCanvasElement | |
); | |
const firstCompressionResult = await blobToFile( | |
blobImage, | |
srcFile.name, | |
srcFile.lastModified | |
); | |
compressionOptions.onProgress?.(50); | |
const secondCompressionResult = await readAndCompressImage( | |
firstCompressionResult, | |
{ | |
autoRotate: true, | |
maxHeight: compressionOptions.maxWidthOrHeight, | |
maxWidth: compressionOptions.maxWidthOrHeight, | |
quality: 0.7, | |
} | |
); | |
compressionOptions.onProgress?.(75); | |
const result = await blobToFile( | |
secondCompressionResult, | |
srcFile.name, | |
srcFile.lastModified | |
); | |
compressionOptions.onProgress?.(100); | |
return result; | |
} | |
return srcFile; | |
} catch (err) { | |
captureException(err, { | |
extra: { | |
file: srcFile, | |
fileSize: srcFile.size, | |
fileType: srcFile.type, | |
maxSize: compressionOptions.maxSizeMB, | |
title: "resizeAndCompressImage error", | |
}, | |
}); | |
return srcFile; | |
} | |
} | |
export { compressImage, mustCompress }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment