import { BMPdecode, BMPencode } from 'bmp-img'; import { AnimFileType, FileType, ImageFileType } from 'picsur-shared/dist/dto/mimes.dto'; import { QOIdecode, QOIencode } from 'qoi-img'; import sharp, { Sharp, SharpOptions } from 'sharp'; export interface SharpResult { data: Buffer; info: sharp.OutputInfo; } export function UniversalSharpIn( image: Buffer, filetype: FileType, options?: SharpOptions, ): Sharp { // if (mime.mime === ImageFileType.ICO) { // return icoSharpIn(image, options); // } else if (filetype.identifier === ImageFileType.BMP) { return bmpSharpIn(image, options); } else if (filetype.identifier === ImageFileType.QOI) { return qoiSharpIn(image, options); } else { return sharp(image, options); } } function bmpSharpIn(image: Buffer, options?: SharpOptions) { const bitmap = BMPdecode(image); return sharp(bitmap.pixels, { ...options, raw: { width: bitmap.width, height: bitmap.height, channels: bitmap.channels, }, }); } // function icoSharpIn(image: Buffer, options?: SharpOptions) { // const result = decodeico(image); // // Get biggest image // const best = result.sort((a, b) => b.width - a.width)[0]; // return sharp(best.data, { // ...options, // raw: { // width: best.width, // height: best.height, // channels: 4, // }, // }); // } function qoiSharpIn(image: Buffer, options?: SharpOptions) { const result = QOIdecode(image); return sharp(result.pixels, { ...options, raw: { width: result.width, height: result.height, channels: result.channels, }, }); } export async function UniversalSharpOut( image: Sharp, filetype: FileType, options?: { quality?: number; }, ): Promise { let result: SharpResult | undefined; switch (filetype.identifier) { case ImageFileType.PNG: result = await image .png({ quality: options?.quality }) .toBuffer({ resolveWithObject: true }); break; case ImageFileType.JPEG: result = await image .jpeg({ quality: options?.quality }) .toBuffer({ resolveWithObject: true }); break; case ImageFileType.TIFF: result = await image .tiff({ quality: options?.quality }) .toBuffer({ resolveWithObject: true }); break; case ImageFileType.BMP: result = await bmpSharpOut(image); break; case ImageFileType.QOI: result = await qoiSharpOut(image); break; case ImageFileType.WEBP: case AnimFileType.WEBP: result = await image .webp({ quality: options?.quality }) .toBuffer({ resolveWithObject: true }); break; case AnimFileType.GIF: result = await image.gif().toBuffer({ resolveWithObject: true }); break; default: throw new Error('Unsupported mime type'); } return result; } async function bmpSharpOut(sharpImage: Sharp): Promise { const raw = await sharpImage.raw().toBuffer({ resolveWithObject: true }); if (raw.info.channels === 1) no1Channel(raw); const encoded = BMPencode(raw.data, { width: raw.info.width, height: raw.info.height, channels: raw.info.channels, }); return { data: encoded, info: raw.info, }; } async function qoiSharpOut(sharpImage: Sharp): Promise { const raw = await sharpImage.raw().toBuffer({ resolveWithObject: true }); if (raw.info.channels === 1) no1Channel(raw); const encoded = QOIencode(raw.data, { width: raw.info.width, height: raw.info.height, channels: raw.info.channels, }); return { data: encoded, info: raw.info, }; } function no1Channel(input: SharpResult): SharpResult { const old = input.data; input.data = Buffer.alloc(input.info.width * input.info.height * 3); for (let i = 0; i < old.length; i++) { input.data[i * 3] = old[i]; input.data[i * 3 + 1] = old[i]; input.data[i * 3 + 2] = old[i]; } input.info.channels = 3; input.info.size = input.data.length; return input; }