102 lines
2.7 KiB
TypeScript
102 lines
2.7 KiB
TypeScript
import extract from "png-chunks-extract";
|
||
|
||
export default class FileHelper {
|
||
/**
|
||
* Checks if a file is an image.
|
||
*
|
||
* @param file The file to check
|
||
* @returns True if the file is an image
|
||
*/
|
||
static isImage(file: File) {
|
||
return file.type.startsWith("image/");
|
||
}
|
||
|
||
/**
|
||
* Checks if a file is a video.
|
||
*
|
||
* @param file The file to check
|
||
* @returns True if the file is an video
|
||
*/
|
||
static isVideo(file: File) {
|
||
return file.type.startsWith("video/");
|
||
}
|
||
|
||
/**
|
||
* Loads the dimensions of a video file.
|
||
*
|
||
* @param file The file to load the dimensions for
|
||
* @returns The dimensions of the video
|
||
*/
|
||
static getVideoDimensions(
|
||
file: File
|
||
): Promise<{ width: number; height: number }> {
|
||
return new Promise((resolve, reject) => {
|
||
const video = document.createElement("video");
|
||
video.preload = "metadata";
|
||
video.crossOrigin = "anonymous";
|
||
video.onloadedmetadata = () => {
|
||
window.URL.revokeObjectURL(video.src);
|
||
resolve({ width: video.videoWidth, height: video.videoHeight });
|
||
};
|
||
video.onerror = reject;
|
||
video.src = URL.createObjectURL(file);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Loads the dimensions of an image file – currently only PNGs are supported – but we mainly use
|
||
* this to get the "real" dimensions of a retina image.
|
||
*
|
||
* @param file The file to load the dimensions for
|
||
* @returns The dimensions of the image, if known.
|
||
*/
|
||
static async getImageDimensions(
|
||
file: File
|
||
): Promise<{ width: number; height: number } | undefined> {
|
||
if (file.type !== "image/png") {
|
||
return;
|
||
}
|
||
|
||
function parsePhys(view: DataView) {
|
||
return {
|
||
ppux: view.getUint32(0),
|
||
ppuy: view.getUint32(4),
|
||
unit: view.getUint8(4),
|
||
};
|
||
}
|
||
|
||
function parseIHDR(view: DataView) {
|
||
return {
|
||
width: view.getUint32(0),
|
||
height: view.getUint32(4),
|
||
};
|
||
}
|
||
|
||
try {
|
||
const buffer = await file.arrayBuffer();
|
||
const chunks = extract(new Uint8Array(buffer));
|
||
const pHYsChunk = chunks.find((chunk) => chunk.name === "pHYs");
|
||
const iHDRChunk = chunks.find((chunk) => chunk.name === "IHDR");
|
||
|
||
if (!pHYsChunk || !iHDRChunk) {
|
||
return;
|
||
}
|
||
|
||
const idhrData = parseIHDR(new DataView(iHDRChunk.data.buffer));
|
||
const physData = parsePhys(new DataView(pHYsChunk.data.buffer));
|
||
|
||
if (physData.unit === 0 && physData.ppux === physData.ppuy) {
|
||
const pixelRatio = Math.round(physData.ppux / 2834.5);
|
||
return {
|
||
width: idhrData.width / pixelRatio,
|
||
height: idhrData.height / pixelRatio,
|
||
};
|
||
}
|
||
} catch (_e) {
|
||
return undefined;
|
||
}
|
||
|
||
return undefined;
|
||
}
|
||
}
|