This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import imageCompression from 'browser-image-compression';
|
||||
|
||||
/**
|
||||
* Client-side image compression shared by every photo upload (expense receipts,
|
||||
* offering paper-proofs). Phone cameras produce 12MP+ JPEGs that are several MB
|
||||
* each; resizing the longest edge and re-encoding as JPEG keeps uploads small
|
||||
* enough to clear the API's per-endpoint size limits (and the reverse proxy).
|
||||
*
|
||||
* Tunables: raise quality/edge if proofs look too soft; lower them if files are
|
||||
* too large.
|
||||
*/
|
||||
export class ImageUtils {
|
||||
/** Longest image edge (px) after compression. */
|
||||
public static readonly MAX_EDGE_PX = 2000;
|
||||
/** JPEG encoder quality, 0..1. */
|
||||
public static readonly JPEG_QUALITY = 0.72;
|
||||
/** Target ceiling per image, in megabytes. */
|
||||
public static readonly MAX_SIZE_MB = 1;
|
||||
|
||||
/**
|
||||
* Compresses a single image File to a resized JPEG. Non-image files (e.g. a
|
||||
* PDF) are returned untouched.
|
||||
*
|
||||
* The result is always renamed to a ".jpg" extension with an "image/jpeg"
|
||||
* type, because the API derives a stored receipt's content-type from its
|
||||
* filename extension — keeping a ".png" name on JPEG bytes would make the
|
||||
* download serve a broken image.
|
||||
*/
|
||||
public static async compressForUpload(file: File): Promise<File> {
|
||||
if (!file.type.startsWith('image/')) {
|
||||
return file;
|
||||
}
|
||||
|
||||
const compressed = await imageCompression(file, {
|
||||
maxWidthOrHeight: ImageUtils.MAX_EDGE_PX,
|
||||
maxSizeMB: ImageUtils.MAX_SIZE_MB,
|
||||
initialQuality: ImageUtils.JPEG_QUALITY,
|
||||
fileType: 'image/jpeg',
|
||||
useWebWorker: true,
|
||||
});
|
||||
|
||||
const baseName = file.name.replace(/\.[^.]+$/, '');
|
||||
return new File([compressed], `${baseName}.jpg`, { type: 'image/jpeg' });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user