UNPKG

nitropage

Version:

A free and open source, extensible visual page builder based on SolidStart.

108 lines (94 loc) 2.47 kB
import { decode, encode } from "blurhash"; import sharp, { Sharp } from "sharp"; import { Readable } from "stream"; import { fileType, svgMime } from "../../media"; import { bufferFromReadable } from "./util"; export const processImageUpload = async function (params: { contentType: string; stream: Readable; }) { const type = fileType(params.contentType); if (type !== "image") { return { stream: params.stream, }; } const buffer = await bufferFromReadable(params.stream); const sharpStream = sharp(buffer); const meta = await sharpStream.metadata(); const isAnimated = meta.pages && meta.pages > 1; const optimizable = params.contentType !== svgMime && !isAnimated; sharpStream.rotate(); const info = Promise.all([ sharpStream.stats(), generateBlurhash(optimizable ? sharpStream.clone() : sharpStream), ]).then(([stats, { blurhash, dataUrl }]) => { return { meta, stats, blurhash, dataUrl, isAnimated, }; }); return { stream: optimizable ? sharpStream : Readable.from(buffer), info, }; }; const generateBlurhash = async (stream: Sharp) => { const resized = await stream .raw() .ensureAlpha() .resize(32) .toBuffer({ resolveWithObject: true }); const xComponent = 4; const yComponent = Math.round( (xComponent / resized.info.width) * resized.info.height, ); const blurhash = encode( new Uint8ClampedArray(resized.data), resized.info.width!, resized.info.height!, xComponent, yComponent, ); const dataUrl = await generateBlurDataUrl( blurhash, resized.info.width, resized.info.height, ); return { blurhash, dataUrl }; }; /** * Source: https://github.com/woltapp/blurhash/issues/43#issuecomment-759112713 */ const generateBlurDataUrl = async ( hash: string, width: number, height: number, options = { size: 10, quality: 25, }, ) => { const hashWidth = options?.size; const hashHeight = Math.round(hashWidth * (height / width)); const pixels = decode(hash, hashWidth, hashHeight); const resizedImageBuf = await sharp(Buffer.from(pixels as any), { raw: { channels: 4, width: hashWidth, height: hashHeight, }, }) .modulate({ saturation: 1.2, }) .jpeg({ overshootDeringing: true, quality: options.quality, }) .toBuffer(); return `data:image/jpeg;base64,${resizedImageBuf.toString("base64")}`; };