Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,31 @@ describe('ImageTokenizer', () => {
});
});

describe('WebP dimension extraction', () => {
it('should extract canvas dimensions from VP8X', async () => {
const width = 100;
const height = 80;

const buf = Buffer.alloc(30);
buf.write('RIFF', 0, 'ascii');
buf.writeUInt32LE(22, 4);
buf.write('WEBP', 8, 'ascii');
buf.write('VP8X', 12, 'ascii');
buf.writeUInt32LE(10, 16); // VP8X chunk size
buf.writeUInt8(0, 20); // flags
buf.writeUIntLE(width - 1, 24, 3); // canvas width minus one (24-bit LE)
buf.writeUIntLE(height - 1, 27, 3); // canvas height minus one (24-bit LE)

const metadata = await tokenizer.extractImageMetadata(
buf.toString('base64'),
'image/webp',
);

expect(metadata.width).toBe(width);
expect(metadata.height).toBe(height);
});
});

describe('batch processing', () => {
it('should process multiple images serially', async () => {
const pngBase64 =
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/utils/request-tokenizer/imageTokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,17 +213,20 @@ export class ImageTokenizer {
const format = buffer.subarray(12, 16).toString('ascii');

if (format === 'VP8 ') {
// Lossy: 14-bit width/height at bytes 26-27 and 28-29 (little-endian)
const width = buffer.readUInt16LE(26) & 0x3fff;
const height = buffer.readUInt16LE(28) & 0x3fff;
return { width, height };
} else if (format === 'VP8L') {
// Lossless: 14-bit (width-1) then (height-1) packed from byte 21 (little-endian)
const bits = buffer.readUInt32LE(21);
const width = (bits & 0x3fff) + 1;
const height = ((bits >> 14) & 0x3fff) + 1;
return { width, height };
} else if (format === 'VP8X') {
const width = (buffer.readUInt32LE(24) & 0xffffff) + 1;
const height = (buffer.readUInt32LE(26) & 0xffffff) + 1;
// Extended: 24-bit (canvas width-1) at bytes 24-26 and (height-1) at bytes 27-29 (little-endian)
const width = buffer.readUIntLE(24, 3) + 1;
const height = buffer.readUIntLE(27, 3) + 1;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The byte offsets 24 and 27 are undocumented magic numbers. The surrounding codebase has an established convention: extractPngDimensions documents "Width/height at bytes 16-19 and 20-23", and extractJpegDimensions documents "Dimensions at offset +5 (height) and +7 (width)". The VP8X branch is the only format parser without such documentation.

The bug this PR fixes was precisely caused by a wrong offset (26 instead of 27) — the absence of a spec-reference comment makes this code fragile against future edits.

Suggested change
const height = buffer.readUIntLE(27, 3) + 1;
} else if (format === 'VP8X') {
// VP8X extended format: canvas dimensions are 24-bit LE values
// stored as (dimension - 1). Width at bytes 24-26, height at 27-29.
const width = buffer.readUIntLE(24, 3) + 1;
const height = buffer.readUIntLE(27, 3) + 1;
return { width, height };

— qwen3.7-max via Qwen Code /review

return { width, height };
}

Expand Down
Loading