Skip to content

Instantly share code, notes, and snippets.

@Aarbel
Created September 5, 2022 09:07
Show Gist options
  • Save Aarbel/c657991441dce71ef6a33dc3e781d117 to your computer and use it in GitHub Desktop.
Save Aarbel/c657991441dce71ef6a33dc3e781d117 to your computer and use it in GitHub Desktop.
Compress image in the browser
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