Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP Performance tracing image optimizer #27047

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
"raw-body": "2.4.1",
"react-is": "17.0.2",
"react-refresh": "0.8.3",
"sharp": "0.28.3",
"stream-browserify": "3.0.0",
"stream-http": "3.1.1",
"string_decoder": "1.3.0",
Expand Down Expand Up @@ -180,6 +181,7 @@
"@types/react-is": "16.7.1",
"@types/semver": "7.3.1",
"@types/send": "0.14.4",
"@types/sharp": "0.28.4",
"@types/styled-jsx": "2.2.8",
"@types/text-table": "0.2.1",
"@types/webpack": "5.28.0",
Expand Down
86 changes: 20 additions & 66 deletions packages/next/server/lib/squoosh/impl.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,44 @@
import { codecs as supportedFormats, preprocessors } from './codecs'
import ImageData from './image_data'
import sharp from 'sharp'

export async function decodeBuffer(
_buffer: Buffer | Uint8Array
): Promise<ImageData> {
const buffer = Buffer.from(_buffer)
const firstChunk = buffer.slice(0, 16)
const firstChunkString = Array.from(firstChunk)
.map((v) => String.fromCodePoint(v))
.join('')
const key = Object.entries(supportedFormats).find(([, { detectors }]) =>
detectors.some((detector) => detector.exec(firstChunkString))
)?.[0] as keyof typeof supportedFormats
if (!key) {
throw Error(`Buffer has an unsupported format`)
}
const d = await supportedFormats[key].dec()
return d.decode(new Uint8Array(buffer))
buffer: Buffer
): Promise<{ width?: number; height?: number }> {
const { width, height } = await sharp(buffer).metadata()
return { width, height }
}

export async function rotate(
image: ImageData,
image: Buffer,
numRotations: number
): Promise<ImageData> {
image = ImageData.from(image)

const m = await preprocessors['rotate'].instantiate()
return await m(image.data, image.width, image.height, { numRotations })
): Promise<Buffer> {
return sharp(image)
.rotate(numRotations * 90)
.toBuffer()
}

type ResizeOpts = { image: ImageData } & (
type ResizeOpts = { image: Buffer } & (
| { width: number; height?: never }
| { height: number; width?: never }
)

export async function resize({ image, width, height }: ResizeOpts) {
image = ImageData.from(image)

const p = preprocessors['resize']
const m = await p.instantiate()
return await m(image.data, image.width, image.height, {
...p.defaultOptions,
width,
height,
})
return sharp(image).resize(width, height).toBuffer()
}

export async function encodeJpeg(
image: ImageData,
image: Buffer,
{ quality }: { quality: number }
): Promise<Buffer | Uint8Array> {
image = ImageData.from(image)

const e = supportedFormats['mozjpeg']
const m = await e.enc()
const r = await m.encode!(image.data, image.width, image.height, {
...e.defaultEncoderOptions,
quality,
})
return Buffer.from(r)
): Promise<Buffer> {
return sharp(image).jpeg({ quality }).toBuffer()
}

export async function encodeWebp(
image: ImageData,
image: Buffer,
{ quality }: { quality: number }
): Promise<Buffer | Uint8Array> {
image = ImageData.from(image)

const e = supportedFormats['webp']
const m = await e.enc()
const r = await m.encode!(image.data, image.width, image.height, {
...e.defaultEncoderOptions,
quality,
})
return Buffer.from(r)
): Promise<Buffer> {
return sharp(image).webp({ quality }).toBuffer()
}

export async function encodePng(
image: ImageData
): Promise<Buffer | Uint8Array> {
image = ImageData.from(image)

const e = supportedFormats['oxipng']
const m = await e.enc()
const r = await m.encode(image.data, image.width, image.height, {
...e.defaultEncoderOptions,
})
return Buffer.from(r)
export async function encodePng(image: Buffer): Promise<Buffer | Uint8Array> {
return sharp(image).png().toBuffer()
}
17 changes: 6 additions & 11 deletions packages/next/server/lib/squoosh/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Worker } from 'jest-worker'
import * as path from 'path'
import { execOnce } from '../../../shared/lib/utils'
import { cpus } from 'os'
import * as worker from './impl'

type RotateOperation = {
type: 'rotate'
Expand All @@ -25,31 +26,25 @@ const getWorker = execOnce(
)

export async function processBuffer(
buffer: Buffer,
imageData: Buffer,
operations: Operation[],
encoding: Encoding,
quality: number
): Promise<Buffer> {
const worker: typeof import('./impl') = getWorker() as any

let imageData = await worker.decodeBuffer(buffer)
let meta = await worker.decodeBuffer(imageData)
for (const operation of operations) {
if (operation.type === 'rotate') {
imageData = await worker.rotate(imageData, operation.numRotations)
} else if (operation.type === 'resize') {
if (
operation.width &&
imageData.width &&
imageData.width > operation.width
) {
if (operation.width && meta.width && meta.width > operation.width) {
imageData = await worker.resize({
image: imageData,
width: operation.width,
})
} else if (
operation.height &&
imageData.height &&
imageData.height > operation.height
meta.height &&
meta.height > operation.height
) {
imageData = await worker.resize({
image: imageData,
Expand Down
Loading