decode-tiff
Version:
Lightweight tiff decoder
351 lines (323 loc) • 10.4 kB
JavaScript
"use strict";
/**
*
* Required Fields for Bilevel Images
*
* ImageWidth 256 100 SHORT or LONG
* ImageLength 257 101 SHORT or LONG
* Compression 259 103 SHORT 1, 2 or 32773
* PhotometricInterpretation 262 106 SHORT 0 or 1
* StripOffsets 273 111 SHORT or LONG
* RowsPerStrip 278 116 SHORT or LONG
* StripByteCounts 279 117 LONG or SHORT
* XResolution 282 11A RATIONAL
* YResolution 283 11B RATIONAL
* ResolutionUnit 296 128 SHORT 1, 2 or 3
*
**/
var TAG_NAME_MAP = {
0x0100: "imageWidth",
0x0101: "imageLength",
0x0102: "bitsPerSample",
0x0103: "compression",
0x0106: "photometricInterpretation",
0x0111: "stripOffsets",
0x0116: "rowsPerStrip",
0x0117: "stripByteCounts",
0x0128: "resolutionUnit",
0x0140: "colorMap"
};
function loadPages(buf) {
var idx = 0;
var isMSB = void 0;
var ifdEntries = {};
var stripData = void 0;
function read(offset, length) {
var begin = offset,
end = offset + length;
if (isMSB) {
return buf.subarray(begin, end);
} else {
var s = buf.subarray(begin, end);
var x = new Uint8Array(end - begin);
for (var i = 0; i < s.byteLength; i++) {
x[s.byteLength - i - 1] = s[i];
}
return x;
}
}
function readAsUint16(offset) {
var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
var force = arguments[2];
if (isMSB) {
var dd = new DataView(buf.buffer);
if (length > 1 || force) {
var y = new Uint16Array(length);
for (var i = 0; i < length; i++) {
y[i] = dd.getUint16(offset + (i << 1));
}
return y;
} else {
return dd.getUint16(offset);
}
} else {
var d = new DataView(read(offset, length << 1).buffer);
if (length > 1 || force) {
var x = new Uint16Array(length);
for (var _i = 0; _i < length; _i++) {
x[_i] = d.getUint16(_i << 1);
}
return x;
} else {
return d.getUint16(0);
}
}
}
function readAsUint32(offset) {
var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
var force = arguments[2];
if (isMSB) {
var dd = new DataView(buf.buffer);
if (length > 1 || force) {
var y = new Uint32Array(length);
for (var i = 0; i < length; i++) {
y[i] = dd.getUint32(offset + (i << 2));
}
return y;
} else {
return dd.getUint32(offset);
}
} else {
var d = new DataView(read(offset, length << 2).buffer);
if (length > 1 || force) {
var x = new Uint32Array(length);
for (var _i2 = 0; _i2 < length; _i2++) {
x[_i2] = d.getUint32(_i2 << 2);
}
return x;
} else {
return d.getUint32(0);
}
}
}
/**
*
* The field types and their sizes are:
*
* 1 = BYTE 8-bit unsigned integer.
* 2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte must be NUL (binary zero).
* 3 = SHORT 16-bit (2-byte) unsigned integer.
* 4 = LONG 32-bit (4-byte) unsigned integer.
* 5 = RATIONAL Two LONGs: the first represents the numerator of a fraction; the second, the denominator.
*
* In TIFF 6.0, some new field types have been defined:
*
* 6 = SBYTE An 8-bit signed (twos-complement) integer.
* 7 = UNDEFINED An 8-bit byte that may contain anything, depending on the definition of the field.
* 8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
* 9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer.
* 10 = SRATIONAL Two SLONG’s: the first represents the numerator of a fraction, the second the denominator.
* 11 = FLOAT Single precision (4-byte) IEEE format.
* 12 = DOUBLE Double precision (8-byte) IEEE format
*
**/
function byteLength(fieldType, numOfValues) {
switch (fieldType) {
case 1:
return numOfValues;
case 3:
return numOfValues << 1;
case 4:
return numOfValues << 2;
case 5:
return numOfValues << 3;
default:
return numOfValues << 2;
}
}
function parseIFDFieldValueToArray(fieldType, numOfValues, valueOffset) {
var bl = byteLength(fieldType, numOfValues);
var l = void 0;
if (bl > 4) valueOffset = readAsUint32(valueOffset);
if (bl < 4) {
l = 4 / bl;
} else {
l = numOfValues;
}
var x = void 0;
switch (fieldType) {
case 1:
break;
case 3:
x = readAsUint16(valueOffset, l, true);
break;
case 4:
x = readAsUint32(valueOffset, l, true);
break;
}
if (!x) return;
if (bl < 4) {
return isMSB ? x.slice(0, l - numOfValues) : x.slice(l - numOfValues);
} else {
return x;
}
}
function parseIFDEntry(tagId, fieldType, numOfValues, valueOffset) {
var k = TAG_NAME_MAP[tagId];
if (k) {
ifdEntries[k] = parseIFDFieldValueToArray(fieldType, numOfValues, valueOffset);
} else {
// TODO
// console.log("unknown IFD entry: ", tagId, fieldType, numOfValues, valueOffset);
}
}
function readStrips(ifdEntries) {
var ret = new Uint8Array(ifdEntries.stripByteCounts.reduce(function (s, b) {
return s + b;
}, 0));
var copiedBl = 0;
for (var s = 0; s < ifdEntries.stripOffsets.length; s++) {
var x = buf.subarray(ifdEntries.stripOffsets[s], ifdEntries.stripOffsets[s] + ifdEntries.stripByteCounts[s]);
ret.set(x, copiedBl);
copiedBl += x.byteLength;
}
return ret;
}
// Image File Header
// Byte order
if (buf[0] === 0x4d && buf[1] === 0x4d) {
isMSB = true;
} else if (buf[0] === 0x49 && buf[1] === 0x49) {
isMSB = false;
} else {
throw new Error("Invalid byte order " + buf[0] + buf[1]);
}
if (read(2, 2)[1] !== 0x2a) {
throw new Error("not tiff");
}
// console.log(readAsUint32(4), read(4, 4));
var pages = [];
for (var ifdOffset = readAsUint32(4); ifdOffset !== 0; ifdOffset = readAsUint32(idx)) {
// Number of Directory Entries
idx = ifdOffset;
var numOfIFD = readAsUint16(idx);
ifdEntries = {};
// IFD Entries
idx += 2;
for (var i = 0; i < numOfIFD; i++) {
// TAG
var tagId = readAsUint16(idx);
// Field type
idx += 2;
var fieldType = readAsUint16(idx);
// The number of values
idx += 2;
var numOfValues = readAsUint32(idx);
// The value offset
idx += 4;
var valueOffset = idx;
parseIFDEntry(tagId, fieldType, numOfValues, valueOffset);
idx += 4;
}
stripData = readStrips(ifdEntries);
pages.push({ stripData: stripData, ifdEntries: ifdEntries });
}
return pages;
}
function decompressData(ifdEntries, stripData) {
var compression = ifdEntries.compression;
if (!compression || compression[0] === 1) {
// no-compress
return stripData;
} else if (compression[0] === 2) {
// CCITT Group 3
throw new Error("CCITT group3 decompressionion is not implemented.");
} else if (compression[0] === 5) {
// LZW
throw new Error("LZW decompressionion is not implemented.");
} else if (compression[0] === 6) {
// JPEG
throw new Error("JPEG decompressionion is not implemented.");
} else if (compression[0] === 7) {
// JPEG2
throw new Error("JPEG2 decompressionion is not implemented.");
} else if (compression[0] === 8) {
// Zip(Adobe Deflate)
throw new Error("Zip (Adove Deflate) decompressionion is not implemented.");
} else if (compression[0] === 32773) {
// Packbits
throw new Error("Packbits decompression is not implemented.");
} else {
throw new Error("Unknown compression type: " + compression[0]);
}
}
function normalizeStripData(ifdEntries, stripData) {
var colorMap = ifdEntries.colorMap,
bitsPerSample = ifdEntries.bitsPerSample,
photometricInterpretation = ifdEntries.photometricInterpretation;
var x = void 0;
stripData = decompressData(ifdEntries, stripData);
if (!bitsPerSample) {
throw new Error("Bilevel image decode is not implemented.");
}
if (colorMap) {
throw new Error("Palette-color image decode is not implemented.");
}
if (photometricInterpretation[0] = 2 && bitsPerSample.length === 4) {
// 32bit RBGA image
return stripData;
} else if (photometricInterpretation[0] = 2 && bitsPerSample.length === 3) {
// 24bit RBG image
x = new Uint8Array(stripData.length / 3 * 4);
for (var i = 0; i < stripData.length / 3; i++) {
x[i * 4] = stripData[i * 3];
x[i * 4 + 1] = stripData[i * 3 + 1];
x[i * 4 + 2] = stripData[i * 3 + 2];
x[i * 4 + 3] = 0xFf;
}
return x;
} else if (photometricInterpretation[0] < 2 && bitsPerSample.length === 1 && bitsPerSample[0] === 4) {
// 4bit grayscale image
x = new Uint8Array(stripData.length * 4);
for (var _i3 = 0; _i3 < stripData.length; _i3++) {
x[_i3 * 4] = stripData[_i3] << 4;
x[_i3 * 4 + 1] = stripData[_i3 + 1] << 4;
x[_i3 * 4 + 2] = stripData[_i3 + 2] << 4;
x[_i3 * 4 + 3] = 0xFF;
}
return x;
} else if (photometricInterpretation[0] < 2 && bitsPerSample.length === 1 && bitsPerSample[0] === 8) {
// 8bit grayscale image
x = new Uint8Array(stripData.length * 4);
for (var _i4 = 0; _i4 < stripData.length; _i4++) {
x[_i4 * 4] = stripData[_i4];
x[_i4 * 4 + 1] = stripData[_i4 + 1];
x[_i4 * 4 + 2] = stripData[_i4 + 2];
x[_i4 * 4 + 3] = 0xFF;
}
return x;
} else {
throw new Error("Can't detect image type. PhotometricInterpretation: " + photometricInterpretation[0] + ", BitsPerSample: " + bitsPerSample);
}
}
function decode(buf) {
var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { singlePage: true };
var rawPages = loadPages(new Uint8Array(buf));
var pages = rawPages.map(function (rawPage) {
var width = rawPage.ifdEntries.imageWidth[0];
var height = rawPage.ifdEntries.imageLength[0];
var data = normalizeStripData(rawPage.ifdEntries, rawPage.stripData);
return { width: width, height: height, data: data, ifdEntries: rawPage.ifdEntries };
});
if (opt.singlePage) {
if (!pages || !pages.length) {
throw new Error("No pages");
}
return pages[0];
} else {
return pages;
}
}
module.exports = {
decode: decode
};