Skip to content

Commit

Permalink
optimize the operation workers
Browse files Browse the repository at this point in the history
  • Loading branch information
shuding committed Mar 18, 2021
1 parent 7757364 commit 7c025f1
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 70 deletions.
45 changes: 26 additions & 19 deletions packages/next/next-server/server/image-optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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) {
Expand Down
56 changes: 46 additions & 10 deletions packages/next/next-server/server/lib/squoosh/impl.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,47 @@
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)
Expand All @@ -20,7 +58,7 @@ export async function decodeBuffer(
return rgba
}

export async function rotate(
async function rotate(
image: ImageData,
numRotations: number
): Promise<ImageData> {
Expand All @@ -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']
Expand All @@ -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> {
Expand All @@ -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> {
Expand All @@ -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']
Expand Down
51 changes: 10 additions & 41 deletions packages/next/next-server/server/lib/squoosh/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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(
() =>
Expand All @@ -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)
)
}

0 comments on commit 7c025f1

Please sign in to comment.