/** * Hand-built JPEGs for the tests of `exif()` and of the image metadata * `quak backup` records: one whose EXIF holds the same values as `exif.heic` * (see `exif-heic.ts`), and one whose EXIF cannot be parsed. */ // Big-endian bytes for the hand-built JPEG below. const u16 = (n: number): number[] => [n >> 8, n & 0xff]; const u32 = (n: number): number[] => [...u16(n >>> 16), ...u16(n & 0xffff)]; const ascii = (s: string): number[] => [...new TextEncoder().encode(s), 0]; const rational = (num: number, den: number): number[] => [ ...u32(num), ...u32(den), ]; // One IFD entry: tag, type (1 BYTE, 2 ASCII, 3 SHORT, 4 LONG, 5 RATIONAL), // count, then the value when it fits in 4 bytes, else its offset. const entry = ( tag: number, type: number, count: number, value: number[], ): number[] => [...u16(tag), ...u16(type), ...u32(count), ...value]; // The TIFF block of a JPEG's EXIF segment, holding every field `Photo`'s typed // methods return: the camera in the first IFD, the exposure in the Exif IFD, // and a GPS position of 40°26'46" N, 79°58'56" W, 12.5 m below sea level. // Offsets count from the start of this block. const TIFF = [ ...[0x4d, 0x4d, 0x00, 0x2a], // big-endian TIFF ...u32(8), // the first IFD's offset // The first IFD, at 8: five entries, then no next IFD. ...u16(5), ...entry(0x010f, 2, 6, u32(74)), // Make ...entry(0x0110, 2, 7, u32(80)), // Model ...entry(0x0112, 3, 1, [...u16(6), 0, 0]), // Orientation ...entry(0x8769, 4, 1, u32(88)), // the Exif IFD's offset ...entry(0x8825, 4, 1, u32(246)), // the GPS IFD's offset ...u32(0), ...ascii("Canon"), // at 74 ...ascii("EOS R5"), // at 80 0, // a pad byte // The Exif IFD, at 88: seven entries, then no next IFD. ...u16(7), ...entry(0x829a, 5, 1, u32(178)), // ExposureTime ...entry(0x829d, 5, 1, u32(186)), // FNumber ...entry(0x8827, 3, 1, [...u16(400), 0, 0]), // ISOSpeedRatings ...entry(0x9003, 2, 20, u32(194)), // DateTimeOriginal ...entry(0x9011, 2, 7, u32(214)), // OffsetTimeOriginal ...entry(0x920a, 5, 1, u32(222)), // FocalLength ...entry(0xa434, 2, 16, u32(230)), // LensModel ...u32(0), ...rational(1, 250), // at 178 ...rational(28, 10), // at 186 ...ascii("2021:07:15 14:30:00"), // at 194 ...ascii("+02:00"), // at 214 0, // a pad byte ...rational(50, 1), // at 222 ...ascii("RF50mm F1.8 STM"), // at 230 // The GPS IFD, at 246: six entries, then no next IFD. ...u16(6), ...entry(0x0001, 2, 2, [...ascii("N"), 0, 0]), // GPSLatitudeRef ...entry(0x0002, 5, 3, u32(324)), // GPSLatitude ...entry(0x0003, 2, 2, [...ascii("W"), 0, 0]), // GPSLongitudeRef ...entry(0x0004, 5, 3, u32(348)), // GPSLongitude ...entry(0x0005, 1, 1, [1, 0, 0, 0]), // GPSAltitudeRef: below sea level ...entry(0x0006, 5, 1, u32(372)), // GPSAltitude ...u32(0), ...[...rational(40, 1), ...rational(26, 1), ...rational(46, 1)], // at 324 ...[...rational(79, 1), ...rational(58, 1), ...rational(56, 1)], // at 348 ...rational(25, 2), // at 372 ]; export const JPEG_WITH_EXIF = new Uint8Array([ ...[0xff, 0xd8], // start of image ...[0xff, 0xe1, ...u16(2 + 6 + TIFF.length)], // APP1 and its length ...[...ascii("Exif"), 0], // "Exif\0\0" ...TIFF, ...[0xff, 0xda, 0x00, 0x02], // start of scan ]); // A JPEG whose EXIF segment is laid out correctly but holds "XX" where the TIFF // byte order belongs, so exifreader cannot parse it. export const JPEG_WITH_BAD_EXIF = new Uint8Array([ ...[0xff, 0xd8], // start of image ...[0xff, 0xe1, ...u16(2 + 6 + 2)], // APP1 and its length ...[...ascii("Exif"), 0], // "Exif\0\0" ...[0x58, 0x58], // "XX" ...[0xff, 0xda, 0x00, 0x02], // start of scan ]);