var JPEGParser = JPEGParser || {}; JPEGParser.BlobView = function () { function fail(msg) { throw Error(msg); } function BlobView(blob, sliceOffset, sliceLength, slice, viewOffset, viewLength, littleEndian) { this.blob = blob; this.sliceOffset = sliceOffset; this.sliceLength = sliceLength; this.slice = slice; this.viewOffset = viewOffset; this.viewLength = viewLength; this.littleEndian = littleEndian; this.view = new DataView(slice, viewOffset, viewLength); this.buffer = slice; this.byteLength = viewLength; this.byteOffset = viewOffset; this.index = 0; } BlobView.get = function (blob, offset, length, callback, littleEndian) { if (offset < 0) fail('negative offset'); if (length < 0) fail('negative length'); if (offset > blob.size) fail('offset larger than blob size'); if (offset + length > blob.size) length = blob.size - offset; var slice = blob.slice(offset, offset + length); var reader = new FileReader(); reader.readAsArrayBuffer(slice); reader.onloadend = function () { var result = null; if (reader.result) { result = new BlobView(blob, offset, length, reader.result, 0, length, littleEndian || false); } callback(result, reader.error); }; }; BlobView.prototype = { constructor: BlobView, getMore: function (offset, length, callback) { if (offset >= this.sliceOffset && offset + length <= this.sliceOffset + this.sliceLength) { callback(new BlobView(this.blob, this.sliceOffset, this.sliceLength, this.slice, offset - this.sliceOffset, length, this.littleEndian)); } else { BlobView.get(this.blob, offset, length, callback, this.littleEndian); } }, littleEndian: function () { this.littleEndian = true; }, bigEndian: function () { this.littleEndian = false; }, getUint8: function (offset) { return this.view.getUint8(offset); }, getInt8: function (offset) { return this.view.getInt8(offset); }, getUint16: function (offset, le) { return this.view.getUint16(offset, le !== undefined ? le : this.littleEndian); }, getInt16: function (offset, le) { return this.view.getInt16(offset, le !== undefined ? le : this.littleEndian); }, getUint32: function (offset, le) { return this.view.getUint32(offset, le !== undefined ? le : this.littleEndian); }, getInt32: function (offset, le) { return this.view.getInt32(offset, le !== undefined ? le : this.littleEndian); }, getFloat32: function (offset, le) { return this.view.getFloat32(offset, le !== undefined ? le : this.littleEndian); }, getFloat64: function (offset, le) { return this.view.getFloat64(offset, le !== undefined ? le : this.littleEndian); }, setUint8: function (offset, value) { return this.view.setUint8(offset, value); }, setInt8: function (offset, value) { return this.view.setInt8(offset, value); }, setUint16: function (offset, value, le) { return this.view.setUint16(offset, value, le !== undefined ? le : this.littleEndian); }, setInt16: function (offset, value, le) { return this.view.setInt16(offset, value, le !== undefined ? le : this.littleEndian); }, setUint32: function (offset, value, le) { return this.view.setUint32(offset, value, le !== undefined ? le : this.littleEndian); }, setInt32: function (offset, value, le) { return this.view.setInt32(offset, value, le !== undefined ? le : this.littleEndian); }, setFloat32: function (offset, value, le) { return this.view.setFloat32(offset, value, le !== undefined ? le : this.littleEndian); }, setFloat64: function (offset, value, le) { return this.view.setFloat64(offset, value, le !== undefined ? le : this.littleEndian); }, readByte: function () { return this.view.getInt8(this.index++); }, readUnsignedByte: function () { return this.view.getUint8(this.index++); }, readShort: function (le) { var val = this.view.getInt16(this.index, le !== undefined ? le : this.littleEndian); this.index += 2; return val; }, readUnsignedShort: function (le) { var val = this.view.getUint16(this.index, le !== undefined ? le : this.littleEndian); this.index += 2; return val; }, readInt: function (le) { var val = this.view.getInt32(this.index, le !== undefined ? le : this.littleEndian); this.index += 4; return val; }, readUnsignedInt: function (le) { var val = this.view.getUint32(this.index, le !== undefined ? le : this.littleEndian); this.index += 4; return val; }, readFloat: function (le) { var val = this.view.getFloat32(this.index, le !== undefined ? le : this.littleEndian); this.index += 4; return val; }, readDouble: function (le) { var val = this.view.getFloat64(this.index, le !== undefined ? le : this.littleEndian); this.index += 8; return val; }, tell: function () { return this.index; }, seek: function (index) { if (index < 0) fail('negative index'); if (index >= this.byteLength) fail('index greater than buffer size'); this.index = index; }, advance: function (n) { var index = this.index + n; if (index < 0) fail('advance past beginning of buffer'); if (index > this.byteLength) fail('advance past end of buffer'); this.index = index; }, getUnsignedByteArray: function (offset, n) { return new Uint8Array(this.buffer, offset + this.viewOffset, n); }, readUnsignedByteArray: function (n) { var val = new Uint8Array(this.buffer, this.index + this.viewOffset, n); this.index += n; return val; }, getBit: function (offset, bit) { var byte = this.view.getUint8(offset); return (byte & 1 << bit) !== 0; }, getUint24: function (offset, le) { var b1, b2, b3; if (le !== undefined ? le : this.littleEndian) { b1 = this.view.getUint8(offset); b2 = this.view.getUint8(offset + 1); b3 = this.view.getUint8(offset + 2); } else { b3 = this.view.getUint8(offset); b2 = this.view.getUint8(offset + 1); b1 = this.view.getUint8(offset + 2); } return (b3 << 16) + (b2 << 8) + b1; }, readUint24: function (le) { var value = this.getUint24(this.index, le); this.index += 3; return value; }, getASCIIText: function (offset, len) { var bytes = new Uint8Array(this.buffer, offset + this.viewOffset, len); return String.fromCharCode.apply(String, bytes); }, getNullTerminatedASCIIString: function (offset) { var string = ''; var characterCode; while (offset < this.sliceLength) { characterCode = this.view.getUint8(offset); if (characterCode === 0) { break; } string += String.fromCharCode(characterCode); offset++; } return string; }, readASCIIText: function (len) { var bytes = new Uint8Array(this.buffer, this.index + this.viewOffset, len); this.index += len; return String.fromCharCode.apply(String, bytes); }, getUTF8Text: function (offset, len) { function fail() { throw new Error('Illegal UTF-8'); } var pos = offset; var end = offset + len; var charcode; var s = ''; var b1, b2, b3, b4; while (pos < end) { var b1 = this.view.getUint8(pos); if (b1 < 128) { s += String.fromCharCode(b1); pos += 1; } else if (b1 < 194) { fail(); } else if (b1 < 224) { if (pos + 1 >= end) fail(); b2 = this.view.getUint8(pos + 1); if (b2 < 128 || b2 > 191) fail(); charcode = ((b1 & 31) << 6) + (b2 & 63); s += String.fromCharCode(charcode); pos += 2; } else if (b1 < 240) { if (pos + 3 >= end) fail(); b2 = this.view.getUint8(pos + 1); if (b2 < 128 || b2 > 191) fail(); b3 = this.view.getUint8(pos + 2); if (b3 < 128 || b3 > 191) fail(); charcode = ((b1 & 15) << 12) + ((b2 & 63) << 6) + (b3 & 63); s += String.fromCharCode(charcode); pos += 3; } else if (b1 < 245) { if (pos + 3 >= end) fail(); b2 = this.view.getUint8(pos + 1); if (b2 < 128 || b2 > 191) fail(); b3 = this.view.getUint8(pos + 2); if (b3 < 128 || b3 > 191) fail(); b4 = this.view.getUint8(pos + 3); if (b4 < 128 || b4 > 191) fail(); charcode = ((b1 & 7) << 18) + ((b2 & 63) << 12) + ((b3 & 63) << 6) + (b4 & 63); charcode -= 65536; s += String.fromCharCode(55296 + ((charcode & 1047552) >>> 10)); s += String.fromCharCode(56320 + (charcode & 1023)); pos += 4; } else { fail(); } } return s; }, readUTF8Text: function (len) { try { return this.getUTF8Text(this.index, len); } finally { this.index += len; } }, getID3Uint28BE: function (offset) { var b1 = this.view.getUint8(offset) & 127; var b2 = this.view.getUint8(offset + 1) & 127; var b3 = this.view.getUint8(offset + 2) & 127; var b4 = this.view.getUint8(offset + 3) & 127; return b1 << 21 | b2 << 14 | b3 << 7 | b4; }, readID3Uint28BE: function () { var value = this.getID3Uint28BE(this.index); this.index += 4; return value; }, readNullTerminatedLatin1Text: function (size) { var s = ''; for (var i = 0; i < size; i++) { var charcode = this.view.getUint8(this.index + i); if (charcode === 0) { i++; break; } s += String.fromCharCode(charcode); } this.index += i; return s; }, readNullTerminatedUTF8Text: function (size) { for (var len = 0; len < size; len++) { if (this.view.getUint8(this.index + len) === 0) break; } var s = this.readUTF8Text(len); if (len < size) this.advance(1); return s; }, readNullTerminatedUTF16Text: function (size, le) { if (le == null) { var BOM = this.readUnsignedShort(); size -= 2; if (BOM === 65279) le = false; else le = true; } var s = ''; for (var i = 0; i < size; i += 2) { var charcode = this.getUint16(this.index + i, le); if (charcode === 0) { i += 2; break; } s += String.fromCharCode(charcode); } this.index += i; return s; } }; return { get: BlobView.get }; }(); (function () { var tagTypes = { BYTE: 1, ASCII: 2, SHORT: 3, LONG: 4, RATIONAL: 5, SBYTE: 6, UNDEFINED: 7, SSHORT: 8, SLONG: 9, SRATIONAL: 10, FLOAT: 11, DOUBLE: 12 }; var tagTypesString = { 1: 'BYTE', 2: 'ASCII', 3: 'SHORT', 4: 'LONG', 5: 'RATIONAL', 6: 'SBYTE', 7: 'UNDEFINED', 8: 'SSHORT', 9: 'SLONG', 10: 'SRATIONAL', 11: 'FLOAT', 12: 'DOUBLE' }; var tagTypeSize = { 1: 1, 2: 1, 3: 2, 4: 4, 5: 8, 6: 1, 7: 1, 8: 2, 9: 4, 10: 8, 11: 4, 12: 8 }; var IFDId = { Image: 1, Photo: 2, GPSInfo: 3, Iop: 4 }; var interOperabilityTags = { '1': { 'IFD': 4, 'key': 'InteroperabilityIndex', 'type': 2 }, '2': { 'IFD': 4, 'key': 'InteroperabilityVersion', 'type': 7 }, '4096': { 'IFD': 4, 'key': 'RelatedImageFileFormat', 'type': 2 }, '4097': { 'IFD': 4, 'key': 'RelatedImageWidth', 'type': 4 }, '4098': { 'IFD': 4, 'key': 'RelatedImageLength', 'type': 4 } }; var tags = { '0': { 'IFD': 3, 'key': 'GPSVersionID', 'type': 1 }, '1': { 'IFD': 3, 'key': 'GPSLatitudeRef', 'type': 2 }, '2': { 'IFD': 3, 'key': 'GPSLatitude', 'type': 5 }, '3': { 'IFD': 3, 'key': 'GPSLongitudeRef', 'type': 2 }, '4': { 'IFD': 3, 'key': 'GPSLongitude', 'type': 5 }, '5': { 'IFD': 3, 'key': 'GPSAltitudeRef', 'type': 1 }, '6': { 'IFD': 3, 'key': 'GPSAltitude', 'type': 5 }, '7': { 'IFD': 3, 'key': 'GPSTimeStamp', 'type': 5 }, '8': { 'IFD': 3, 'key': 'GPSSatellites', 'type': 2 }, '9': { 'IFD': 3, 'key': 'GPSStatus', 'type': 2 }, '10': { 'IFD': 3, 'key': 'GPSMeasureMode', 'type': 2 }, '11': { 'IFD': 3, 'key': 'GPSDOP', 'type': 5 }, '12': { 'IFD': 3, 'key': 'GPSSpeedRef', 'type': 2 }, '13': { 'IFD': 3, 'key': 'GPSSpeed', 'type': 5 }, '14': { 'IFD': 3, 'key': 'GPSTrackRef', 'type': 2 }, '15': { 'IFD': 3, 'key': 'GPSTrack', 'type': 5 }, '16': { 'IFD': 3, 'key': 'GPSImgDirectionRef', 'type': 2 }, '17': { 'IFD': 3, 'key': 'GPSImgDirection', 'type': 5 }, '18': { 'IFD': 3, 'key': 'GPSMapDatum', 'type': 2 }, '19': { 'IFD': 3, 'key': 'GPSDestLatitudeRef', 'type': 2 }, '20': { 'IFD': 3, 'key': 'GPSDestLatitude', 'type': 5 }, '21': { 'IFD': 3, 'key': 'GPSDestLONGitudeRef', 'type': 2 }, '22': { 'IFD': 3, 'key': 'GPSDestLONGitude', 'type': 5 }, '23': { 'IFD': 3, 'key': 'GPSDestBearingRef', 'type': 2 }, '24': { 'IFD': 3, 'key': 'GPSDestBearing', 'type': 5 }, '25': { 'IFD': 3, 'key': 'GPSDestDistanceRef', 'type': 2 }, '26': { 'IFD': 3, 'key': 'GPSDestDistance', 'type': 5 }, '27': { 'IFD': 3, 'key': 'GPSProcessingMethod', 'type': 7 }, '28': { 'IFD': 3, 'key': 'GPSAreaInformation', 'type': 7 }, '29': { 'IFD': 3, 'key': 'GPSDateStamp', 'type': 2 }, '30': { 'IFD': 3, 'key': 'GPSDifferential', 'type': 3 }, '254': { 'IFD': 1, 'key': 'NewSubfileType', 'type': 4 }, '255': { 'IFD': 1, 'key': 'SubfileType', 'type': 3 }, '256': { 'IFD': 1, 'key': 'ImageWidth', 'type': 4 }, '257': { '': 1, 'key': 'ImageLength', 'type': 4 }, '258': { 'IFD': 1, 'key': 'BitsPerSample', 'type': 3 }, '259': { 'IFD': 1, 'key': 'Compression', 'type': 3 }, '262': { 'IFD': 1, 'key': 'PhotometricInterpretation', 'type': 3 }, '263': { 'IFD': 1, 'key': 'Threshholding', 'type': 3 }, '264': { 'IFD': 1, 'key': 'CellWidth', 'type': 3 }, '265': { 'IFD': 1, 'key': 'CellLength', 'type': 3 }, '266': { 'IFD': 1, 'key': 'FillOrder', 'type': 3 }, '269': { 'IFD': 1, 'key': 'DocumentName', 'type': 2 }, '270': { 'IFD': 1, 'key': 'ImageDescription', 'type': 2 }, '271': { 'IFD': 1, 'key': 'Make', 'type': 2 }, '272': { 'IFD': 1, 'key': 'Model', 'type': 2 }, '273': { 'IFD': 1, 'key': 'StripOffsets', 'type': 4 }, '274': { 'IFD': 1, 'key': 'Orientation', 'type': 3 }, '277': { 'IFD': 1, 'key': 'SamplesPerPixel', 'type': 3 }, '278': { 'IFD': 1, 'key': 'RowsPerStrip', 'type': 4 }, '279': { 'IFD': 1, 'key': 'StripByteCounts', 'type': 4 }, '282': { 'IFD': 1, 'key': 'XResolution', 'type': 5 }, '283': { 'IFD': 1, 'key': 'YResolution', 'type': 5 }, '284': { 'IFD': 1, 'key': 'PlanarConfiguration', 'type': 3 }, '290': { 'IFD': 1, 'key': 'GrayResponseUnit', 'type': 3 }, '291': { 'IFD': 1, 'key': 'GrayResponseCurve', 'type': 3 }, '292': { 'IFD': 1, 'key': 'T4Options', 'type': 4 }, '293': { 'IFD': 1, 'key': 'T6Options', 'type': 4 }, '296': { 'IFD': 1, 'key': 'ResolutionUnit', 'type': 3 }, '301': { 'IFD': 1, 'key': 'TransferFunction', 'type': 3 }, '305': { 'IFD': 1, 'key': 'Software', 'type': 2 }, '306': { 'IFD': 1, 'key': 'DateTime', 'type': 2 }, '315': { 'IFD': 1, 'key': 'Artist', 'type': 2 }, '316': { 'IFD': 1, 'key': 'HostComputer', 'type': 2 }, '317': { 'IFD': 1, 'key': 'Predictor', 'type': 3 }, '318': { 'IFD': 1, 'key': 'WhitePoint', 'type': 5 }, '319': { 'IFD': 1, 'key': 'PrimaryChromaticities', 'type': 5 }, '320': { 'IFD': 1, 'key': 'ColorMap', 'type': 3 }, '321': { 'IFD': 1, 'key': 'HalftoneHints', 'type': 3 }, '322': { 'IFD': 1, 'key': 'TileWidth', 'type': 3 }, '323': { 'IFD': 1, 'key': 'TileLength', 'type': 3 }, '324': { 'IFD': 1, 'key': 'TileOffsets', 'type': 3 }, '325': { 'IFD': 1, 'key': 'TileByteCounts', 'type': 3 }, '330': { 'IFD': 1, 'key': 'SubIFDs', 'type': 4 }, '332': { 'IFD': 1, 'key': 'InkSet', 'type': 3 }, '333': { 'IFD': 1, 'key': 'InkNames', 'type': 2 }, '334': { 'IFD': 1, 'key': 'NumberOfInks', 'type': 3 }, '336': { 'IFD': 1, 'key': 'DotRange', 'type': 1 }, '337': { 'IFD': 1, 'key': 'TargetPrinter', 'type': 2 }, '338': { 'IFD': 1, 'key': 'ExtraSamples', 'type': 3 }, '339': { 'IFD': 1, 'key': 'SampleFormat', 'type': 3 }, '340': { 'IFD': 1, 'key': 'SMinSampleValue', 'type': 3 }, '341': { 'IFD': 1, 'key': 'SMaxSampleValue', 'type': 3 }, '342': { 'IFD': 1, 'key': 'TransferRange', 'type': 3 }, '343': { 'IFD': 1, 'key': 'ClipPath', 'type': 1 }, '344': { 'IFD': 1, 'key': 'XClipPathUnits', 'type': 8 }, '345': { 'IFD': 1, 'key': 'YClipPathUnits', 'type': 8 }, '346': { 'IFD': 1, 'key': 'Indexed', 'type': 3 }, '347': { 'IFD': 1, 'key': 'JPEGTables', 'type': 7 }, '351': { 'IFD': 1, 'key': 'OPIProxy', 'type': 3 }, '512': { 'IFD': 1, 'key': 'JPEGProc', 'type': 4 }, '513': { 'IFD': 1, 'key': 'JPEGInterchangeFormat', 'type': 4 }, '514': { 'IFD': 1, 'key': 'JPEGInterchangeFormatLength', 'type': 4 }, '515': { 'IFD': 1, 'key': 'JPEGRestartInterval', 'type': 3 }, '517': { 'IFD': 1, 'key': 'JPEGLosslessPredictors', 'type': 3 }, '518': { 'IFD': 1, 'key': 'JPEGPointTransforms', 'type': 3 }, '519': { 'IFD': 1, 'key': 'JPEGQTables', 'type': 4 }, '520': { 'IFD': 1, 'key': 'JPEGDCTables', 'type': 4 }, '521': { 'IFD': 1, 'key': 'JPEGACTables', 'type': 4 }, '529': { 'IFD': 1, 'key': 'YCbCrCoefficients', 'type': 5 }, '530': { 'IFD': 1, 'key': 'YCbCrSubSampling', 'type': 3 }, '531': { 'IFD': 1, 'key': 'YCbCrPositioning', 'type': 3 }, '532': { 'IFD': 1, 'key': 'ReferenceBlackWhite', 'type': 5 }, '700': { 'IFD': 1, 'key': 'XMLPacket', 'type': 1 }, '18246': { 'IFD': 1, 'key': 'Rating', 'type': 3 }, '18249': { 'IFD': 1, 'key': 'RatingPercent', 'type': 3 }, '32781': { 'IFD': 1, 'key': 'ImageID', 'type': 2 }, '33421': { 'IFD': 1, 'key': 'CFARepeatPatternDim', 'type': 3 }, '33422': { 'IFD': 1, 'key': 'CFAPattern', 'type': 1 }, '33423': { 'IFD': 1, 'key': 'BatteryLevel', 'type': 5 }, '33432': { 'IFD': 1, 'key': 'Copyright', 'type': 2 }, '33434': { 'IFD': 2, 'key': 'ExposureTime', 'type': 5 }, '33437': { 'IFD': 2, 'key': 'FNumber', 'type': 5 }, '33723': { 'IFD': 1, 'key': 'IPTCNAA', 'type': 4 }, '34377': { 'IFD': 1, 'key': 'ImageResources', 'type': 1 }, '34665': { 'IFD': 1, 'key': 'ExifTag', 'type': 4 }, '34675': { 'IFD': 1, 'key': 'InterColorProfile', 'type': 7 }, '34850': { 'IFD': 2, 'key': 'ExposureProgram', 'type': 3 }, '34852': { 'IFD': 2, 'key': 'SpectralSensitivity', 'type': 2 }, '34853': { 'IFD': 1, 'key': 'GPSTag', 'type': 4 }, '34855': { 'IFD': 2, 'key': 'ISOSpeedRatings', 'type': 3 }, '34856': { 'IFD': 2, 'key': 'OECF', 'type': 7 }, '34857': { 'IFD': 1, 'key': 'Interlace', 'type': 3 }, '34858': { 'IFD': 1, 'key': 'TimeZoneOffset', 'type': 8 }, '34859': { 'IFD': 1, 'key': 'SelfTimerMode', 'type': 3 }, '34864': { 'IFD': 2, 'key': 'SensitivityType', 'type': 3 }, '34865': { 'IFD': 2, 'key': 'StandardOutputSensitivity', 'type': 4 }, '34866': { 'IFD': 2, 'key': 'RecommendedExposureIndex', 'type': 4 }, '34867': { 'IFD': 2, 'key': 'ISOSpeed', 'type': 4 }, '34868': { 'IFD': 2, 'key': 'ISOSpeedLatitudeyyy', 'type': 4 }, '34869': { 'IFD': 2, 'key': 'ISOSpeedLatitudezzz', 'type': 4 }, '36864': { 'IFD': 2, 'key': 'ExifVersion', 'type': 7 }, '36867': { 'IFD': 2, 'key': 'DateTimeOriginal', 'type': 2 }, '36868': { 'IFD': 2, 'key': 'DateTimeDigitized', 'type': 2 }, '37121': { 'IFD': 2, 'key': 'ComponentsConfiguration', 'type': 7 }, '37122': { 'IFD': 2, 'key': 'CompressedBitsPerPixel', 'type': 5 }, '37377': { 'IFD': 2, 'key': 'ShutterSpeedValue', 'type': 10 }, '37378': { 'IFD': 2, 'key': 'ApertureValue', 'type': 5 }, '37379': { 'IFD': 2, 'key': 'BrightnessValue', 'type': 10 }, '37380': { 'IFD': 2, 'key': 'ExposureBiasValue', 'type': 10 }, '37381': { 'IFD': 2, 'key': 'MaxApertureValue', 'type': 5 }, '37382': { 'IFD': 2, 'key': 'SubjectDistance', 'type': 5 }, '37383': { 'IFD': 2, 'key': 'MeteringMode', 'type': 3 }, '37384': { 'IFD': 2, 'key': 'LightSource', 'type': 3 }, '37385': { 'IFD': 2, 'key': 'Flash', 'type': 3 }, '37386': { 'IFD': 2, 'key': 'FocalLength', 'type': 5 }, '37387': { 'IFD': 1, 'key': 'FlashEnergy', 'type': 5 }, '37388': { 'IFD': 1, 'key': 'SpatialFrequencyResponse', 'type': 7 }, '37389': { 'IFD': 1, 'key': 'Noise', 'type': 7 }, '37390': { 'IFD': 1, 'key': 'FocalPlaneXResolution', 'type': 5 }, '37391': { 'IFD': 1, 'key': 'FocalPlaneYResolution', 'type': 5 }, '37392': { 'IFD': 1, 'key': 'FocalPlaneResolutionUnit', 'type': 3 }, '37393': { 'IFD': 1, 'key': 'ImageNumber', 'type': 4 }, '37394': { 'IFD': 1, 'key': 'SecurityClassification', 'type': 2 }, '37395': { 'IFD': 1, 'key': 'ImageHistory', 'type': 2 }, '37396': { 'IFD': 2, 'key': 'SubjectArea', 'type': 3 }, '37397': { 'IFD': 1, 'key': 'ExposureIndex', 'type': 5 }, '37398': { 'IFD': 1, 'key': 'TIFFEPStandardID', 'type': 1 }, '37399': { 'IFD': 1, 'key': 'SensingMethod', 'type': 3 }, '37500': { 'IFD': 2, 'key': 'MakerNote', 'type': 7 }, '37510': { 'IFD': 2, 'key': 'UserComment', 'type': 2 }, '37520': { 'IFD': 2, 'key': 'SubSecTime', 'type': 2 }, '37521': { 'IFD': 2, 'key': 'SubSecTimeOriginal', 'type': 2 }, '37522': { 'IFD': 2, 'key': 'SubSecTimeDigitized', 'type': 2 }, '40091': { 'IFD': 1, 'key': 'XPTitle', 'type': 1 }, '40092': { 'IFD': 1, 'key': 'XPComment', 'type': 1 }, '40093': { 'IFD': 1, 'key': 'XPAuthor', 'type': 1 }, '40094': { 'IFD': 1, 'key': 'XPKeywords', 'type': 1 }, '40095': { 'IFD': 1, 'key': 'XPSubject', 'type': 1 }, '40960': { 'IFD': 2, 'key': 'FlashpixVersion', 'type': 7 }, '40961': { 'IFD': 2, 'key': 'ColorSpace', 'type': 3 }, '40962': { 'IFD': 2, 'key': 'PixelXDimension', 'type': 4 }, '40963': { 'IFD': 2, 'key': 'PixelYDimension', 'type': 4 }, '40964': { 'IFD': 2, 'key': 'RelatedSoundFile', 'type': 2 }, '40965': { 'IFD': 2, 'key': 'InteroperabilityTag', 'type': 4 }, '41483': { 'IFD': 2, 'key': 'FlashEnergy', 'type': 5 }, '41484': { 'IFD': 2, 'key': 'SpatialFrequencyResponse', 'type': 7 }, '41486': { 'IFD': 2, 'key': 'FocalPlaneXResolution', 'type': 5 }, '41487': { 'IFD': 2, 'key': 'FocalPlaneYResolution', 'type': 5 }, '41488': { 'IFD': 2, 'key': 'FocalPlaneResolutionUnit', 'type': 3 }, '41492': { 'IFD': 2, 'key': 'SubjectLocation', 'type': 3 }, '41493': { 'IFD': 2, 'key': 'ExposureIndex', 'type': 5 }, '41495': { 'IFD': 2, 'key': 'SensingMethod', 'type': 3 }, '41728': { 'IFD': 2, 'key': 'FileSource', 'type': 7 }, '41729': { 'IFD': 2, 'key': 'SceneType', 'type': 7 }, '41730': { 'IFD': 2, 'key': 'CFAPattern', 'type': 7 }, '41985': { 'IFD': 2, 'key': 'CustomRendered', 'type': 3 }, '41986': { 'IFD': 2, 'key': 'ExposureMode', 'type': 3 }, '41987': { 'IFD': 2, 'key': 'WhiteBalance', 'type': 3 }, '41988': { 'IFD': 2, 'key': 'DigitalZoomRatio', 'type': 5 }, '41989': { 'IFD': 2, 'key': 'FocalLengthIn35mmFilm', 'type': 3 }, '41990': { 'IFD': 2, 'key': 'SceneCaptureType', 'type': 3 }, '41991': { 'IFD': 2, 'key': 'GainControl', 'type': 3 }, '41992': { 'IFD': 2, 'key': 'Contrast', 'type': 3 }, '41993': { 'IFD': 2, 'key': 'Saturation', 'type': 3 }, '41994': { 'IFD': 2, 'key': 'Sharpness', 'type': 3 }, '41995': { 'IFD': 2, 'key': 'DeviceSettingDescription', 'type': 7 }, '41996': { 'IFD': 2, 'key': 'SubjectDistanceRange', 'type': 3 }, '42016': { 'IFD': 2, 'key': 'ImageUniqueID', 'type': 2 }, '42032': { 'IFD': 2, 'key': 'CameraOwnerName', 'type': 2 }, '42033': { 'IFD': 2, 'key': 'BodySerialNumber', 'type': 2 }, '42034': { 'IFD': 2, 'key': 'LensSpecification', 'type': 5 }, '42035': { 'IFD': 2, 'key': 'LensMake', 'type': 2 }, '42036': { 'IFD': 2, 'key': 'LensModel', 'type': 2 }, '42037': { 'IFD': 2, 'key': 'LensSerialNumber', 'type': 2 }, '50341': { 'IFD': 1, 'key': 'PrintImageMatching', 'type': 7 }, '50706': { 'IFD': 1, 'key': 'DNGVersion', 'type': 1 }, '50707': { 'IFD': 1, 'key': 'DNGBackwardVersion', 'type': 1 }, '50708': { 'IFD': 1, 'key': 'UniqueCameraModel', 'type': 2 }, '50709': { 'IFD': 1, 'key': 'LocalizedCameraModel', 'type': 1 }, '50710': { 'IFD': 1, 'key': 'CFAPlaneColor', 'type': 1 }, '50711': { 'IFD': 1, 'key': 'CFALayout', 'type': 3 }, '50712': { 'IFD': 1, 'key': 'LinearizationTable', 'type': 3 }, '50713': { 'IFD': 1, 'key': 'BlackLevelRepeatDim', 'type': 3 }, '50714': { 'IFD': 1, 'key': 'BlackLevel', 'type': 5 }, '50715': { 'IFD': 1, 'key': 'BlackLevelDeltaH', 'type': 10 }, '50716': { 'IFD': 1, 'key': 'BlackLevelDeltaV', 'type': 10 }, '50717': { 'IFD': 1, 'key': 'WhiteLevel', 'type': 3 }, '50718': { 'IFD': 1, 'key': 'DefaultScale', 'type': 5 }, '50719': { 'IFD': 1, 'key': 'DefaultCropOrigin', 'type': 3 }, '50720': { 'IFD': 1, 'key': 'DefaultCropSize', 'type': 3 }, '50721': { 'IFD': 1, 'key': 'ColorMatrix1', 'type': 10 }, '50722': { 'IFD': 1, 'key': 'ColorMatrix2', 'type': 10 }, '50723': { 'IFD': 1, 'key': 'CameraCalibration1', 'type': 10 }, '50724': { 'IFD': 1, 'key': 'CameraCalibration2', 'type': 10 }, '50725': { 'IFD': 1, 'key': 'ReductionMatrix1', 'type': 10 }, '50726': { 'IFD': 1, 'key': 'ReductionMatrix2', 'type': 10 }, '50727': { 'IFD': 1, 'key': 'AnalogBalance', 'type': 5 }, '50728': { 'IFD': 1, 'key': 'AsShotNeutral', 'type': 3 }, '50729': { 'IFD': 1, 'key': 'AsShotWhiteXY', 'type': 5 }, '50730': { 'IFD': 1, 'key': 'BaselineExposure', 'type': 10 }, '50731': { 'IFD': 1, 'key': 'BaselineNoise', 'type': 5 }, '50732': { 'IFD': 1, 'key': 'BaselineSharpness', 'type': 5 }, '50733': { 'IFD': 1, 'key': 'BayerGreenSplit', 'type': 4 }, '50734': { 'IFD': 1, 'key': 'LinearResponseLimit', 'type': 5 }, '50735': { 'IFD': 1, 'key': 'CameraSerialNumber', 'type': 2 }, '50736': { 'IFD': 1, 'key': 'LensInfo', 'type': 5 }, '50737': { 'IFD': 1, 'key': 'ChromaBlurRadius', 'type': 5 }, '50738': { 'IFD': 1, 'key': 'AntiAliasStrength', 'type': 5 }, '50739': { 'IFD': 1, 'key': 'ShadowScale', 'type': 10 }, '50740': { 'IFD': 1, 'key': 'DNGPrivateData', 'type': 1 }, '50741': { 'IFD': 1, 'key': 'MakerNoteSafety', 'type': 3 }, '50778': { 'IFD': 1, 'key': 'CalibrationIlluminant1', 'type': 3 }, '50779': { 'IFD': 1, 'key': 'CalibrationIlluminant2', 'type': 3 }, '50780': { 'IFD': 1, 'key': 'BestQualityScale', 'type': 5 }, '50781': { 'IFD': 1, 'key': 'RawDataUniqueID', 'type': 1 }, '50827': { 'IFD': 1, 'key': 'OriginalRawFileName', 'type': 1 }, '50828': { 'IFD': 1, 'key': 'OriginalRawFileData', 'type': 7 }, '50829': { 'IFD': 1, 'key': 'ActiveArea', 'type': 3 }, '50830': { 'IFD': 1, 'key': 'MaskedAreas', 'type': 3 }, '50831': { 'IFD': 1, 'key': 'AsShotICCProfile', 'type': 7 }, '50832': { 'IFD': 1, 'key': 'AsShotPreProfileMatrix', 'type': 10 }, '50833': { 'IFD': 1, 'key': 'CurrentICCProfile', 'type': 7 }, '50834': { 'IFD': 1, 'key': 'CurrentPreProfileMatrix', 'type': 10 }, '50879': { 'IFD': 1, 'key': 'ColorimetricReference', 'type': 3 }, '50931': { 'IFD': 1, 'key': 'CameraCalibrationSignature', 'type': 1 }, '50932': { 'IFD': 1, 'key': 'ProfileCalibrationSignature', 'type': 1 }, '50934': { 'IFD': 1, 'key': 'AsShotProfileName', 'type': 1 }, '50935': { 'IFD': 1, 'key': 'NoiseReductionApplied', 'type': 5 }, '50936': { 'IFD': 1, 'key': 'ProfileName', 'type': 1 }, '50937': { 'IFD': 1, 'key': 'ProfileHueSatMapDims', 'type': 4 }, '50938': { 'IFD': 1, 'key': 'ProfileHueSatMapData1', 'type': 11 }, '50939': { 'IFD': 1, 'key': 'ProfileHueSatMapData2', 'type': 11 }, '50940': { 'IFD': 1, 'key': 'ProfileToneCurve', 'type': 11 }, '50941': { 'IFD': 1, 'key': 'ProfileEmbedPolicy', 'type': 4 }, '50942': { 'IFD': 1, 'key': 'ProfileCopyright', 'type': 1 }, '50964': { 'IFD': 1, 'key': 'ForwardMatrix1', 'type': 10 }, '50965': { 'IFD': 1, 'key': 'ForwardMatrix2', 'type': 10 }, '50966': { 'IFD': 1, 'key': 'PreviewApplicationName', 'type': 1 }, '50967': { 'IFD': 1, 'key': 'PreviewApplicationVersion', 'type': 1 }, '50968': { 'IFD': 1, 'key': 'PreviewSettingsName', 'type': 1 }, '50969': { 'IFD': 1, 'key': 'PreviewSettingsDigest', 'type': 1 }, '50970': { 'IFD': 1, 'key': 'PreviewColorSpace', 'type': 4 }, '50971': { 'IFD': 1, 'key': 'PreviewDateTime', 'type': 2 }, '50972': { 'IFD': 1, 'key': 'RawImageDigest', 'type': 7 }, '50973': { 'IFD': 1, 'key': 'OriginalRawFileDigest', 'type': 7 }, '50974': { 'IFD': 1, 'key': 'SubTileBlockSize', 'type': 4 }, '50975': { 'IFD': 1, 'key': 'RowInterleaveFactor', 'type': 4 }, '50981': { 'IFD': 1, 'key': 'ProfileLookTableDims', 'type': 4 }, '50982': { 'IFD': 1, 'key': 'ProfileLookTableData', 'type': 11 }, '51008': { 'IFD': 1, 'key': 'OpcodeList1', 'type': 7 }, '51009': { 'IFD': 1, 'key': 'OpcodeList2', 'type': 7 }, '51022': { 'IFD': 1, 'key': 'OpcodeList3', 'type': 7 }, '51041': { 'IFD': 1, 'key': 'NoiseProfile', 'type': 12 } }; var tagsStringValues = { 'ExposureProgram': { 0: 'Not defined', 1: 'Manual', 2: 'Normal program', 3: 'Aperture priority', 4: 'Shutter priority', 5: 'Creative program', 6: 'Action program', 7: 'Portrait mode', 8: 'Landscape mode' }, 'MeteringMode': { 0: 'Unknown', 1: 'Average', 2: 'CenterWeightedAverage', 3: 'Spot', 4: 'MultiSpot', 5: 'Pattern', 6: 'Partial', 255: 'Other' }, 'LightSource': { 0: 'Unknown', 1: 'Daylight', 2: 'Fluorescent', 3: 'Tungsten (incandescent light)', 4: 'Flash', 9: 'Fine weather', 10: 'Cloudy weather', 11: 'Shade', 12: 'Daylight fluorescent (D 5700 - 7100K)', 13: 'Day white fluorescent (N 4600 - 5400K)', 14: 'Cool white fluorescent (W 3900 - 4500K)', 15: 'White fluorescent (WW 3200 - 3700K)', 17: 'Standard light A', 18: 'Standard light B', 19: 'Standard light C', 20: 'D55', 21: 'D65', 22: 'D75', 23: 'D50', 24: 'ISO studio tungsten', 255: 'Other' }, 'Flash': { 0: 'Flash did not fire', 1: 'Flash fired', 5: 'Strobe return light not detected', 7: 'Strobe return light detected', 9: 'Flash fired, compulsory flash mode', 13: 'Flash fired, compulsory flash mode, return light not detected', 15: 'Flash fired, compulsory flash mode, return light detected', 16: 'Flash did not fire, compulsory flash mode', 24: 'Flash did not fire, auto mode', 25: 'Flash fired, auto mode', 29: 'Flash fired, auto mode, return light not detected', 31: 'Flash fired, auto mode, return light detected', 32: 'No flash function', 65: 'Flash fired, red-eye reduction mode', 69: 'Flash fired, red-eye reduction mode, return light not detected', 71: 'Flash fired, red-eye reduction mode, return light detected', 73: 'Flash fired, compulsory flash mode, red-eye reduction mode', 77: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected', 79: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected', 89: 'Flash fired, auto mode, red-eye reduction mode', 93: 'Flash fired, auto mode, return light not detected, red-eye reduction mode', 95: 'Flash fired, auto mode, return light detected, red-eye reduction mode' }, 'SensingMethod': { 1: 'Not defined', 2: 'One-chip color area sensor', 3: 'Two-chip color area sensor', 4: 'Three-chip color area sensor', 5: 'Color sequential area sensor', 7: 'Trilinear sensor', 8: 'Color sequential linear sensor' }, 'SceneCaptureType': { 0: 'Standard', 1: 'Landscape', 2: 'Portrait', 3: 'Night scene' }, 'SceneType': { 1: 'Directly photographed' }, 'CustomRendered': { 0: 'Normal process', 1: 'Custom process' }, 'WhiteBalance': { 0: 'Auto white balance', 1: 'Manual white balance' }, 'GainControl': { 0: 'None', 1: 'Low gain up', 2: 'High gain up', 3: 'Low gain down', 4: 'High gain down' }, 'Contrast': { 0: 'Normal', 1: 'Soft', 2: 'Hard' }, 'Saturation': { 0: 'Normal', 1: 'Low saturation', 2: 'High saturation' }, 'Sharpness': { 0: 'Normal', 1: 'Soft', 2: 'Hard' }, 'SubjectDistanceRange': { 0: 'Unknown', 1: 'Macro', 2: 'Close view', 3: 'Distant view' }, 'FileSource': { 3: 'DSC' }, 'Components': { 0: '', 1: 'Y', 2: 'Cb', 3: 'Cr', 4: 'R', 5: 'G', 6: 'B' } }; var orientationDegrees = { '1': 0, '2': 0, '3': 180, '4': 180, '5': 90, '6': 90, '7': 270, '8': 270 }; var rotateImage = function (orientation, degrees) { var clockWiseRotation = { 1: 6, 2: 5, 3: 8, 4: 7, 5: 4, 6: 3, 7: 2, 8: 1 }; var counterClockWiseRotation = { 1: 8, 2: 7, 3: 6, 4: 5, 5: 2, 6: 1, 7: 4, 8: 3 }; var steps = Math.abs(Math.ceil(degrees / 90)); var clockWise = degrees > 0; while (steps > 0) { orientation = clockWise ? clockWiseRotation[orientation] : counterClockWiseRotation[orientation]; steps--; } return orientation; }; var getTagId = function (key) { var id; Object.keys(tags).forEach(function (tagId) { if (tags[tagId].key === key) { id = tagId; } }); return id; }; this.JPEGParser = this.JPEGParser || {}; this.JPEGParser.exifSpec = { rotateImage: rotateImage, orientationDegrees: orientationDegrees, getTagId: getTagId, tags: tags, interOperabilityTags: interOperabilityTags, tagTypeSize: tagTypeSize }; }.call(this)); (function () { 'use strict'; var segmentTypes = { 1: 'TEM', 2: 'RES', 192: 'SOF0', 193: 'SOF1', 194: 'SOF2', 195: 'SOF3', 197: 'SOF5', 198: 'SOF6', 199: 'SOF7', 201: 'SOF9', 202: 'SOF10', 203: 'SOF11', 205: 'SOF13', 206: 'SOF14', 207: 'SOF15', 204: 'DAC', 196: 'DHT', 208: 'RST0', 209: 'RST1', 210: 'RST2', 211: 'RST3', 212: 'RST4', 213: 'RST5', 214: 'RST6', 215: 'RST7', 216: 'SOI', 217: 'EOI', 218: 'SOS', 219: 'DQT', 220: 'DNL', 221: 'DRI', 222: 'DHP', 223: 'EXP', 224: 'APP0', 225: 'APP1', 226: 'APP2', 227: 'APP3', 228: 'APP4', 229: 'APP5', 230: 'APP6', 231: 'APP7', 232: 'APP8', 233: 'APP9', 234: 'APP10', 235: 'APP11', 236: 'APP12', 237: 'APP13', 238: 'APP14', 239: 'APP15', 240: 'JPG0', 241: 'JPG1', 242: 'JPG2', 243: 'JPG3', 244: 'JPG4', 245: 'JPG5', 246: 'JPG6', 247: 'JPG7', 248: 'JPG8', 249: 'JPG9', 250: 'JPG10', 251: 'JPG11', 252: 'JPG12', 253: 'JPG13', 254: 'COM' }; var APPSegmentFormats = { 'JFIF': { 'segmentType': 'APP1' }, 'JFXX': { 'segmentType': 'APP1' }, 'Exif': { 'segmentType': 'APP0' } }; this.JPEGParser = this.JPEGParser || {}; this.JPEGParser.jpegSpec = {}; this.JPEGParser.jpegSpec.segmentTypes = segmentTypes; this.JPEGParser.jpegSpec.APPSegmentFormats = APPSegmentFormats; }.call(this)); (function () { 'use strict'; var readSegment = function (blobView, offset) { var metaData = {}; var thumbnailBlob; metaData.version = blobView.getUint8(offset + 9).toString(); metaData.version += '.0' + blobView.getUint8(offset + 10); metaData.units = blobView.getUint8(offset + 11); metaData.XDensity = blobView.getUint16(offset + 12); metaData.YDensity = blobView.getUint16(offset + 14); metaData.XThumbnail = blobView.getUint8(offset + 16); metaData.YThumbnail = blobView.getUint8(offset + 17); if (metaData.XThumbnail !== 0 && metaData.YThumbnail !== 0) { thumbnailBlob = blobView.blob.slice(offset + 18, 3 * metaData.XThumbnail * metaData.YThumbnail); } return { 'metaData': metaData, 'thumbnailBlob': thumbnailBlob }; }; this.JPEGParser = this.JPEGParser || {}; this.JPEGParser.JFIF = this.JPEGParser.JFIF || {}; this.JPEGParser.JFIF.readSegment = readSegment; }.call(this)); (function () { 'use strict'; var offsets = { 'segmentMarker': 0, 'APP1Marker': 1, 'APP1Length': 2, 'TIFFHeader': 10, 'TIFFByteOrder': 10, 'TIFFMagicNumber': 12, 'TIFFFirstIFD': 14 }; var exifSpec = JPEGParser.exifSpec; var mergeObjects = function (object1, object2) { for (var tag in object2) { if (object2.hasOwnProperty(tag)) { object1[tag] = object2[tag]; } } return object1; }; var parseASCIIString = function (blobView, offset, count) { var value = ''; for (var i = 0; i < count; i++) { var ch = blobView.getUint8(offset + i); if (ch !== 0 || i < count - 1) { value += String.fromCharCode(ch); } } return value; }; var writeTagValueArray = function (blobView, valueOffset, type, arrayOfValues, byteOrder) { var writtenBytes = 0; var i; if (Array.isArray(arrayOfValues)) { for (i = 0; i < arrayOfValues.length; ++i) { writtenBytes += writeTagValue(blobView, valueOffset + writtenBytes, type, arrayOfValues[i], byteOrder); } } else { throw 'Error writing array, the value is not an array: ' + arrayOfValues; } return writtenBytes; }; var writeTagValue = function (blobView, valueOffset, typeId, newValue, byteOrder) { var writtenBytes; if (Array.isArray(newValue)) { writtenBytes = writeTagValueArray(blobView, valueOffset, typeId, newValue, byteOrder); } else { switch (typeId) { case 1: blobView.setUint8(valueOffset, newValue); writtenBytes = 1; break; case 2: writtenBytes = writeString(blobView, valueOffset, newValue); break; case 3: blobView.setUint16(valueOffset, newValue, byteOrder); writtenBytes = 2; break; case 4: blobView.setUint32(valueOffset, newValue, byteOrder); writtenBytes = 4; break; case 6: blobView.setInt8(valueOffset, newValue); writtenBytes = 1; break; case 7: blobView.setUint8(valueOffset, newValue); writtenBytes = 1; break; case 8: blobView.setInt16(valueOffset, newValue, byteOrder); writtenBytes = 2; break; case 9: blobView.setInt32(valueOffset, newValue, byteOrder); writtenBytes = 4; break; case 10: case 5: writeRational(blobView, valueOffset, typeId, newValue, byteOrder); writtenBytes = 8; break; case 11: blobView.setFloat32(valueOffset, newValue, byteOrder); writtenBytes = 4; break; case 12: blobView.setFloat64(valueOffset, newValue, byteOrder); writtenBytes = 8; break; default: throw 'Writting Exif Tag Value: Unkown value type: ' + valueType; } } return writtenBytes; }; var parseTagValue = function (blobView, valueOffset, typeId, count) { var numerator; var denominator; switch (typeId) { case 1: return blobView.getUint8(valueOffset); case 2: return parseASCIIString(blobView, valueOffset, count); case 3: return blobView.getUint16(valueOffset); case 4: return blobView.getUint32(valueOffset); case 5: numerator = blobView.getUint32(valueOffset); denominator = blobView.getUint32(valueOffset + 4); return { 'numerator': numerator, 'denominator': denominator }; case 6: return blobView.getInt8(valueOffset); case 7: return blobView.getUint8(valueOffset); case 8: return blobView.getInt16(valueOffset); case 9: return blobView.getInt32(valueOffset); case 10: numerator = blobView.getInt32(valueOffset); denominator = blobView.getInt32(valueOffset + 4); return { 'numerator': numerator, 'denominator': denominator }; case 11: return blobView.getFloat32(valueOffset); case 12: return blobView.getFloat64(valueOffset); default: throw 'Reading Exif Tag Value: Unkown value type: ' + typeId; } }; var readTagValue = function (blobView, TIFFHeaderOffset, valueOffset, typeId, count) { var tagValues; var typeSize = exifSpec.tagTypeSize[typeId]; if (typeSize * count > 4) { valueOffset = TIFFHeaderOffset + blobView.getUint32(valueOffset); } if (count === 1 || typeId === 2) { return parseTagValue(blobView, valueOffset, typeId, count); } else { tagValues = []; for (var i = 0; i < count; ++i) { tagValues.push(parseTagValue(blobView, valueOffset, typeId, 1)); valueOffset += typeSize; } return tagValues; } }; var writeRational = function (blobView, valueOffset, typeId, newValue, byteOrder) { if (typeId === 10) { blobView.setInt32(valueOffset, newValue.numerator, byteOrder); blobView.setInt32(valueOffset + 4, newValue.denominator, byteOrder); } if (typeId === 5) { blobView.setUint32(valueOffset, newValue.numerator, byteOrder); blobView.setUint32(valueOffset + 4, newValue.denominator, byteOrder); } return 8; }; var writeString = function (blobView, offset, str) { var i; for (i = 0; i < str.length; ++i) { blobView.setUint8(offset + i, str.charCodeAt(i)); } blobView.setUint8(offset + str.length, 0); return str.length + 1; }; var readIFD = function (blobView, TIFFHeaderOffset, IFDOffset) { var offset = TIFFHeaderOffset + IFDOffset; var numberOfEntries = blobView.getUint16(offset); offset += 2; var i; var entries; var entry; var tag; var typeId; var count; var tagValueOffset; var nextIFDOffset; if (numberOfEntries > 0) { entries = {}; } for (i = 0; i < numberOfEntries; ++i) { tag = blobView.getUint16(offset); typeId = blobView.getUint16(offset + 2); count = blobView.getUint32(offset + 4); entries[tag] = { 'type': typeId, 'count': count, 'value': readTagValue(blobView, TIFFHeaderOffset, offset + 8, typeId, count), 'valueOffset': offset + 8 }; offset += 12; } nextIFDOffset = blobView.getUint32(offset); return { 'entries': entries, 'nextIFDOffset': nextIFDOffset }; }; var writeIFD = function (blobView, TIFFHeaderOffset, IFDOffset, valuesOffset, IFDType, metaData, nextIFD) { var count; var bytesWritten = 0; var bytesWrittenValue; var numberOfEntries = 0; var offset = IFDOffset + 2; Object.keys(metaData).forEach(function (key) { var tagId = exifSpec.getTagId(key); var tagInfo = exifSpec.tags[tagId]; if (!tagInfo) { return; } var type = tagInfo.type; var typeSize = exifSpec.tagTypeSize[type]; if (tagId && tagInfo.IFD === IFDType) { blobView.setUint16(offset, tagId, false); blobView.setUint16(offset + 2, type, false); count = calculateTagValueCount(type, metaData[key]); blobView.setUint32(offset + 4, count, false); if (count * typeSize <= 4) { writeTagValue(blobView, offset + 8, type, metaData[key], false); } else { blobView.setUint32(offset + 8, valuesOffset - TIFFHeaderOffset, false); bytesWrittenValue = writeTagValue(blobView, valuesOffset, type, metaData[key], false); if (bytesWrittenValue % 2 === 1) bytesWrittenValue++; valuesOffset += bytesWrittenValue; bytesWritten += bytesWrittenValue; } bytesWritten += 12; offset += 12; numberOfEntries++; } }); if (numberOfEntries || IFDType === 2 && metaData.ExifTag || IFDType === 3 && metaData.GPSTag || IFDType === 4 && metaData.InteroperabilityTag) { blobView.setUint16(IFDOffset, numberOfEntries, false); bytesWritten += 2; } if (IFDType === 1) { bytesWritten += 4; if (nextIFD) { blobView.setUint32(offset, bytesWritten + 8, false); } else { blobView.setUint32(offset, 0, false); } } return bytesWritten; }; var makeDirectoryEntriesHumanReadable = function (entries) { var tags = {}; var tagInfo; Object.keys(entries).forEach(function (tag) { tagInfo = entries.IFD === 4 ? interOperabilityTags.tags[tag] : exifSpec.tags[tag]; if (!tagInfo) { if (showErrors) console.log('Error parsing IFD: Tag ' + tag + ' is not valid'); return; } tags[tagInfo.key] = entries[tag].value; }); return tags; }; var readTIFFByteOrder = function (blobView, TIFFOffset) { var byteOrder = blobView.getUint16(TIFFOffset + offsets.TIFFByteOrder); if (byteOrder !== 18761 && byteOrder !== 19789) { throw 'TIFF Image parser failed: Invalid byte order in EXIF segment'; } return byteOrder; }; var isTIFFLittleEndian = function (byteOrder) { if (byteOrder === 18761) { return true; } else if (byteOrder === 19789) { return false; } else { throw 'TIFF Image parser failed: Invalid byte order in EXIF segment'; } }; var isValidTIFFFile = function (blobView, TIFFOffset) { var TIFFMagicNumber = blobView.getUint16(TIFFOffset + offsets.TIFFMagicNumber); if (TIFFMagicNumber !== 42) { throw 'TIFF Image parser failed: Wrong magic number in TIFF header'; } return true; }; var readExifMetaData = function (blobView, TIFFOffset) { var thumbnailBlob; var thumbnailIFDEntries; var IFD0; var IFD1; var EXIFIFD; var GPSIFD; var interoperabilityIFD; var JPEGInterchangeFormatLength; var JPEGInterchangeFormat; var TIFFHeaderOffset = TIFFOffset + offsets.TIFFHeader; var byteOrder = readTIFFByteOrder(blobView, TIFFOffset); blobView.littleEndian = isTIFFLittleEndian(byteOrder); if (!isValidTIFFFile(blobView, TIFFOffset)) { return; } offsets.firstIFD = blobView.getUint32(TIFFOffset + offsets.TIFFFirstIFD); IFD0 = readIFD(blobView, TIFFHeaderOffset, offsets.firstIFD); if (IFD0.nextIFDOffset) { IFD1 = readIFD(blobView, TIFFHeaderOffset, IFD0.nextIFDOffset); } if (IFD1 && IFD1.entries[exifSpec.getTagId('JPEGInterchangeFormat')]) { JPEGInterchangeFormatLength = IFD1.entries[exifSpec.getTagId('JPEGInterchangeFormatLength')].value; JPEGInterchangeFormat = IFD1.entries[exifSpec.getTagId('JPEGInterchangeFormat')].value; thumbnailBlob = blobView.blob.slice(TIFFHeaderOffset + JPEGInterchangeFormat, TIFFHeaderOffset + JPEGInterchangeFormat + JPEGInterchangeFormatLength); } if (typeof IFD0.entries === 'undefined') IFD0.entries = [{}]; if (IFD0.entries[exifSpec.getTagId('ExifTag')]) { EXIFIFD = readIFD(blobView, TIFFHeaderOffset, IFD0.entries[exifSpec.getTagId('ExifTag')].value); } if (IFD0.entries[exifSpec.getTagId('GPSTag')]) { GPSIFD = readIFD(blobView, TIFFHeaderOffset, IFD0.entries[exifSpec.getTagId('GPSTag')].value); } if (IFD0.entries[exifSpec.getTagId('InteroperabilityTag')]) { interoperabilityIFD = readIFD(blobView, TIFFHeaderOffset, IFD0.entries[exifSpec.getTagId('InteroperabilityTag')].value); } return { 'IFD0': IFD0.entries, 'IFD1': IFD1 && IFD1.entries, 'EXIFIFD': EXIFIFD && EXIFIFD.entries, 'GPSIFD': GPSIFD && GPSIFD.entries, 'interoperabilityIFD': interoperabilityIFD && interoperabilityIFD.entries, 'thumbnailBlob': thumbnailBlob, 'byteOrder': byteOrder }; }; var calculateTagValueSize = function (tagName, value) { var tagId = exifSpec.getTagId(tagName); var tagTypeId = exifSpec.tags[tagId].type; var length = 0; switch (tagTypeId) { case 1: case 6: case 7: length = 1; break; case 2: length = value.length + 1; break; case 3: case 8: length = 2; break; case 4: case 9: case 11: length = 4; break; case 10: case 5: case 12: length = 8; break; default: throw 'Calculating Exif Tag Value Size: Unkown value type: ' + tagTypeId; } if (Array.isArray(value)) { length = value.length * length; } return length; }; var calculateTagValueCount = function (tagType, value) { if (Array.isArray(value)) { return value.length; } if (tagType === 2) { return value.length + 1; } return 1; }; var calculateIFDLengths = function (metaData) { var ExifTags; var GPSTags; var interoperabilityTags; var IFD0Tags = false; var lengths = { IFD0Length: 0, IFD0LengthDataSection: 0, ExifIFDLength: 0, ExifIFDLengthDataSection: 0, GPSIFDLength: 0, GPSIFDLengthDataSection: 0, interoperabilityIFDLength: 0, interoperabilityLengthDataSection: 0 }; var exifTagAlreadyPresent = false; var gpsTagAlreadyPresent = false; var interoperabilityTagAlreadyPresent = false; var valueSize; var IFDSize = 12; Object.keys(metaData).forEach(function (key) { var tagId = exifSpec.getTagId(key); var tagInfo = tagId && exifSpec.tags[tagId]; if (tagInfo) { valueSize = calculateTagValueSize(key, metaData[key]); if (valueSize <= 4) { valueSize = 0; } else { if (valueSize % 2 === 1) valueSize += 1; } if (tagInfo.IFD === 1) { lengths.IFD0Length += IFDSize; lengths.IFD0LengthDataSection += valueSize; IFD0Tags = true; } if (tagInfo.IFD === 2) { lengths.ExifIFDLength += IFDSize; lengths.ExifIFDLengthDataSection += valueSize; ExifTags = true; } if (tagInfo.IFD === 3) { lengths.GPSIFDLength += IFDSize; lengths.GPSIFDLengthDataSection += valueSize; GPSTags = true; } if (tagInfo.IFD === 4) { lengths.interoperabilityIFDLength += IFDSize; lengths.interoperabilityLengthDataSection += valueSize; interoperabilityTags = true; } } }); lengths.IFD0Length += 4; if (ExifTags && !metaData.ExifTag) { lengths.IFD0Length += 12; } if (GPSTags && !metaData.GPSTag) { lengths.IFD0Length += 12; } if (interoperabilityTags && !metaData.InteroperabilityTag) { lengths.IFD0Length += 12; } lengths.IFD0Length += 2; if (metaData.ExifTag) { lengths.ExifIFDLength += 2; } if (metaData.GPSTag) { lengths.GPSIFDLength += 2; } if (metaData.InteroperabilityTag) { lengths.interoperabilityIFDLength += 2; } return lengths; }; var writeSegmentHeader = function (blobView, offset, length) { blobView.setUint16(offset, 65505, false); blobView.setUint16(offset + 2, length, false); blobView.setUint8(offset + 4, 69); blobView.setUint8(offset + 5, 120); blobView.setUint8(offset + 6, 105); blobView.setUint8(offset + 7, 102); blobView.setUint8(offset + 8, 0); blobView.setUint8(offset + 9, 0); return 10; }; var writeTiffHeader = function (blobView, offset) { blobView.setUint16(offset + 0, 19789, false); blobView.setUint16(offset + 2, 42, false); blobView.setUint32(offset + 4, 8, false); return 8; }; var createSegment = function (metaData, callback, thumbnailBlob, thumbnailMetaData) { var IFDBuffer; var blob; var valuesOffset; var offset = 0; thumbnailMetaData = thumbnailMetaData || {}; if (thumbnailBlob) { thumbnailMetaData.JPEGInterchangeFormat = 0; thumbnailMetaData.JPEGInterchangeFormatLength = thumbnailBlob.size; thumbnailMetaData.Orientation = metaData.Orientation; } var IFD1Lengths = calculateIFDLengths(thumbnailMetaData); var IFD1Length = thumbnailBlob ? IFD1Lengths.IFD0Length : 0; var IFD1LengthDataSection = thumbnailBlob ? IFD1Lengths.IFD0LengthDataSection : 0; var IFDlengths = calculateIFDLengths(metaData); var IFD0Length = IFDlengths.IFD0Length; var IFD0LengthDataSection = IFDlengths.IFD0LengthDataSection; var ExifIFDLength = IFDlengths.ExifIFDLength; var ExifIFDLengthDataSection = IFDlengths.ExifIFDLengthDataSection; var GPSIFDLength = IFDlengths.GPSIFDLength; var GPSIFDLengthDataSection = IFDlengths.GPSIFDLengthDataSection; var interoperabilityIFDLength = IFDlengths.interoperabilityIFDLength; var interoperabilityLengthDataSection = IFDlengths.interoperabilityLengthDataSection; var tiffHeaderOffset; var exifSegmentBlob; var segmentContent = []; var headerLength = 18; var IFDLengths = headerLength + IFD0Length + IFD1Length + ExifIFDLength + GPSIFDLength + interoperabilityIFDLength; var DataSectionsLength = IFD0LengthDataSection + IFD1LengthDataSection + ExifIFDLengthDataSection + GPSIFDLengthDataSection + interoperabilityLengthDataSection; var segmentLength = IFDLengths + DataSectionsLength; var segmentLengthWithThumbnail = thumbnailBlob ? segmentLength + thumbnailBlob.size : segmentLength; var writtenBytesError = 'Written bytes and segment length don\'t match. There was a problem creating the segment'; IFDBuffer = new ArrayBuffer(segmentLength); blob = new Blob([IFDBuffer], { type: 'image/jpeg' }); JPEGParser.BlobView.get(blob, 0, blob.size, function (blobView) { offset += writeSegmentHeader(blobView, offset, segmentLengthWithThumbnail - 2); tiffHeaderOffset = offset; offset += writeTiffHeader(blobView, offset); if (ExifIFDLength) { metaData.ExifTag = 8 + IFD0Length + IFD0LengthDataSection + IFD1Length + IFD1LengthDataSection; } if (GPSIFDLength) { metaData.GPSTag = 8 + IFD0Length + IFD0LengthDataSection + IFD1Length + IFD1LengthDataSection + ExifIFDLength + ExifIFDLengthDataSection; } if (interoperabilityIFDLength) { metaData.InteroperabilityTag = 8 + IFD0Length + IFD0LengthDataSection + IFD1Length + IFD1LengthDataSection + ExifIFDLength + ExifIFDLengthDataSection + GPSIFDLength + GPSIFDLengthDataSection; } offset += writeIFD(blobView, tiffHeaderOffset, offset, offset + IFD0Length, 1, metaData, ExifIFDLength); if (IFD1Length) { thumbnailMetaData.JPEGInterchangeFormat = segmentLength - 10; offset += writeIFD(blobView, tiffHeaderOffset, offset, offset + IFD1Length, 1, thumbnailMetaData, ExifIFDLength); } offset += writeIFD(blobView, tiffHeaderOffset, offset, offset + ExifIFDLength, 2, metaData); offset += writeIFD(blobView, tiffHeaderOffset, offset, offset + GPSIFDLength, 3, metaData); offset += writeIFD(blobView, tiffHeaderOffset, offset, offset + interoperabilityIFDLength, 4, metaData); if (offset !== segmentLength) { if (showErrors) console.log(writtenBytesError); callback(writtenBytesError); return; } segmentContent.push(blobView.buffer); if (thumbnailMetaData && thumbnailBlob) { segmentContent.push(thumbnailBlob); } exifSegmentBlob = new Blob(segmentContent); callback(null, exifSegmentBlob); }); }; var createThumbnail = function (file, callback, scaleFactor) { var image = new Image(); var thumbnailCreated = function (thumbnailBlob) { callback(null, thumbnailBlob); }; scaleFactor = scaleFactor || 8; image.onload = function () { var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); canvas.height = image.height / scaleFactor; canvas.width = image.width / scaleFactor; context.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height); canvas.toBlob(thumbnailCreated, 'image/jpeg'); URL.revokeObjectURL(image.src); image.src = ''; }; image.src = URL.createObjectURL(file); }; var readSegment = function (blobView, segmentOffset) { var segmentMetaData = readExifMetaData(blobView, segmentOffset); var exifMetaData = segmentMetaData.IFD0; exifMetaData = mergeObjects(exifMetaData, segmentMetaData.EXIFIFD); exifMetaData = mergeObjects(exifMetaData, segmentMetaData.GPSIFD); return { 'metaData': makeDirectoryEntriesHumanReadable(exifMetaData), 'thumbnailMetaData': segmentMetaData.IFD1 && makeDirectoryEntriesHumanReadable(segmentMetaData.IFD1), 'thumbnailBlob': segmentMetaData.thumbnailBlob }; }; this.JPEGParser = this.JPEGParser || {}; this.JPEGParser.Exif = this.JPEGParser.Exif || {}; this.JPEGParser.Exif.mergeObjects = mergeObjects; this.JPEGParser.Exif.readSegment = readSegment; this.JPEGParser.Exif.createSegment = createSegment; this.JPEGParser.Exif.createThumbnail = createThumbnail; }.call(this)); (function () { 'use strict'; var offsets = { 'SOIMarker': 0, 'segmentMarker': 0, 'segmentType': 1, 'segmentLength': 2, 'segmentFormat': 4, 'firstSegment': 2 }; var metaDataTypes = { 'Exif': JPEGParser.Exif, 'JFIF': JPEGParser.JFIF }; var showErrors = false; var turnErrorsOn = function () { showErrors = true; }; var readSegmentMarker = function (blobView, offset) { return blobView.getUint8(offset + offsets.segmentMarker); }; var readSegmentType = function (blobView, offset) { return blobView.getUint8(offset + offsets.segmentType); }; var readSegmentLength = function (blobView, offset) { var segmentType = JPEGParser.jpegSpec.segmentTypes[readSegmentType(blobView, offset)]; if (segmentType === 'SOS' || segmentType.indexOf('RST') === 0) { return findNextSegmentOffset(blobView, offset) - offset; } return blobView.getUint16(offset + 2, false) + 2; }; var readSegmentFormat = function (blobView, offset) { return blobView.getNullTerminatedASCIIString(offset + offsets.segmentFormat); }; var validateJPEGFile = function (blobView) { if (blobView.byteLength < 2 || blobView.getUint16(offsets.SOIMarker) !== 65496) { return false; } return true; }; var validateSegment = function (segmentMarker, segmentType) { return segmentMarker === 255 && segmentType > 0 && segmentType < 255; }; var findNextSegmentOffset = function (blobView, offset) { offset += 2; var previousByte = 0; var currentByte; while (true) { if (offset >= blobView.sliceLength) { break; } currentByte = blobView.getUint8(offset); if (currentByte !== 0 && previousByte === 255) { break; } previousByte = currentByte; offset += 1; } return offset - 1; }; var isSOFSegment = function (segmentType) { return segmentType >= 192 && segmentType <= 195 || segmentType >= 197 && segmentType <= 199 || segmentType >= 201 && segmentType <= 203 || segmentType >= 205 && segmentType <= 207; }; var isEOISegment = function (segmentType) { return segmentType === 217; }; var isAPPSegment = function (segmentType) { return segmentType >= 224 && segmentType <= 239; }; var parseAPPSegment = function (blobView, offset) { var segmentFormat = readSegmentFormat(blobView, offset); var segment; if (metaDataTypes[segmentFormat]) { segment = metaDataTypes[segmentFormat].readSegment(blobView, offset); return { 'format': segmentFormat, 'offset': offset, 'metaData': segment.metaData, 'thumbnailMetaData': segment.thumbnailMetaData, 'thumbnailBlob': segment.thumbnailBlob }; } else { if (showErrors) console.log('Unkown APP segment format: ' + segmentFormat); } }; var parseSegments = function (blobView) { var offset = 2; var segmentsMetaData = {}; var APPSegment; var segmentLength; while (offset + 4 <= blobView.sliceLength) { var segmentMarker = readSegmentMarker(blobView, offset); var segmentType = readSegmentType(blobView, offset); if (!validateSegment(segmentMarker, segmentType)) { if (showErrors) console.log('Invalid JPEG Segment at offset ' + offset); break; } if (isEOISegment(segmentType)) { break; } if (isSOFSegment(segmentType)) { segmentsMetaData.height = blobView.getUint16(offset + 5, false); segmentsMetaData.width = blobView.getUint16(offset + 7, false); segmentsMetaData.progressive = segmentType === 194; } if (isAPPSegment(segmentType)) { APPSegment = parseAPPSegment(blobView, offset); if (APPSegment) { segmentsMetaData[APPSegment.format] = APPSegment.metaData; segmentsMetaData[APPSegment.format].segmentOffset = APPSegment.offset; segmentsMetaData[APPSegment.format].segmentLength = readSegmentLength(blobView, offset); segmentsMetaData.thumbnailBlob = segmentsMetaData.thumbnailBlob || APPSegment.thumbnailBlob; segmentsMetaData.thumbnailMetaData = segmentsMetaData.thumbnailBlob || APPSegment.thumbnailMetaData; } } segmentLength = readSegmentLength(blobView, offset); if (segmentLength <= 0) { throw 'Invalid length in segement at offset: ' + offset; } offset += segmentLength; } return segmentsMetaData; }; var validateExifSegment = function (blobView, offset) { var firstSegmentType = JPEGParser.jpegSpec.segmentTypes[readSegmentType(blobView, offset)]; var firstSegmentFormat = readSegmentFormat(blobView, offset); if (firstSegmentType !== 'APP1' || firstSegmentFormat !== 'Exif') { return false; } return true; }; var readJPEGSegments = function (blob, size, callback, validateFirstSegment) { JPEGParser.BlobView.get(blob, 0, size, function (blobView) { if (validateJPEGFile(blobView) === false) { callback('Not a valid JPEG file'); } else { if (validateFirstSegment && !validateFirstSegment(blobView, 2)) { callback('First segment not valid'); } else { callback(null, parseSegments(blobView), blobView); } } }); }; var insertSegment = function (segmentBlob, blob, metaDataType, callback) { JPEGParser.BlobView.get(blob, 0, blob.size, function (blobView) { var blobSegments; var blob; var blobBeforeSegment; var blobAfterSegment; var existingSegment; var fileSegments; if (validateJPEGFile(blobView) === false) { callback('Not a valid JPEG file'); } else { fileSegments = parseSegments(blobView); if (fileSegments[metaDataType]) { existingSegment = fileSegments[metaDataType]; blobBeforeSegment = blobView.blob.slice(0, existingSegment.segmentOffset); blobAfterSegment = blobView.blob.slice(existingSegment.segmentOffset + existingSegment.segmentLength, blobView.sliceLength); } else { blobBeforeSegment = blobView.blob.slice(0, 2); blobAfterSegment = blobView.blob.slice(2, blobView.sliceLength); } blob = new Blob([ blobBeforeSegment, segmentBlob, blobAfterSegment ], { type: 'image/jpeg' }); callback(null, blob); } }); }; var readMetaData = function (blob, callback, validateFirstSegment) { var processSegments = function (error, segmentsMetaData) { if (error) { callback(error); } else { segmentsMetaData.fileType = 'JPEG'; segmentsMetaData.fileSize = blob.size; callback(null, segmentsMetaData); } }; var size = Math.min(64 * 1024 + 2, blob.size); readJPEGSegments(blob, size, processSegments, validateFirstSegment); }; var writeMetaData = function (blob, size, newMetaData, metaDataType, callback, createNewThumbnail) { var processSegments = function (error, segmentsMetaData, blobView) { var segmentCreated = function (error, segmentBlob) { insertSegment(segmentBlob, blob, metaDataType, callback); }; var createSegment = function (thumbnailMetaData, thumbnailBlob) { metaDataTypes[metaDataType].createSegment(newMetaData, segmentCreated, thumbnailBlob, thumbnailMetaData); }; var thumbnailCreated = function (error, thumbnailBlob) { createSegment({}, thumbnailBlob); }; if (metaDataTypes[metaDataType]) { if (segmentsMetaData[metaDataType]) { newMetaData = JPEGParser.Exif.mergeObjects(segmentsMetaData[metaDataType], newMetaData); } if (createNewThumbnail) { metaDataTypes[metaDataType].createThumbnail(blob, thumbnailCreated, 16); } else { createSegment(segmentsMetaData.thumbnailMetaData, segmentsMetaData.thumbnailBlob); } } else { throw 'Writting MetaData: Unknown type of MetaData ' + metaDataType; } }; readJPEGSegments(blob, size, processSegments); }; var readExifMetaData = function (blob, callback) { var processMetaData = function (error, metaData) { metaData = metaData && metaData.Exif; callback(error, metaData); }; readMetaData(blob, processMetaData); }; var writeExifMetaData = function (blob, metaData, callback) { writeMetaData(blob, blob.size, metaData, 'Exif', callback); }; this.JPEGParser = this.JPEGParser || {}; this.showErrors = false; this.JPEGParser.turnErrorsOn = turnErrorsOn; this.JPEGParser.readMetaData = readMetaData; this.JPEGParser.readExifMetaData = readExifMetaData; this.JPEGParser.writeExifMetaData = writeExifMetaData; }.call(this));