未验证 提交 241a916e 编写于 作者: S Shu Ding 提交者: GitHub

Improve image optimizer to only create 1 worker thread (#23188)

As stated in #23157, this PR merges all the operations into 1 worker thread (`processBuffer` in `impl.ts`) and only pass a list of operation names and args into the worker. This should improve the speed and memory usage of next/image.

Fixes #23157. X-ref: #22925.
上级 70d306e0
......@@ -10,15 +10,7 @@ import Stream from 'stream'
import nodeUrl, { UrlWithParsedQuery } from 'url'
import { fileExists } from '../../lib/file-exists'
import { ImageConfig, imageConfigDefault } from './image-config'
import ImageData from './lib/squoosh/image_data'
import {
decodeBuffer,
encodeJpeg,
encodePng,
encodeWebp,
resize,
rotate,
} from './lib/squoosh/main'
import { processBuffer, Operation } from './lib/squoosh/main'
import Server from './next-server'
import { sendEtagResponse } from './send-payload'
import { getContentType, getExtension } from './serve-static'
......@@ -251,33 +243,48 @@ export async function imageOptimizer(
}
try {
let bitmap: ImageData = await decodeBuffer(upstreamBuffer)
const orientation = await getOrientation(upstreamBuffer)
const operations: Operation[] = []
if (orientation === Orientation.RIGHT_TOP) {
bitmap = await rotate(bitmap, 1)
operations.push({ type: 'rotate', numRotations: 1 })
} else if (orientation === Orientation.BOTTOM_RIGHT) {
bitmap = await rotate(bitmap, 2)
operations.push({ type: 'rotate', numRotations: 2 })
} else if (orientation === Orientation.LEFT_BOTTOM) {
bitmap = await rotate(bitmap, 3)
operations.push({ type: 'rotate', numRotations: 3 })
} else {
// TODO: support more orientations
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// const _: never = orientation
}
if (bitmap.width && bitmap.width > width) {
bitmap = await resize(bitmap, { width })
}
operations.push({ type: 'resize', width })
let optimizedBuffer: Buffer | undefined
//if (contentType === AVIF) {
//} else
if (contentType === WEBP) {
optimizedBuffer = await encodeWebp(bitmap, { quality })
optimizedBuffer = await processBuffer(
upstreamBuffer,
operations,
'webp',
quality
)
} else if (contentType === PNG) {
optimizedBuffer = await encodePng(bitmap)
optimizedBuffer = await processBuffer(
upstreamBuffer,
operations,
'png',
quality
)
} else if (contentType === JPEG) {
optimizedBuffer = await encodeJpeg(bitmap, { quality })
optimizedBuffer = await processBuffer(
upstreamBuffer,
operations,
'jpeg',
quality
)
}
if (optimizedBuffer) {
......
import { codecs as supportedFormats, preprocessors } from './codecs'
import ImageData from './image_data'
export async function decodeBuffer(
_buffer: Buffer | Uint8Array
): Promise<ImageData> {
type RotateOperation = {
type: 'rotate'
numRotations: number
}
type ResizeOperation = {
type: 'resize'
width: number
}
export type Operation = RotateOperation | ResizeOperation
export type Encoding = 'jpeg' | 'png' | 'webp'
export async function processBuffer(
buffer: Buffer | Uint8Array,
operations: Operation[],
encoding: Encoding,
quality: number
): Promise<Buffer | Uint8Array> {
let imageData = await decodeBuffer(buffer)
for (const operation of operations) {
if (operation.type === 'rotate') {
imageData = await rotate(imageData, operation.numRotations)
} else if (operation.type === 'resize') {
if (imageData.width && imageData.width > operation.width) {
imageData = await resize(imageData, operation.width)
}
}
}
switch (encoding) {
case 'jpeg':
return encodeJpeg(imageData, { quality })
case 'webp':
return encodeWebp(imageData, { quality })
case 'png':
return encodePng(imageData)
default:
throw Error(`Unsupported encoding format`)
}
}
async function decodeBuffer(_buffer: Buffer | Uint8Array): Promise<ImageData> {
const buffer = Buffer.from(_buffer)
const firstChunk = buffer.slice(0, 16)
const firstChunkString = Array.from(firstChunk)
......@@ -20,7 +58,7 @@ export async function decodeBuffer(
return rgba
}
export async function rotate(
async function rotate(
image: ImageData,
numRotations: number
): Promise<ImageData> {
......@@ -30,7 +68,7 @@ export async function rotate(
return await m(image.data, image.width, image.height, { numRotations })
}
export async function resize(image: ImageData, { width }: { width: number }) {
async function resize(image: ImageData, width: number) {
image = ImageData.from(image)
const p = preprocessors['resize']
......@@ -41,7 +79,7 @@ export async function resize(image: ImageData, { width }: { width: number }) {
})
}
export async function encodeJpeg(
async function encodeJpeg(
image: ImageData,
{ quality }: { quality: number }
): Promise<Buffer | Uint8Array> {
......@@ -56,7 +94,7 @@ export async function encodeJpeg(
return Buffer.from(r)
}
export async function encodeWebp(
async function encodeWebp(
image: ImageData,
{ quality }: { quality: number }
): Promise<Buffer | Uint8Array> {
......@@ -71,9 +109,7 @@ export async function encodeWebp(
return Buffer.from(r)
}
export async function encodePng(
image: ImageData
): Promise<Buffer | Uint8Array> {
async function encodePng(image: ImageData): Promise<Buffer | Uint8Array> {
image = ImageData.from(image)
const e = supportedFormats['oxipng']
......
import { Worker } from 'jest-worker'
import * as path from 'path'
import { execOnce } from '../../../lib/utils'
import ImageData from './image_data'
import { Operation, Encoding } from './impl'
const getWorker = execOnce(
() =>
......@@ -10,47 +10,16 @@ const getWorker = execOnce(
})
)
export async function decodeBuffer(buffer: Buffer): Promise<ImageData> {
const worker: typeof import('./impl') = getWorker() as any
return ImageData.from(await worker.decodeBuffer(buffer))
}
export async function rotate(
image: ImageData,
numRotations: number
): Promise<ImageData> {
const worker: typeof import('./impl') = getWorker() as any
return ImageData.from(await worker.rotate(image, numRotations))
}
export async function resize(
image: ImageData,
{ width }: { width: number }
): Promise<ImageData> {
const worker: typeof import('./impl') = getWorker() as any
return ImageData.from(await worker.resize(image, { width }))
}
export { Operation }
export async function encodeJpeg(
image: ImageData,
{ quality }: { quality: number }
export async function processBuffer(
buffer: Buffer,
operations: Operation[],
encoding: Encoding,
quality: number
): Promise<Buffer> {
const worker: typeof import('./impl') = getWorker() as any
const o = await worker.encodeJpeg(image, { quality })
return Buffer.from(o)
}
export async function encodeWebp(
image: ImageData,
{ quality }: { quality: number }
): Promise<Buffer> {
const worker: typeof import('./impl') = getWorker() as any
const o = await worker.encodeWebp(image, { quality })
return Buffer.from(o)
}
export async function encodePng(image: ImageData): Promise<Buffer> {
const worker: typeof import('./impl') = getWorker() as any
const o = await worker.encodePng(image)
return Buffer.from(o)
return Buffer.from(
await worker.processBuffer(buffer, operations, encoding, quality)
)
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册