1export async function pngChunks(blob) {
2 const uint8arr = new Uint8Array(await blob.arrayBuffer());
3 const chunks = [];
4 if (uint8arr.length < 12) return chunks;
5 const view = new DataView(uint8arr.buffer);
6 if (view.getBigUint64(0) !== 9894494448401390090n) return chunks;
7
8 const decoder = new TextDecoder();
9 let index = 8;
10 while (index < uint8arr.length) {
11 const len = view.getUint32(index);
12 chunks.push({
13 name: decoder.decode(uint8arr.slice(index + 4, index + 8)),
14 data: uint8arr.slice(index + 8, index + 8 + len),
15 });
16 index += len + 12;
17 }
18
19 return chunks;
20}
21
22// decode a image and try to obtain width and dppx. If will never throw but instead
23// return default values.
24export async function imageInfo(blob) {
25 let width = 0; // 0 means no width could be determined
26 let dppx = 1; // 1 dot per pixel for non-HiDPI screens
27
28 if (blob.type === 'image/png') { // only png is supported currently
29 try {
30 for (const {name, data} of await pngChunks(blob)) {
31 const view = new DataView(data.buffer);
32 if (name === 'IHDR' && data?.length) {
33 // extract width from mandatory IHDR chunk
34 width = view.getUint32(0);
35 } else if (name === 'pHYs' && data?.length) {
36 // extract dppx from optional pHYs chunk, assuming pixels are square
37 const unit = view.getUint8(8);
38 if (unit === 1) {
39 dppx = Math.round(view.getUint32(0) / 39.3701) / 72; // meter to inch to dppx
40 }
41 }
42 }
43 } catch {}
44 }
45
46 return {width, dppx};
47}