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:
Siddharth Kumar Sah
2026-03-22 03:42:40 +08:00
parent 5ab4c96ef7
commit ccac5b885c
23 changed files with 1111 additions and 3 deletions
+107
View File
@@ -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 };
}