import * as plugins from './plugins.js'; import * as pluginsTyped from './plugins.typed.js'; export interface IAssetVariation { format?: 'avif' | 'webp' | 'png' | 'jpeg'; width?: number; height?: number; invert?: boolean; progressive?: boolean; } export interface ISmartJimpOptions { mode: 'sharp' | 'jimp'; } export class SmartJimp { public levelCache = new plugins.levelcache.LevelCache({ cacheId: 'mastercache', maxMemoryStorageInMB: 100, maxDiskStorageInMB: 5000, }); public options: ISmartJimpOptions; constructor(optionsArg: ISmartJimpOptions) { this.options = optionsArg; } /** * get a key that is unique for a wanted asset variation */ private getCacheKey( sourceTypeArg: 'streamfile' | 'smartfile', sourceIdArg: string, assetVariationArg?: IAssetVariation ) { return `${sourceTypeArg}_${sourceIdArg}_${ assetVariationArg ? `${assetVariationArg.width || 'auto'}x${assetVariationArg.height || 'auto'}` : 'original' }`; } sharpMod: typeof pluginsTyped.sharpType.default; public async getSharpMod(): Promise< typeof pluginsTyped.sharpType.default > { if (!this.sharpMod) { this.sharpMod = (await import('sharp')).default; } return this.sharpMod; } jimpMod: typeof pluginsTyped.jimpType; public async getJimpMod(): Promise { if (!this.jimpMod) { this.jimpMod = await import('jimp'); } return this.jimpMod; } public async computeAssetVariation(assetBufferArg: Buffer, assetVariationArg: IAssetVariation) { if (this.options.mode === 'sharp') { const sharp = await this.getSharpMod(); if (!assetVariationArg) { return assetBufferArg; } let sharpImage = sharp(assetBufferArg); sharpImage = sharpImage.resize(assetVariationArg.width, assetVariationArg.height); const resultResize = sharpImage.resize(assetVariationArg.width, assetVariationArg.height); if (assetVariationArg.invert) { // TODO: implement invert } switch (assetVariationArg.format) { case 'avif': sharpImage = resultResize.avif(); break; case 'webp': sharpImage = resultResize.webp(); break; case 'png': sharpImage = resultResize.png(); break; case 'jpeg': sharpImage = resultResize.jpeg({ progressive: assetVariationArg.progressive || false }); break; default: // Default to JPEG sharpImage = resultResize.jpeg({ progressive: assetVariationArg.progressive || false }); } return sharpImage.toBuffer(); } else if (this.options.mode === 'jimp') { const jimpMod = await this.getJimpMod(); let jimpImage = await jimpMod.Jimp.read(assetBufferArg); if (assetVariationArg.width || assetVariationArg.height) { const resizeOptions: any = {}; if (assetVariationArg.width) resizeOptions.w = assetVariationArg.width; if (assetVariationArg.height) resizeOptions.h = assetVariationArg.height; jimpImage.resize(resizeOptions); } if (assetVariationArg.invert) { jimpImage.invert(); } // Note: Jimp does not support progressive JPEG encoding // Progressive option is ignored in jimp mode switch (assetVariationArg.format) { case 'png': return await jimpImage.getBuffer("image/png"); case 'jpeg': return await jimpImage.getBuffer("image/jpeg"); case 'webp': // Jimp doesn't support WebP, fallback to JPEG return await jimpImage.getBuffer("image/jpeg"); case 'avif': // Jimp doesn't support AVIF, fallback to JPEG return await jimpImage.getBuffer("image/jpeg"); default: // Default to JPEG return await jimpImage.getBuffer("image/jpeg"); } } } public async getFromSmartfile( smartfileArg: plugins.smartfile.SmartFile, wantedDimensionsArg?: IAssetVariation ) { const cacheKey = this.getCacheKey( 'smartfile', await smartfileArg.getHash(), wantedDimensionsArg ); const existingCacheEntry = await this.levelCache.retrieveCacheEntryByKey(cacheKey); if (existingCacheEntry) { return existingCacheEntry.contents; } else { const computedAssetBuffer = await this.computeAssetVariation( smartfileArg.contentBuffer, wantedDimensionsArg ); this.levelCache.storeCacheEntryByKey( cacheKey, new plugins.levelcache.CacheEntry({ contents: computedAssetBuffer, ttl: 600000, }) ); return computedAssetBuffer; } } public async createAvifImageFromBuffer(bufferArg: Buffer) { const sharp = await this.getSharpMod(); const sharpImage = sharp(bufferArg); const result = await sharpImage.avif().toBuffer(); return result; } }