Files
goodbuddy/src/main/bounded-jpeg.ts
T
2026-08-05 19:40:40 +08:00

59 lines
1.4 KiB
TypeScript

export const MAX_BOUNDED_JPEG_BYTES = 220 * 1024
export const BOUNDED_JPEG_QUALITIES = [60, 45, 30, 20, 10] as const
type JpegImage = {
getSize(): { width: number; height: number }
resize(options: {
width: number
quality: 'good'
}): JpegImage
toJPEG(quality: number): Buffer
}
export function isValidJpeg(data: Buffer): boolean {
return (
data.byteLength >= 4 &&
data[0] === 0xff &&
data[1] === 0xd8 &&
data.at(-2) === 0xff &&
data.at(-1) === 0xd9
)
}
export function encodeBoundedJpeg(
image: JpegImage,
maximumBytes = MAX_BOUNDED_JPEG_BYTES
): Buffer {
if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 4) {
throw new Error('JPEG 大小限制无效')
}
const initialWidth = Math.max(1, image.getSize().width)
const widths = [
initialWidth,
1_600,
1_280,
960,
720
].filter(
(width, index, values) =>
width <= initialWidth && values.indexOf(width) === index
)
for (const width of widths) {
const candidate =
width === initialWidth
? image
: image.resize({ width, quality: 'good' })
for (const quality of BOUNDED_JPEG_QUALITIES) {
const data = candidate.toJPEG(quality)
if (!isValidJpeg(data)) {
throw new Error('JPEG 图片内容无效')
}
if (data.byteLength <= maximumBytes) {
return data
}
}
}
throw new Error('JPEG 图片压缩后仍然过大')
}