Skip to content

Instantly share code, notes, and snippets.

@druminski
Last active February 18, 2024 21:43
Show Gist options
  • Save druminski/1d0a0d30d72aba23690de040ab90bcaa to your computer and use it in GitHub Desktop.
Save druminski/1d0a0d30d72aba23690de040ab90bcaa to your computer and use it in GitHub Desktop.
JavaScript function reading dimensions of TIFF image. Helpful when you need such functionality in Google Apps Script.
/**
* Materials:
* How to read only portion of bytes in a file on Google Drive:
* https://stackoverflow.com/questions/60181888/google-apps-script-get-range-of-bytes-from-binary-file
* TIFF File structure: https://www.awaresystems.be/imaging/tiff/faq.html#q3
*/
const TIFF_IFD_FIELD_LENGTH_IN_BYTES = 12;
const NUMBER_OF_BYTES_TO_READ_FROM_FILE = 500000;
// https://www.awaresystems.be/imaging/tiff/tifftags/baseline.html
const TIFF_TAG_IMAGE_WIDTH = 256;
const TIFF_TAG_IMAGE_LENGTH = 257;
const TIFF_TAG_X_RESOLUTION = 282;
const TIFF_TAG_Y_RESOLUTION = 283;
const TIFF_TAG_RESOLUTION_UNIT = 296;
// fileId can be read from sharable URL to Google Drive file
function getTIFFDimensionsFromFileID(fileId) {
const fileFirstBytes = readFileFirstBytes(fileId, NUMBER_OF_BYTES_TO_READ_FROM_FILE);
return getTIFFDimenstionsFromBytes(fileFirstBytes);
}
function readFileFirstBytes(fileId, bytesCount) {
const start = 0;
const end = bytesCount;
const url = "https://www.googleapis.com/drive/v3/files/" + fileId + "?alt=media";
const params = {
method: "get",
headers: {
Authorization: "Bearer " + ScriptApp.getOAuthToken(),
Range: "bytes=" + start + "-" + end,
},
};
return UrlFetchApp.fetch(url, params).getContent();
}
function getTIFFDimenstionsFromBytes(bytes) {
const isLittleEndian = bytes[0] == 73 && bytes[1] == 73 ? true : false;
const buffer = new ArrayBuffer(TIFF_IFD_FIELD_LENGTH_IN_BYTES);
const view = new DataView(buffer);
view.setInt8(0, bytes[2]);
view.setInt8(1, bytes[3]);
if (view.getInt16(0, isLittleEndian) != 42) { // check magic byte
console.log('Not a TIFF file.');
return;
}
// Offset to first Image File Directory (IFD) is encoded in 4-7 bytes
view.setInt8(0, bytes[4]);
view.setInt8(1, bytes[5]);
view.setInt8(2, bytes[6]);
view.setInt8(3, bytes[7]);
const firstIFDOffset = view.getInt32(0, isLittleEndian);
view.setInt8(0, bytes[firstIFDOffset]);
view.setInt8(1, bytes[firstIFDOffset + 1]);
const tagsCount = view.getInt16(0, isLittleEndian);
const imageDimenstions = {
widthInPixels: -1,
heightInPixels: -1,
xResolution: {
numerator: -1,
denominator: -1,
},
yResolution: {
numerator: -1,
denominator: -1,
},
resolutionUnit: "inch"
}
const firstTagOffset = firstIFDOffset + 2;
for (let i=0; i < tagsCount; i++) {
readFileDimensionsFromTiffTag(firstTagOffset + 12 * i, bytes, view, isLittleEndian, imageDimenstions);
}
return imageDimenstions;
}
function readFileDimensionsFromTiffTag(offset, bytes, view, isLittleEndian, imageDimenstions) {
for (let i = 0; i < TIFF_IFD_FIELD_LENGTH_IN_BYTES; i++) {
view.setInt8(i, bytes[offset + i]);
}
const tagCode = view.getInt16(0, isLittleEndian);
const tagDatatype = view.getInt16(2, isLittleEndian);
let tagData = -1;
let tagDataNumerator = -1;
let tagDataDenominator = -1;
let valueOffset = -1;
switch (tagDatatype) {
case 3:
// read 16 bits, 2 bytes
tagData = view.getInt16(8, isLittleEndian);
break;
case 4:
// read 32 bits, 4 bytes
tagData = view.getInt32(8, isLittleEndian);
break;
case 5:
valueOffset = view.getInt32(8, isLittleEndian);
for (let i = 0; i < 8; i++) {
view.setInt8(i, bytes[valueOffset + i]);
}
tagDataNumerator = view.getInt32(0, isLittleEndian);
tagDataDenominator = view.getInt32(4, isLittleEndian);
break;
}
switch (tagCode) {
case TIFF_TAG_IMAGE_WIDTH:
imageDimenstions.widthInPixels = tagData;
break;
case TIFF_TAG_IMAGE_LENGTH:
imageDimenstions.heightInPixels = tagData;
break;
case TIFF_TAG_X_RESOLUTION:
imageDimenstions.xResolution.numerator = tagDataNumerator;
imageDimenstions.xResolution.denominator = tagDataDenominator;
break;
case TIFF_TAG_Y_RESOLUTION:
imageDimenstions.yResolution.numerator = tagDataNumerator;
imageDimenstions.yResolution.denominator = tagDataDenominator;
break;
case TIFF_TAG_RESOLUTION_UNIT:
if (tagData == 1) {
imageDimenstions.resolutionUnit = "none";
} else if (tagData == 2) {
imageDimenstions.resolutionUnit = "inch";
} else if (tagData == 3) {
imageDimenstions.resolutionUnit = "cm";
}
break;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment