mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(image-engine): add Sharp wrapper with 14 image operations
Operations: resize, crop, rotate, flip, convert, compress, strip-metadata, brightness, contrast, saturation, color-channels, grayscale, sepia, invert. Includes format detection, MIME mapping, metadata parsing, and engine orchestrator.
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import sharp from "sharp";
|
||||
import type {
|
||||
OperationResult,
|
||||
OutputFormat,
|
||||
Sharp,
|
||||
ResizeOptions,
|
||||
CropOptions,
|
||||
RotateOptions,
|
||||
FlipOptions,
|
||||
ConvertOptions,
|
||||
CompressOptions,
|
||||
StripMetadataOptions,
|
||||
BrightnessOptions,
|
||||
ContrastOptions,
|
||||
SaturationOptions,
|
||||
ColorChannelOptions,
|
||||
} from "./types.js";
|
||||
import { resize } from "./operations/resize.js";
|
||||
import { crop } from "./operations/crop.js";
|
||||
import { rotate } from "./operations/rotate.js";
|
||||
import { flip } from "./operations/flip.js";
|
||||
import { convert } from "./operations/convert.js";
|
||||
import { compress } from "./operations/compress.js";
|
||||
import { stripMetadata } from "./operations/strip-metadata.js";
|
||||
import { brightness } from "./operations/brightness.js";
|
||||
import { contrast } from "./operations/contrast.js";
|
||||
import { saturation } from "./operations/saturation.js";
|
||||
import { colorChannels } from "./operations/color-channels.js";
|
||||
import { grayscale } from "./operations/grayscale.js";
|
||||
import { sepia } from "./operations/sepia.js";
|
||||
import { invert } from "./operations/invert.js";
|
||||
import { getImageInfo } from "./utils/metadata.js";
|
||||
|
||||
export interface Operation {
|
||||
type: string;
|
||||
options: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const OPERATION_MAP: Record<
|
||||
string,
|
||||
(image: Sharp, options: Record<string, unknown>) => Promise<Sharp>
|
||||
> = {
|
||||
resize: (img, opts) => resize(img, opts as unknown as ResizeOptions),
|
||||
crop: (img, opts) => crop(img, opts as unknown as CropOptions),
|
||||
rotate: (img, opts) => rotate(img, opts as unknown as RotateOptions),
|
||||
flip: (img, opts) => flip(img, opts as unknown as FlipOptions),
|
||||
convert: (img, opts) => convert(img, opts as unknown as ConvertOptions),
|
||||
compress: (img, opts) => compress(img, opts as unknown as CompressOptions),
|
||||
"strip-metadata": (img, opts) =>
|
||||
stripMetadata(img, opts as unknown as StripMetadataOptions),
|
||||
brightness: (img, opts) => brightness(img, opts as unknown as BrightnessOptions),
|
||||
contrast: (img, opts) => contrast(img, opts as unknown as ContrastOptions),
|
||||
saturation: (img, opts) => saturation(img, opts as unknown as SaturationOptions),
|
||||
"color-channels": (img, opts) =>
|
||||
colorChannels(img, opts as unknown as ColorChannelOptions),
|
||||
grayscale: (img) => grayscale(img),
|
||||
sepia: (img) => sepia(img),
|
||||
invert: (img) => invert(img),
|
||||
};
|
||||
|
||||
const FORMAT_MAP: Record<OutputFormat, string> = {
|
||||
jpg: "jpeg",
|
||||
png: "png",
|
||||
webp: "webp",
|
||||
avif: "avif",
|
||||
tiff: "tiff",
|
||||
gif: "gif",
|
||||
};
|
||||
|
||||
/**
|
||||
* Process an image through a pipeline of operations.
|
||||
*
|
||||
* @param input - The raw image buffer
|
||||
* @param operations - Array of operations to apply in sequence
|
||||
* @param outputFormat - Optional output format (defaults to input format)
|
||||
* @returns The processed image buffer and metadata
|
||||
*/
|
||||
export async function processImage(
|
||||
input: Buffer,
|
||||
operations: Operation[],
|
||||
outputFormat?: OutputFormat
|
||||
): Promise<OperationResult> {
|
||||
let image: Sharp = sharp(input);
|
||||
|
||||
// Apply each operation in sequence
|
||||
for (const op of operations) {
|
||||
const handler = OPERATION_MAP[op.type];
|
||||
if (!handler) {
|
||||
throw new Error(`Unknown operation: ${op.type}`);
|
||||
}
|
||||
image = await handler(image, op.options);
|
||||
}
|
||||
|
||||
// Convert to output format if specified
|
||||
if (outputFormat) {
|
||||
const sharpFormat = FORMAT_MAP[outputFormat];
|
||||
if (!sharpFormat) {
|
||||
throw new Error(`Unsupported output format: ${outputFormat}`);
|
||||
}
|
||||
image = image.toFormat(sharpFormat as keyof import("sharp").FormatEnum);
|
||||
}
|
||||
|
||||
const buffer = await image.toBuffer();
|
||||
const info = await getImageInfo(buffer);
|
||||
|
||||
return { buffer, info };
|
||||
}
|
||||
Reference in New Issue
Block a user