1
>
/*---------------------------------------------------------------------------------------------
image.ts
2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import { VSBuffer } from './buffer.js';
7
>
8
>
export interface IImageDimensions {
9
>
readonly width: number;
10
>
readonly height: number;
11
>
}
12
>
13
>
/**
14
>
* Read the pixel dimensions of an image by inspecting the bytes of its encoded
15
>
* buffer. The format is auto-detected from the magic-number prefix.
16
>
*
17
>
* Supports JPEG, PNG, GIF and WebP (lossy, lossless and extended).
18
>
*
19
>
* Returns `undefined` if the buffer does not start with a recognized signature
20
>
* or if the dimensions could not be parsed.
21
>
*/
22
>
export function readImageDimensions(buffer: VSBuffer): IImageDimensions | undefined {
23
>
const bytes = buffer.buffer;
24
>
if (bytes.length < 12) {
25
>
return undefined;
26
>
}
27
>
// JPEG: FF D8
28
>
if (bytes[0] === 0xFF && bytes[1] === 0xD8) {
29
>
return readJpegDimensions(bytes);
30
>
}
31
>
// PNG: 89 50 4E 47 0D 0A 1A 0A
32
>
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4E && bytes[3] === 0x47 &&
33
>
bytes[4] === 0x0D && bytes[5] === 0x0A && bytes[6] === 0x1A && bytes[7] === 0x0A) {
34
>
return readPngDimensions(bytes);
35
>
}
36
>
// GIF: "GIF87a" or "GIF89a"
37
>
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x38 &&
38
>
(bytes[4] === 0x37 || bytes[4] === 0x39) && bytes[5] === 0x61) {
39
>
return readGifDimensions(bytes);
40
>
}
41
>
// WebP: "RIFF" <size> "WEBP"
42
>
if (bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46 &&
43
>
bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50) {
44
>
return readWebPDimensions(bytes);
45
>
}
46
>
return undefined;
47
>
}
48
>
49
>
function readJpegDimensions(bytes: Uint8Array): IImageDimensions | undefined {
50
>
let i = 2;
51
>
while (i < bytes.length - 9) {
52
>
// Skip fill bytes (0xFF) before the marker.
53
>
while (i < bytes.length && bytes[i] === 0xFF) {
54
>
i++;
55
>
}
56
>
if (i >= bytes.length) {
57
return undefined;
58
}
60
>
i++;
61
>
// Standalone markers without a length field.
62
>
if (marker === 0xD8 || marker === 0xD9 || marker === 0x01 || (marker >= 0xD0 && marker <= 0xD7)) {
63
continue;
64
}
65
>
if (i + 1 >= bytes.length) {
image.ts
66
return undefined;
67
}
68
>
const segLength = (bytes[i] << 8) | bytes[i + 1];
image.ts
69
>
// The 2-byte length field includes itself, so anything < 2 is corrupt.
70
>
if (segLength < 2) {
71
>
return undefined;
72
>
}
73
>
// SOFn markers (0xC0..0xCF) carry the frame dimensions, except DHT (0xC4), JPG (0xC8) and DAC (0xCC).
74
>
if (marker >= 0xC0 && marker <= 0xCF && marker !== 0xC4 && marker !== 0xC8 && marker !== 0xCC) {
75
>
if (i + 6 >= bytes.length) {
76
return undefined;
77
}
78
>
const height = (bytes[i + 3] << 8) | bytes[i + 4];
image.ts
79
>
const width = (bytes[i + 5] << 8) | bytes[i + 6];
80
>
return { width, height };
81
>
}
82
>
i += segLength;
83
>
}
84
return undefined;
85
}
87
>
function readPngDimensions(bytes: Uint8Array): IImageDimensions | undefined {
88
>
// The IHDR chunk immediately follows the 8-byte signature: 4-byte length, 4-byte type "IHDR",
89
>
// then 4-byte width (BE) and 4-byte height (BE).
90
>
if (bytes.length < 24 ||
91
>
bytes[12] !== 0x49 || bytes[13] !== 0x48 || bytes[14] !== 0x44 || bytes[15] !== 0x52) {
92
return undefined;
93
}
94
>
const width = (bytes[16] << 24 | bytes[17] << 16 | bytes[18] << 8 | bytes[19]) >>> 0;
image.ts
95
>
const height = (bytes[20] << 24 | bytes[21] << 16 | bytes[22] << 8 | bytes[23]) >>> 0;
96
>
return { width, height };
97
>
}
98
>
99
>
function readGifDimensions(bytes: Uint8Array): IImageDimensions | undefined {
100
>
// Logical screen width/height are at bytes 6..9, little-endian.
101
>
if (bytes.length < 10) {
102
return undefined;
103
}
104
>
const width = bytes[6] | (bytes[7] << 8);
image.ts
105
>
const height = bytes[8] | (bytes[9] << 8);
106
>
return { width, height };
107
>
}
108
>
109
>
function readWebPDimensions(bytes: Uint8Array): IImageDimensions | undefined {
110
>
// Chunk header at bytes 12..15 selects the WebP variant.
111
>
if (bytes.length < 30) {
112
return undefined;
113
}
114
>
// The RIFF chunk payload size is declared at bytes 16..19 (LE) and must be large enough
image.ts
115
>
// to contain the bytes each variant reads, and must fit within the buffer.
116
>
const chunkSize = (bytes[16] | (bytes[17] << 8) | (bytes[18] << 16) | (bytes[19] << 24)) >>> 0;
117
>
if (chunkSize > bytes.length - 20) {
118
return undefined;
119
}
120
>
// VP8 (lossy, "VP8 " with trailing space). The frame tag occupies bytes 23..25 and must be the
image.ts
121
>
// 3-byte start code 0x9D 0x01 0x2A; without it the buffer is not a valid VP8 keyframe.
122
>
if (bytes[12] === 0x56 && bytes[13] === 0x50 && bytes[14] === 0x38 && bytes[15] === 0x20) {
123
>
if (chunkSize < 10 || bytes[23] !== 0x9D || bytes[24] !== 0x01 || bytes[25] !== 0x2A) {
124
>
return undefined;
125
>
}
126
>
const width = (bytes[26] | (bytes[27] << 8)) & 0x3FFF;
127
>
const height = (bytes[28] | (bytes[29] << 8)) & 0x3FFF;
128
>
return { width, height };
129
>
}
130
>
// VP8L (lossless). The bitstream starts at byte 21 (after the 0x2F signature). Width-1
131
>
// occupies bits 0..13 and height-1 occupies bits 14..27, both little-endian.
132
>
if (bytes[12] === 0x56 && bytes[13] === 0x50 && bytes[14] === 0x38 && bytes[15] === 0x4C) {
133
>
if (chunkSize < 5 || bytes[20] !== 0x2F) {
134
>
return undefined;
135
>
}
136
>
const width = ((bytes[21] | (bytes[22] << 8)) & 0x3FFF) + 1;
137
>
const height = (((bytes[22] >> 6) | (bytes[23] << 2) | ((bytes[24] & 0x0F) << 10)) & 0x3FFF) + 1;
138
>
return { width, height };
139
>
}
140
>
// VP8X (extended)
141
>
if (bytes[12] === 0x56 && bytes[13] === 0x50 && bytes[14] === 0x38 && bytes[15] === 0x58) {
142
>
if (chunkSize < 10) {
143
>
return undefined;
144
>
}
145
>
const width = ((bytes[24] | (bytes[25] << 8) | (bytes[26] << 16)) & 0xFFFFFF) + 1;
146
>
const height = ((bytes[27] | (bytes[28] << 8) | (bytes[29] << 16)) & 0xFFFFFF) + 1;
147
>
return { width, height };
148
>
}
149
return undefined;
150
}