Last active
May 29, 2023 06:57
-
-
Save wmakeev/e3cf3ef024ecc94a35b493cc710c613f to your computer and use it in GitHub Desktop.
[Image resize] #imagemagick #resize #spawn #convert
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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