From 1c19618eb2e1c25db07fdf59504774dce589d4c6 Mon Sep 17 00:00:00 2001 From: rubikscraft Date: Wed, 28 Dec 2022 16:49:45 +0100 Subject: [PATCH] Store animated images as lossless webp --- .../managers/image/image-converter.service.ts | 22 ++++++++++++------- .../managers/image/image-processor.service.ts | 15 ++++++++----- backend/src/workers/sharp/sharp.message.ts | 4 ++++ backend/src/workers/sharp/universal-sharp.ts | 13 ++++++----- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/backend/src/managers/image/image-converter.service.ts b/backend/src/managers/image/image-converter.service.ts index a43c178..76f85b1 100644 --- a/backend/src/managers/image/image-converter.service.ts +++ b/backend/src/managers/image/image-converter.service.ts @@ -3,7 +3,7 @@ import ms from 'ms'; import { ImageRequestParams } from 'picsur-shared/dist/dto/api/image.dto'; import { FileType, - SupportedFileTypeCategory, + SupportedFileTypeCategory } from 'picsur-shared/dist/dto/mimes.dto'; import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum'; import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types'; @@ -12,6 +12,13 @@ import { SysPreferenceDbService } from '../../collections/preference-db/sys-pref import { SharpWrapper } from '../../workers/sharp.wrapper'; import { ImageResult } from './imageresult'; +interface InternalConvertOptions { + lossless?: boolean; + effort?: number; +} + +export type ConvertOptions = ImageRequestParams & InternalConvertOptions; + @Injectable() export class ImageConverterService { constructor(private readonly sysPref: SysPreferenceDbService) {} @@ -20,7 +27,7 @@ export class ImageConverterService { image: Buffer, sourceFiletype: FileType, targetFiletype: FileType, - options: ImageRequestParams, + options: ConvertOptions, ): AsyncFailable { if ( sourceFiletype.identifier === targetFiletype.identifier && @@ -32,23 +39,22 @@ export class ImageConverterService { }; } - if (targetFiletype.category === SupportedFileTypeCategory.Image) { - return this.convertStill(image, sourceFiletype, targetFiletype, options); - } else if ( + if ( + targetFiletype.category === SupportedFileTypeCategory.Image || targetFiletype.category === SupportedFileTypeCategory.Animation ) { - return this.convertStill(image, sourceFiletype, targetFiletype, options); + return this.convertImage(image, sourceFiletype, targetFiletype, options); //return this.convertAnimation(image, targetmime, options); } else { return Fail(FT.SysValidation, 'Unsupported mime type'); } } - private async convertStill( + private async convertImage( image: Buffer, sourceFiletype: FileType, targetFiletype: FileType, - options: ImageRequestParams, + options: ConvertOptions, ): AsyncFailable { const [memLimit, timeLimit] = await Promise.all([ this.sysPref.getNumberPreference(SysPreference.ConversionMemoryLimit), diff --git a/backend/src/managers/image/image-processor.service.ts b/backend/src/managers/image/image-processor.service.ts index ee451d0..4e93ed0 100644 --- a/backend/src/managers/image/image-processor.service.ts +++ b/backend/src/managers/image/image-processor.service.ts @@ -1,8 +1,9 @@ import { Injectable } from '@nestjs/common'; import { + AnimFileType, FileType, ImageFileType, - SupportedFileTypeCategory, + SupportedFileTypeCategory } from 'picsur-shared/dist/dto/mimes.dto'; import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types'; @@ -41,10 +42,12 @@ export class ImageProcessorService { image: Buffer, filetype: FileType, ): AsyncFailable { - // Webps and gifs are stored as is for now - return { - image: image, - filetype: filetype.identifier, - }; + const outputFileType = ParseFileType(AnimFileType.WEBP); + if (HasFailed(outputFileType)) return outputFileType; + + return this.imageConverter.convert(image, filetype, outputFileType, { + lossless: true, + effort: 0, + }); } } diff --git a/backend/src/workers/sharp/sharp.message.ts b/backend/src/workers/sharp/sharp.message.ts index b1f5268..cd8cac2 100644 --- a/backend/src/workers/sharp/sharp.message.ts +++ b/backend/src/workers/sharp/sharp.message.ts @@ -26,6 +26,10 @@ export type SharpWorkerOperation = export interface SharpWorkerFinishOptions { quality?: number; + + // Only for internal use + lossless?: boolean; + effort?: number; } // Messages diff --git a/backend/src/workers/sharp/universal-sharp.ts b/backend/src/workers/sharp/universal-sharp.ts index ba01820..89e625d 100644 --- a/backend/src/workers/sharp/universal-sharp.ts +++ b/backend/src/workers/sharp/universal-sharp.ts @@ -2,10 +2,11 @@ import { BMPdecode, BMPencode } from 'bmp-img'; import { AnimFileType, FileType, - ImageFileType, + ImageFileType } from 'picsur-shared/dist/dto/mimes.dto'; import { QOIdecode, QOIencode } from 'qoi-img'; import sharp, { Sharp, SharpOptions } from 'sharp'; +import { SharpWorkerFinishOptions } from './sharp.message'; export interface SharpResult { data: Buffer; @@ -72,9 +73,7 @@ function qoiSharpIn(image: Buffer, options?: SharpOptions) { export async function UniversalSharpOut( image: Sharp, filetype: FileType, - options?: { - quality?: number; - }, + options?: SharpWorkerFinishOptions, ): Promise { let result: SharpResult | undefined; @@ -103,7 +102,11 @@ export async function UniversalSharpOut( case ImageFileType.WEBP: case AnimFileType.WEBP: result = await image - .webp({ quality: options?.quality }) + .webp({ + quality: options?.quality, + lossless: options?.lossless, + effort: options?.effort, + }) .toBuffer({ resolveWithObject: true }); break; case AnimFileType.GIF: