const SHORT_EDGE = 768 const LONG_EDGE = 1344 export const ASPECT_PRESETS: Record = { '16:9': { width: 1344, height: 768 }, '9:16': { width: 768, height: 1344 }, '1:1': { width: 1024, height: 1024 } } export function snap32(value: number) { return Math.max(32, Math.round(value / 32) * 32) } /** Fit the source still to H3's native 768 short-edge canvas, 32px grid, 1344 long-edge cap. */ export function autoResolution(srcWidth: number, srcHeight: number) { const ratio = srcWidth / Math.max(srcHeight, 1) let width: number let height: number if (ratio >= 1) { height = SHORT_EDGE width = Math.min(LONG_EDGE, height * ratio) } else { width = SHORT_EDGE height = Math.min(LONG_EDGE, width / ratio) } width = snap32(width) height = snap32(height) if (width > LONG_EDGE) width = LONG_EDGE if (height > LONG_EDGE) height = LONG_EDGE return { width: Math.max(32, width), height: Math.max(32, height) } } export function imageDimensions(buf: Buffer) { if (buf.length < 24) return null if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) { return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) } } if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) { return { width: buf.readUInt16LE(6), height: buf.readUInt16LE(8) } } if (buf[0] === 0x42 && buf[1] === 0x4d && buf.length >= 26) { return { width: buf.readInt32LE(18), height: Math.abs(buf.readInt32LE(22)) } } if (buf.toString('ascii', 0, 4) === 'RIFF' && buf.toString('ascii', 8, 12) === 'WEBP') { return webpSize(buf) } if (buf[0] === 0xff && buf[1] === 0xd8) { return jpegSize(buf) } return null } function webpSize(buf: Buffer) { const kind = buf.toString('ascii', 12, 16) if (kind === 'VP8X' && buf.length >= 30) { const width = 1 + buf[24] + (buf[25] << 8) + (buf[26] << 16) const height = 1 + buf[27] + (buf[28] << 8) + (buf[29] << 16) return { width, height } } if (kind === 'VP8 ' && buf.length >= 30) { return { width: buf.readUInt16LE(26) & 0x3fff, height: buf.readUInt16LE(28) & 0x3fff } } if (kind === 'VP8L' && buf.length >= 25) { const bits = buf.readUInt32LE(21) return { width: (bits & 0x3fff) + 1, height: ((bits >> 14) & 0x3fff) + 1 } } return null } function jpegSize(buf: Buffer) { let offset = 2 while (offset + 9 < buf.length) { if (buf[offset] !== 0xff) return null const marker = buf[offset + 1] if (marker === 0xd8 || marker === 0xd9) { offset += 2 continue } const length = buf.readUInt16BE(offset + 2) if (marker >= 0xc0 && marker <= 0xc3) { return { width: buf.readUInt16BE(offset + 7), height: buf.readUInt16BE(offset + 5) } } offset += 2 + length } return null } export function resolveOutputSize(aspect: string | undefined, image: Buffer, fallbackWidth?: number, fallbackHeight?: number) { if (aspect && ASPECT_PRESETS[aspect]) { return ASPECT_PRESETS[aspect] } const fromFields = Number(fallbackWidth) && Number(fallbackHeight) ? { width: snap32(Number(fallbackWidth)), height: snap32(Number(fallbackHeight)) } : null const size = imageDimensions(image) if (size?.width && size?.height) { return autoResolution(size.width, size.height) } return fromFields || ASPECT_PRESETS['16:9'] }