UNPKG

vue-image-upload-resize

Version:

A simple Vue.js component for client-side image upload with resizing

826 lines (750 loc) 26.2 kB
/** Adapted and simplified from https://github.com/exif-js/exif-js MIT License (c) Jacob Seidelin https://github.com/exif-js/exif-js/blob/master/LICENSE.md */ var debug = false var ExifTags = { // version tags 0x9000: 'ExifVersion', // EXIF version 0xa000: 'FlashpixVersion', // Flashpix format version // colorspace tags 0xa001: 'ColorSpace', // Color space information tag // image configuration 0xa002: 'PixelXDimension', // Valid width of meaningful image 0xa003: 'PixelYDimension', // Valid height of meaningful image 0x9101: 'ComponentsConfiguration', // Information about channels 0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel // user information 0x927c: 'MakerNote', // Any desired information written by the manufacturer 0x9286: 'UserComment', // Comments by user // related file 0xa004: 'RelatedSoundFile', // Name of related sound file // date and time 0x9003: 'DateTimeOriginal', // Date and time when the original image was generated 0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally 0x9290: 'SubsecTime', // Fractions of seconds for DateTime 0x9291: 'SubsecTimeOriginal', // Fractions of seconds for DateTimeOriginal 0x9292: 'SubsecTimeDigitized', // Fractions of seconds for DateTimeDigitized // picture-taking conditions 0x829a: 'ExposureTime', // Exposure time (in seconds) 0x829d: 'FNumber', // F number 0x8822: 'ExposureProgram', // Exposure program 0x8824: 'SpectralSensitivity', // Spectral sensitivity 0x8827: 'ISOSpeedRatings', // ISO speed rating 0x8828: 'OECF', // Optoelectric conversion factor 0x9201: 'ShutterSpeedValue', // Shutter speed 0x9202: 'ApertureValue', // Lens aperture 0x9203: 'BrightnessValue', // Value of brightness 0x9204: 'ExposureBias', // Exposure bias 0x9205: 'MaxApertureValue', // Smallest F number of lens 0x9206: 'SubjectDistance', // Distance to subject in meters 0x9207: 'MeteringMode', // Metering mode 0x9208: 'LightSource', // Kind of light source 0x9209: 'Flash', // Flash status 0x9214: 'SubjectArea', // Location and area of main subject 0x920a: 'FocalLength', // Focal length of the lens in mm 0xa20b: 'FlashEnergy', // Strobe energy in BCPS 0xa20c: 'SpatialFrequencyResponse', // 0xa20e: 'FocalPlaneXResolution', // Number of pixels in width direction per FocalPlaneResolutionUnit 0xa20f: 'FocalPlaneYResolution', // Number of pixels in height direction per FocalPlaneResolutionUnit 0xa210: 'FocalPlaneResolutionUnit', // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution 0xa214: 'SubjectLocation', // Location of subject in image 0xa215: 'ExposureIndex', // Exposure index selected on camera 0xa217: 'SensingMethod', // Image sensor type 0xa300: 'FileSource', // Image source (3 == DSC) 0xa301: 'SceneType', // Scene type (1 == directly photographed) 0xa302: 'CFAPattern', // Color filter array geometric pattern 0xa401: 'CustomRendered', // Special processing 0xa402: 'ExposureMode', // Exposure mode 0xa403: 'WhiteBalance', // 1 = auto white balance, 2 = manual 0xa404: 'DigitalZoomRation', // Digital zoom ratio 0xa405: 'FocalLengthIn35mmFilm', // Equivalent foacl length assuming 35mm film camera (in mm) 0xa406: 'SceneCaptureType', // Type of scene 0xa407: 'GainControl', // Degree of overall image gain adjustment 0xa408: 'Contrast', // Direction of contrast processing applied by camera 0xa409: 'Saturation', // Direction of saturation processing applied by camera 0xa40a: 'Sharpness', // Direction of sharpness processing applied by camera 0xa40b: 'DeviceSettingDescription', // 0xa40c: 'SubjectDistanceRange', // Distance to subject // other tags 0xa005: 'InteroperabilityIFDPointer', 0xa420: 'ImageUniqueID', // Identifier assigned uniquely to each image } var TiffTags = { 0x0100: 'ImageWidth', 0x0101: 'ImageHeight', 0x8769: 'ExifIFDPointer', 0x8825: 'GPSInfoIFDPointer', 0xa005: 'InteroperabilityIFDPointer', 0x0102: 'BitsPerSample', 0x0103: 'Compression', 0x0106: 'PhotometricInterpretation', 0x0112: 'Orientation', 0x0115: 'SamplesPerPixel', 0x011c: 'PlanarConfiguration', 0x0212: 'YCbCrSubSampling', 0x0213: 'YCbCrPositioning', 0x011a: 'XResolution', 0x011b: 'YResolution', 0x0128: 'ResolutionUnit', 0x0111: 'StripOffsets', 0x0116: 'RowsPerStrip', 0x0117: 'StripByteCounts', 0x0201: 'JPEGInterchangeFormat', 0x0202: 'JPEGInterchangeFormatLength', 0x012d: 'TransferFunction', 0x013e: 'WhitePoint', 0x013f: 'PrimaryChromaticities', 0x0211: 'YCbCrCoefficients', 0x0214: 'ReferenceBlackWhite', 0x0132: 'DateTime', 0x010e: 'ImageDescription', 0x010f: 'Make', 0x0110: 'Model', 0x0131: 'Software', 0x013b: 'Artist', 0x8298: 'Copyright', } var GPSTags = { 0x0000: 'GPSVersionID', 0x0001: 'GPSLatitudeRef', 0x0002: 'GPSLatitude', 0x0003: 'GPSLongitudeRef', 0x0004: 'GPSLongitude', 0x0005: 'GPSAltitudeRef', 0x0006: 'GPSAltitude', 0x0007: 'GPSTimeStamp', 0x0008: 'GPSSatellites', 0x0009: 'GPSStatus', 0x000a: 'GPSMeasureMode', 0x000b: 'GPSDOP', 0x000c: 'GPSSpeedRef', 0x000d: 'GPSSpeed', 0x000e: 'GPSTrackRef', 0x000f: 'GPSTrack', 0x0010: 'GPSImgDirectionRef', 0x0011: 'GPSImgDirection', 0x0012: 'GPSMapDatum', 0x0013: 'GPSDestLatitudeRef', 0x0014: 'GPSDestLatitude', 0x0015: 'GPSDestLongitudeRef', 0x0016: 'GPSDestLongitude', 0x0017: 'GPSDestBearingRef', 0x0018: 'GPSDestBearing', 0x0019: 'GPSDestDistanceRef', 0x001a: 'GPSDestDistance', 0x001b: 'GPSProcessingMethod', 0x001c: 'GPSAreaInformation', 0x001d: 'GPSDateStamp', 0x001e: 'GPSDifferential', } // EXIF 2.3 Spec var IFD1Tags = { 0x0100: 'ImageWidth', 0x0101: 'ImageHeight', 0x0102: 'BitsPerSample', 0x0103: 'Compression', 0x0106: 'PhotometricInterpretation', 0x0111: 'StripOffsets', 0x0112: 'Orientation', 0x0115: 'SamplesPerPixel', 0x0116: 'RowsPerStrip', 0x0117: 'StripByteCounts', 0x011a: 'XResolution', 0x011b: 'YResolution', 0x011c: 'PlanarConfiguration', 0x0128: 'ResolutionUnit', 0x0201: 'JpegIFOffset', // When image format is JPEG, this value show offset to JPEG data stored.(aka "ThumbnailOffset" or "JPEGInterchangeFormat") 0x0202: 'JpegIFByteCount', // When image format is JPEG, this value shows data size of JPEG image (aka "ThumbnailLength" or "JPEGInterchangeFormatLength") 0x0211: 'YCbCrCoefficients', 0x0212: 'YCbCrSubSampling', 0x0213: 'YCbCrPositioning', 0x0214: 'ReferenceBlackWhite', } var StringValues = { 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: { 0x0000: 'Flash did not fire', 0x0001: 'Flash fired', 0x0005: 'Strobe return light not detected', 0x0007: 'Strobe return light detected', 0x0009: 'Flash fired, compulsory flash mode', 0x000d: 'Flash fired, compulsory flash mode, return light not detected', 0x000f: 'Flash fired, compulsory flash mode, return light detected', 0x0010: 'Flash did not fire, compulsory flash mode', 0x0018: 'Flash did not fire, auto mode', 0x0019: 'Flash fired, auto mode', 0x001d: 'Flash fired, auto mode, return light not detected', 0x001f: 'Flash fired, auto mode, return light detected', 0x0020: 'No flash function', 0x0041: 'Flash fired, red-eye reduction mode', 0x0045: 'Flash fired, red-eye reduction mode, return light not detected', 0x0047: 'Flash fired, red-eye reduction mode, return light detected', 0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode', 0x004d: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected', 0x004f: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected', 0x0059: 'Flash fired, auto mode, red-eye reduction mode', 0x005d: 'Flash fired, auto mode, return light not detected, red-eye reduction mode', 0x005f: '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 IptcFieldMap = { 0x78: 'caption', 0x6e: 'credit', 0x19: 'keywords', 0x37: 'dateCreated', 0x50: 'byline', 0x55: 'bylineTitle', 0x7a: 'captionWriter', 0x69: 'headline', 0x74: 'copyright', 0x0f: 'category', } function imageHasData(img) { return !!img.exifdata } function base64ToArrayBuffer(base64) { base64 = base64.replace(/^data:([^;]+);base64,/gim, '') var binary = atob(base64) var len = binary.length var buffer = new ArrayBuffer(len) var view = new Uint8Array(buffer) for (var i = 0; i < len; i++) { view[i] = binary.charCodeAt(i) } return buffer } function objectURLToBlob(url, callback) { var http = new XMLHttpRequest() http.open('GET', url, true) http.responseType = 'blob' http.onload = function() { if (this.status == 200 || this.status === 0) { callback(this.response) } } http.send() } function getImageData(img, callback) { function handleBinaryFile(binFile) { var data = findEXIFinJPEG(binFile) img.exifdata = data || {} var iptcdata = findIPTCinJPEG(binFile) img.iptcdata = iptcdata || {} if (callback) { callback.call(img) } } if (img.src) { if (/^data:/i.test(img.src)) { // Data URI var arrayBuffer = base64ToArrayBuffer(img.src) handleBinaryFile(arrayBuffer) } else if (/^blob:/i.test(img.src)) { // Object URL var fileReader = new FileReader() fileReader.onload = function(e) { handleBinaryFile(e.target.result) } objectURLToBlob(img.src, function(blob) { fileReader.readAsArrayBuffer(blob) }) } else { var http = new XMLHttpRequest() http.onload = function() { if (this.status == 200 || this.status === 0) { handleBinaryFile(http.response) } else { throw 'Could not load image' } http = null } http.open('GET', img.src, true) http.responseType = 'arraybuffer' http.send(null) } } else if (self.FileReader && (img instanceof self.Blob || img instanceof self.File)) { fileReader = new FileReader() fileReader.onload = function(e) { // eslint-disable-next-line if (debug) console.log('Got file of length ' + e.target.result.byteLength) handleBinaryFile(e.target.result) } fileReader.readAsArrayBuffer(img) } } function findEXIFinJPEG(file) { var dataView = new DataView(file) // eslint-disable-next-line if (debug) console.log('Got file of length ' + file.byteLength) if (dataView.getUint8(0) != 0xff || dataView.getUint8(1) != 0xd8) { // eslint-disable-next-line if (debug) console.log('Not a valid JPEG') return false // not a valid jpeg } var offset = 2, length = file.byteLength, marker while (offset < length) { if (dataView.getUint8(offset) != 0xff) { // eslint-disable-next-line if (debug) console.log('Not a valid marker at offset ' + offset + ', found: ' + dataView.getUint8(offset)) return false // not a valid marker, something is wrong } marker = dataView.getUint8(offset + 1) // eslint-disable-next-line if (debug) console.log(marker) // we could implement handling for other markers here, // but we're only looking for 0xFFE1 for EXIF data if (marker == 225) { // eslint-disable-next-line if (debug) console.log('Found 0xFFE1 marker') return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2) // offset += 2 + file.getShortAt(offset+2, true); } else { offset += 2 + dataView.getUint16(offset + 2) } } } function findIPTCinJPEG(file) { var dataView = new DataView(file) // eslint-disable-next-line if (debug) console.log('Got file of length ' + file.byteLength) if (dataView.getUint8(0) != 0xff || dataView.getUint8(1) != 0xd8) { // eslint-disable-next-line if (debug) console.log('Not a valid JPEG') return false // not a valid jpeg } var offset = 2, length = file.byteLength var isFieldSegmentStart = function(dataView, offset) { return ( dataView.getUint8(offset) === 0x38 && dataView.getUint8(offset + 1) === 0x42 && dataView.getUint8(offset + 2) === 0x49 && dataView.getUint8(offset + 3) === 0x4d && dataView.getUint8(offset + 4) === 0x04 && dataView.getUint8(offset + 5) === 0x04 ) } while (offset < length) { if (isFieldSegmentStart(dataView, offset)) { // Get the length of the name header (which is padded to an even number of bytes) var nameHeaderLength = dataView.getUint8(offset + 7) if (nameHeaderLength % 2 !== 0) nameHeaderLength += 1 // Check for pre photoshop 6 format if (nameHeaderLength === 0) { // Always 4 nameHeaderLength = 4 } var startOffset = offset + 8 + nameHeaderLength var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength) return readIPTCData(file, startOffset, sectionLength) } // Not the marker, continue searching offset++ } } function readIPTCData(file, startOffset, sectionLength) { var dataView = new DataView(file) var data = {} var fieldValue, fieldName, dataSize, segmentType var segmentStartPos = startOffset while (segmentStartPos < startOffset + sectionLength) { if (dataView.getUint8(segmentStartPos) === 0x1c && dataView.getUint8(segmentStartPos + 1) === 0x02) { segmentType = dataView.getUint8(segmentStartPos + 2) if (segmentType in IptcFieldMap) { dataSize = dataView.getInt16(segmentStartPos + 3) fieldName = IptcFieldMap[segmentType] fieldValue = getStringFromDB(dataView, segmentStartPos + 5, dataSize) // Check if we already stored a value with this name if (data.hasOwnProperty(fieldName)) { // Value already stored with this name, create multivalue field if (data[fieldName] instanceof Array) { data[fieldName].push(fieldValue) } else { data[fieldName] = [data[fieldName], fieldValue] } } else { data[fieldName] = fieldValue } } } segmentStartPos++ } return data } function readTags(file, tiffStart, dirStart, strings, bigEnd) { var entries = file.getUint16(dirStart, !bigEnd), tags = {}, entryOffset, tag, i for (i = 0; i < entries; i++) { entryOffset = dirStart + i * 12 + 2 tag = strings[file.getUint16(entryOffset, !bigEnd)] // eslint-disable-next-line if (!tag && debug) console.log('Unknown tag: ' + file.getUint16(entryOffset, !bigEnd)) tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) } return tags } function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) { var type = file.getUint16(entryOffset + 2, !bigEnd), numValues = file.getUint32(entryOffset + 4, !bigEnd), valueOffset = file.getUint32(entryOffset + 8, !bigEnd) + tiffStart, offset, vals, val, n, numerator, denominator switch (type) { case 1: // byte, 8-bit unsigned int case 7: // undefined, 8-bit byte, value depending on field if (numValues == 1) { return file.getUint8(entryOffset + 8, !bigEnd) } else { offset = numValues > 4 ? valueOffset : entryOffset + 8 vals = [] for (n = 0; n < numValues; n++) { vals[n] = file.getUint8(offset + n) } return vals } case 2: // ascii, 8-bit byte offset = numValues > 4 ? valueOffset : entryOffset + 8 return getStringFromDB(file, offset, numValues - 1) case 3: // short, 16 bit int if (numValues == 1) { return file.getUint16(entryOffset + 8, !bigEnd) } else { offset = numValues > 2 ? valueOffset : entryOffset + 8 vals = [] for (n = 0; n < numValues; n++) { vals[n] = file.getUint16(offset + 2 * n, !bigEnd) } return vals } case 4: // long, 32 bit int if (numValues == 1) { return file.getUint32(entryOffset + 8, !bigEnd) } else { vals = [] for (n = 0; n < numValues; n++) { vals[n] = file.getUint32(valueOffset + 4 * n, !bigEnd) } return vals } case 5: // rational = two long values, first is numerator, second is denominator if (numValues == 1) { numerator = file.getUint32(valueOffset, !bigEnd) denominator = file.getUint32(valueOffset + 4, !bigEnd) val = new Number(numerator / denominator) val.numerator = numerator val.denominator = denominator return val } else { vals = [] for (n = 0; n < numValues; n++) { numerator = file.getUint32(valueOffset + 8 * n, !bigEnd) denominator = file.getUint32(valueOffset + 4 + 8 * n, !bigEnd) vals[n] = new Number(numerator / denominator) vals[n].numerator = numerator vals[n].denominator = denominator } return vals } case 9: // slong, 32 bit signed int if (numValues == 1) { return file.getInt32(entryOffset + 8, !bigEnd) } else { vals = [] for (n = 0; n < numValues; n++) { vals[n] = file.getInt32(valueOffset + 4 * n, !bigEnd) } return vals } case 10: // signed rational, two slongs, first is numerator, second is denominator if (numValues == 1) { return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset + 4, !bigEnd) } else { vals = [] for (n = 0; n < numValues; n++) { vals[n] = file.getInt32(valueOffset + 8 * n, !bigEnd) / file.getInt32(valueOffset + 4 + 8 * n, !bigEnd) } return vals } } } /** * Given an IFD (Image File Directory) start offset * returns an offset to next IFD or 0 if it's the last IFD. */ function getNextIFDOffset(dataView, dirStart, bigEnd) { //the first 2bytes means the number of directory entries contains in this IFD var entries = dataView.getUint16(dirStart, !bigEnd) // After last directory entry, there is a 4bytes of data, // it means an offset to next IFD. // If its value is '0x00000000', it means this is the last IFD and there is no linked IFD. return dataView.getUint32(dirStart + 2 + entries * 12, !bigEnd) // each entry is 12 bytes long } function readThumbnailImage(dataView, tiffStart, firstIFDOffset, bigEnd) { // get the IFD1 offset var IFD1OffsetPointer = getNextIFDOffset(dataView, tiffStart + firstIFDOffset, bigEnd) if (!IFD1OffsetPointer) { // console.log('******** IFD1Offset is empty, image thumb not found ********'); return {} } else if (IFD1OffsetPointer > dataView.byteLength) { // this should not happen // console.log('******** IFD1Offset is outside the bounds of the DataView ********'); return {} } // console.log('******* thumbnail IFD offset (IFD1) is: %s', IFD1OffsetPointer); var thumbTags = readTags(dataView, tiffStart, tiffStart + IFD1OffsetPointer, IFD1Tags, bigEnd) // EXIF 2.3 specification for JPEG format thumbnail // If the value of Compression(0x0103) Tag in IFD1 is '6', thumbnail image format is JPEG. // Most of Exif image uses JPEG format for thumbnail. In that case, you can get offset of thumbnail // by JpegIFOffset(0x0201) Tag in IFD1, size of thumbnail by JpegIFByteCount(0x0202) Tag. // Data format is ordinary JPEG format, starts from 0xFFD8 and ends by 0xFFD9. It seems that // JPEG format and 160x120pixels of size are recommended thumbnail format for Exif2.1 or later. if (thumbTags['Compression']) { // console.log('Thumbnail image found!'); switch (thumbTags['Compression']) { case 6: // console.log('Thumbnail image format is JPEG'); if (thumbTags.JpegIFOffset && thumbTags.JpegIFByteCount) { // extract the thumbnail var tOffset = tiffStart + thumbTags.JpegIFOffset var tLength = thumbTags.JpegIFByteCount thumbTags['blob'] = new Blob([new Uint8Array(dataView.buffer, tOffset, tLength)], { type: 'image/jpeg', }) } break case 1: console.warn('Thumbnail image format is TIFF, which is not implemented.') break default: console.warn("Unknown thumbnail image format '%s'", thumbTags['Compression']) } } else if (thumbTags['PhotometricInterpretation'] == 2) { console.warn('Thumbnail image format is RGB, which is not implemented.') } return thumbTags } function getStringFromDB(buffer, start, length) { var outstr = '' for (var n = start; n < start + length; n++) { outstr += String.fromCharCode(buffer.getUint8(n)) } return outstr } function readEXIFData(file, start) { if (getStringFromDB(file, start, 4) != 'Exif') { // eslint-disable-next-line if (debug) console.log('Not valid EXIF data! ' + getStringFromDB(file, start, 4)) return false } var bigEnd, tags, tag, exifData, gpsData, tiffOffset = start + 6 // test for TIFF validity and endianness if (file.getUint16(tiffOffset) == 0x4949) { bigEnd = false } else if (file.getUint16(tiffOffset) == 0x4d4d) { bigEnd = true } else { // eslint-disable-next-line if (debug) console.log('Not valid TIFF data! (no 0x4949 or 0x4D4D)') return false } if (file.getUint16(tiffOffset + 2, !bigEnd) != 0x002a) { // eslint-disable-next-line if (debug) console.log('Not valid TIFF data! (no 0x002A)') return false } var firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd) if (firstIFDOffset < 0x00000008) { // eslint-disable-next-line if (debug) console.log('Not valid TIFF data! (First offset less than 8)', file.getUint32(tiffOffset + 4, !bigEnd)) return false } tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd) if (tags.ExifIFDPointer) { exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd) for (tag in exifData) { switch (tag) { case 'LightSource': case 'Flash': case 'MeteringMode': case 'ExposureProgram': case 'SensingMethod': case 'SceneCaptureType': case 'SceneType': case 'CustomRendered': case 'WhiteBalance': case 'GainControl': case 'Contrast': case 'Saturation': case 'Sharpness': case 'SubjectDistanceRange': case 'FileSource': exifData[tag] = StringValues[tag][exifData[tag]] break case 'ExifVersion': case 'FlashpixVersion': exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]) break case 'ComponentsConfiguration': exifData[tag] = StringValues.Components[exifData[tag][0]] + StringValues.Components[exifData[tag][1]] + StringValues.Components[exifData[tag][2]] + StringValues.Components[exifData[tag][3]] break } tags[tag] = exifData[tag] } } if (tags.GPSInfoIFDPointer) { gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd) for (tag in gpsData) { switch (tag) { case 'GPSVersionID': gpsData[tag] = gpsData[tag][0] + '.' + gpsData[tag][1] + '.' + gpsData[tag][2] + '.' + gpsData[tag][3] break } tags[tag] = gpsData[tag] } } // extract thumbnail tags['thumbnail'] = readThumbnailImage(file, tiffOffset, firstIFDOffset, bigEnd) return tags } const EXIF = { getData: function(img, callback) { if (((self.Image && img instanceof self.Image) || (self.HTMLImageElement && img instanceof self.HTMLImageElement)) && !img.complete) return false if (!imageHasData(img)) { getImageData(img, callback) } else { if (callback) { callback.call(img) } } return true }, getTag: function(img, tag) { if (!imageHasData(img)) return return img.exifdata[tag] }, } export default EXIF