Skip to content

Instantly share code, notes, and snippets.

@wmakeev
Last active May 29, 2023 06:57
Show Gist options
  • Select an option

  • Save wmakeev/e3cf3ef024ecc94a35b493cc710c613f to your computer and use it in GitHub Desktop.

Select an option

Save wmakeev/e3cf3ef024ecc94a35b493cc710c613f to your computer and use it in GitHub Desktop.
[Image resize] #imagemagick #resize #spawn #convert
import { spawn } from "node:child_process";
import type { Readable } from "node:stream";
export interface ImageResizeOptions {
quality?: number;
square?: boolean;
}
const CONVERT_ERR_MATCH_REGEX = /convert:\s(.+)\s`/gm;
const readStreamToBuffer = async (stream: Readable) => {
const chunks = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
const buffer = Buffer.concat(chunks);
return buffer;
};
/**
* Меняет размер изображения
*
* @param jpgStream Readable поток тела изображения
* @param maxSize Максимальная длина в пикселях наибольшей стороны изображения
* @param options Опции
* @returns Буфер с изображением
* @link https://www.smashingmagazine.com/2015/06/efficient-image-resizing-with-imagemagick/
*/
export async function resizeImage(
jpgStream: Readable,
maxSize: number,
options: ImageResizeOptions = {}
) {
const { quality = 82, square = false } = options;
/* prettier-ignore */
const convert = spawn(
'convert',
[
// Input from stream
'-',
// Resize
'-thumbnail', `${maxSize}x${maxSize}>`,
'-unsharp', '0.25x0.25+8+0.065',
// '-dither', 'None',
// '-posterize', '136',
'-quality', `${quality}`,
'-define', 'jpeg:fancy-upsampling=off',
// '-define', 'png:compression-filter=5',
// '-define', 'png:compression-level=9',
// '-define', 'png:compression-strategy=1',
// '-define', 'png:exclude-chunk=all',
'-interlace', 'none',
'-colorspace', 'sRGB',
// Make canvas square
...(
square
? [
'-gravity', 'center',
'-background', 'white',
'-extent', `${maxSize}x${maxSize}`
]
: []
),
'-strip',
// Output to stream
'jpg:-'
],
{
// https://nodejs.org/api/child_process.html#optionsstdio
// [stdin, stdout, stderr]
stdio: ['pipe', 'pipe', 'pipe']
}
)
jpgStream.pipe(convert.stdin!);
const [imageBuffer, errBuffer] = await Promise.all([
readStreamToBuffer(convert.stdout),
readStreamToBuffer(convert.stderr),
]);
if (errBuffer.length === 0 && imageBuffer.length) {
return imageBuffer;
} else {
// convert: no decode delegate for this image format `' @ error/constitute.c/ReadImage/746.
// convert: no images defined `jpg:-' @ error/convert.c/ConvertImageCommand/3342./
const errorMsg = errBuffer.toString("utf8");
let errors;
if (errorMsg) {
errors = [...errorMsg.matchAll(CONVERT_ERR_MATCH_REGEX)]
.map((m) => m[1])
.join(" | ");
}
throw new Error("Image resize error" + (errors ? ` - ${errors}` : ""));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment