Spaces:
Running
Running
File size: 3,499 Bytes
fab7cea | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | // Image size from the file header, without decoding: PNG, JPEG (with EXIF orientation) and WebP.
// Anything else (GIF, BMP, HEIC, AVIF, …) returns null and is refused before any decoding.
// Returns { width, height, format, rotated } with width/height as displayed (after EXIF rotation), or null.
const HEAD_BYTES = 512 * 1024;
export async function readImageInfo(file) {
const buf = new Uint8Array(await file.slice(0, HEAD_BYTES).arrayBuffer());
return png(buf) ?? jpeg(buf) ?? webp(buf);
}
const u16be = (b, i) => (b[i] << 8) | b[i + 1];
const u16le = (b, i) => b[i] | (b[i + 1] << 8);
const u32be = (b, i) => ((b[i] << 24) | (b[i + 1] << 16) | (b[i + 2] << 8) | b[i + 3]) >>> 0;
const u32le = (b, i) => (b[i] | (b[i + 1] << 8) | (b[i + 2] << 16) | (b[i + 3] << 24)) >>> 0;
const ascii = (b, i, n) => String.fromCharCode(...b.subarray(i, i + n));
function result(width, height, format, orientation = 1) {
if (!width || !height) return null;
const rotated = orientation >= 5 && orientation <= 8;
return { width: rotated ? height : width, height: rotated ? width : height, format, rotated };
}
function png(b) {
if (b.length < 24 || u32be(b, 0) !== 0x89504e47 || ascii(b, 12, 4) !== "IHDR") return null;
let at = 8;
while (at + 8 <= b.length) {
const len = u32be(b, at);
const type = ascii(b, at + 4, 4);
if (type === "acTL") return { ...result(u32be(b, 16), u32be(b, 20), "png"), animated: true };
if (type === "IDAT") return result(u32be(b, 16), u32be(b, 20), "png");
at += 12 + len;
}
return null;
}
function exifOrientation(b, start, len) {
if (ascii(b, start, 4) !== "Exif") return 1;
const tiff = start + 6;
if (tiff + 8 > b.length) return 1;
const little = ascii(b, tiff, 2) === "II";
const r16 = (i) => (little ? u16le(b, i) : u16be(b, i));
const r32 = (i) => (little ? u32le(b, i) : u32be(b, i));
const ifd = tiff + r32(tiff + 4);
if (ifd + 2 > Math.min(b.length, start + len)) return 1;
const count = r16(ifd);
for (let k = 0; k < count; k++) {
const e = ifd + 2 + k * 12;
if (e + 12 > b.length) break;
if (r16(e) === 0x0112) return r16(e + 8) || 1;
}
return 1;
}
function jpeg(b) {
if (b.length < 4 || b[0] !== 0xff || b[1] !== 0xd8) return null;
let at = 2;
let orientation = 1;
while (at + 4 <= b.length) {
if (b[at] !== 0xff) return null;
const marker = b[at + 1];
if (marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd7) || marker === 0x01) {
at += 2;
continue;
}
const len = u16be(b, at + 2);
if (marker === 0xe1) orientation = exifOrientation(b, at + 4, len - 2);
const isSof = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
if (isSof && at + 9 <= b.length) return result(u16be(b, at + 7), u16be(b, at + 5), "jpeg", orientation);
at += 2 + len;
}
return null;
}
function webp(b) {
if (b.length < 30 || ascii(b, 0, 4) !== "RIFF" || ascii(b, 8, 4) !== "WEBP") return null;
const kind = ascii(b, 12, 4);
if (kind === "VP8 ") return result(u16le(b, 26) & 0x3fff, u16le(b, 28) & 0x3fff, "webp");
if (kind === "VP8L") {
const bits = u32le(b, 21);
return result((bits & 0x3fff) + 1, ((bits >> 14) & 0x3fff) + 1, "webp");
}
if (kind === "VP8X") {
const animated = (b[20] & 0x02) !== 0;
const w = 1 + (b[24] | (b[25] << 8) | (b[26] << 16));
const h = 1 + (b[27] | (b[28] << 8) | (b[29] << 16));
return { ...result(w, h, "webp"), animated };
}
return null;
}
|