Last active
February 18, 2024 21:43
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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