|
1 | 1 | // based on http://www.compix.com/fileformattif.htm |
| 2 | +// TO-DO: support big-endian as well |
| 3 | +import { readUInt, toHexString, toUTF8String } from "./utils"; |
2 | 4 | import type { IImage } from "./interface"; |
3 | | -import { toHexString } from "./utils"; |
| 5 | + |
| 6 | +// Read IFD (image-file-directory) into a buffer |
| 7 | +function readIFD(buffer: Uint8Array, isBigEndian: boolean) { |
| 8 | + const ifdOffset = readUInt(buffer, 32, 4, isBigEndian); |
| 9 | + |
| 10 | + // read only till the end of the file |
| 11 | + let bufferSize = 1024; |
| 12 | + const fileSize = buffer.length; |
| 13 | + if (ifdOffset + bufferSize > fileSize) { |
| 14 | + bufferSize = fileSize - ifdOffset - 10; |
| 15 | + } |
| 16 | + |
| 17 | + return buffer.slice(ifdOffset + 2, ifdOffset + 2 + bufferSize); |
| 18 | +} |
| 19 | + |
| 20 | +// TIFF values seem to be messed up on Big-Endian, this helps |
| 21 | +function readValue(buffer: Uint8Array, isBigEndian: boolean): number { |
| 22 | + const low = readUInt(buffer, 16, 8, isBigEndian); |
| 23 | + const high = readUInt(buffer, 16, 10, isBigEndian); |
| 24 | + return (high << 16) + low; |
| 25 | +} |
| 26 | + |
| 27 | +// move to the next tag |
| 28 | +function nextTag(buffer: Uint8Array) { |
| 29 | + if (buffer.length > 24) { |
| 30 | + return buffer.slice(12); |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +// Extract IFD tags from TIFF metadata |
| 35 | +function extractTags(buffer: Uint8Array, isBigEndian: boolean) { |
| 36 | + const tags: { [key: number]: number } = {}; |
| 37 | + |
| 38 | + let temp: Uint8Array | undefined = buffer; |
| 39 | + while (temp && temp.length > 0) { |
| 40 | + const code = readUInt(temp, 16, 0, isBigEndian); |
| 41 | + const type = readUInt(temp, 16, 2, isBigEndian); |
| 42 | + const length = readUInt(temp, 32, 4, isBigEndian); |
| 43 | + |
| 44 | + // 0 means end of IFD |
| 45 | + if (code === 0) { |
| 46 | + break; |
| 47 | + } else { |
| 48 | + // 256 is width, 257 is height |
| 49 | + // if (code === 256 || code === 257) { |
| 50 | + if (length === 1 && (type === 3 || type === 4)) { |
| 51 | + tags[code] = readValue(temp, isBigEndian); |
| 52 | + } |
| 53 | + |
| 54 | + // move to the next tag |
| 55 | + temp = nextTag(temp); |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + return tags; |
| 60 | +} |
| 61 | + |
| 62 | +// Test if the TIFF is Big Endian or Little Endian |
| 63 | +function determineEndianness(input: Uint8Array) { |
| 64 | + const signature = toUTF8String(input, 0, 2); |
| 65 | + if (signature === "II") { |
| 66 | + return "LE"; |
| 67 | + } else if (signature === "MM") { |
| 68 | + return "BE"; |
| 69 | + } |
| 70 | +} |
4 | 71 |
|
5 | 72 | const signatures = new Set([ |
6 | | - "492049", // ? |
| 73 | + // '492049', // currently not supported |
7 | 74 | "49492a00", // Little endian |
8 | 75 | "4d4d002a", // Big Endian |
9 | | - "4d4d002a", // BigTIFF > 4GB. currently not supported |
| 76 | + // '4d4d002a', // BigTIFF > 4GB. currently not supported |
10 | 77 | ]); |
11 | 78 |
|
12 | 79 | export const TIFF: IImage = { |
13 | 80 | validate: (input) => signatures.has(toHexString(input, 0, 4)), |
14 | | - calculate: () => ({ width: undefined, height: undefined }), |
| 81 | + |
| 82 | + calculate(input) { |
| 83 | + // Determine BE/LE |
| 84 | + const isBigEndian = determineEndianness(input) === "BE"; |
| 85 | + |
| 86 | + // read the IFD |
| 87 | + const ifdBuffer = readIFD(input, isBigEndian); |
| 88 | + |
| 89 | + // extract the tags from the IFD |
| 90 | + const tags = extractTags(ifdBuffer, isBigEndian); |
| 91 | + |
| 92 | + const width = tags[256]; |
| 93 | + const height = tags[257]; |
| 94 | + |
| 95 | + if (!width || !height) { |
| 96 | + throw new TypeError("Invalid Tiff. Missing tags"); |
| 97 | + } |
| 98 | + |
| 99 | + return { height, width }; |
| 100 | + }, |
15 | 101 | }; |
0 commit comments