nitropage
Version:
A free and open source, extensible visual page builder based on SolidStart.
108 lines (94 loc) • 2.47 kB
text/typescript
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")}`;
};