|
- /*
- * JavaScript Load Image Exif Parser
- * https://github.com/blueimp/JavaScript-Load-Image
- *
- * Copyright 2013, Sebastian Tschan
- * https://blueimp.net
- *
- * Licensed under the MIT license:
- * https://opensource.org/licenses/MIT
- */
-
- /* global define, module, require, DataView */
-
- /* eslint-disable no-console */
-
- ;(function (factory) {
- 'use strict'
- if (typeof define === 'function' && define.amd) {
- // Register as an anonymous AMD module:
- define(['jquery/fileUploader/vendor/blueimp-load-image/js/load-image', 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta'], factory)
- } else if (typeof module === 'object' && module.exports) {
- factory(require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'), require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta'))
- } else {
- // Browser globals:
- factory(window.loadImage)
- }
- })(function (loadImage) {
- 'use strict'
-
- /**
- * Exif tag map
- *
- * @name ExifMap
- * @class
- * @param {number|string} tagCode IFD tag code
- */
- function ExifMap(tagCode) {
- if (tagCode) {
- Object.defineProperty(this, 'map', {
- value: this.ifds[tagCode].map
- })
- Object.defineProperty(this, 'tags', {
- value: (this.tags && this.tags[tagCode]) || {}
- })
- }
- }
-
- ExifMap.prototype.map = {
- Orientation: 0x0112,
- Thumbnail: 'ifd1',
- Blob: 0x0201, // Alias for JPEGInterchangeFormat
- Exif: 0x8769,
- GPSInfo: 0x8825,
- Interoperability: 0xa005
- }
-
- ExifMap.prototype.ifds = {
- ifd1: { name: 'Thumbnail', map: ExifMap.prototype.map },
- 0x8769: { name: 'Exif', map: {} },
- 0x8825: { name: 'GPSInfo', map: {} },
- 0xa005: { name: 'Interoperability', map: {} }
- }
-
- /**
- * Retrieves exif tag value
- *
- * @param {number|string} id Exif tag code or name
- * @returns {object} Exif tag value
- */
- ExifMap.prototype.get = function (id) {
- return this[id] || this[this.map[id]]
- }
-
- /**
- * Returns the Exif Thumbnail data as Blob.
- *
- * @param {DataView} dataView Data view interface
- * @param {number} offset Thumbnail data offset
- * @param {number} length Thumbnail data length
- * @returns {undefined|Blob} Returns the Thumbnail Blob or undefined
- */
- function getExifThumbnail(dataView, offset, length) {
- if (!length) return
- if (offset + length > dataView.byteLength) {
- console.log('Invalid Exif data: Invalid thumbnail data.')
- return
- }
- return new Blob(
- [loadImage.bufferSlice.call(dataView.buffer, offset, offset + length)],
- {
- type: 'image/jpeg'
- }
- )
- }
-
- var ExifTagTypes = {
- // byte, 8-bit unsigned int:
- 1: {
- getValue: function (dataView, dataOffset) {
- return dataView.getUint8(dataOffset)
- },
- size: 1
- },
- // ascii, 8-bit byte:
- 2: {
- getValue: function (dataView, dataOffset) {
- return String.fromCharCode(dataView.getUint8(dataOffset))
- },
- size: 1,
- ascii: true
- },
- // short, 16 bit int:
- 3: {
- getValue: function (dataView, dataOffset, littleEndian) {
- return dataView.getUint16(dataOffset, littleEndian)
- },
- size: 2
- },
- // long, 32 bit int:
- 4: {
- getValue: function (dataView, dataOffset, littleEndian) {
- return dataView.getUint32(dataOffset, littleEndian)
- },
- size: 4
- },
- // rational = two long values, first is numerator, second is denominator:
- 5: {
- getValue: function (dataView, dataOffset, littleEndian) {
- return (
- dataView.getUint32(dataOffset, littleEndian) /
- dataView.getUint32(dataOffset + 4, littleEndian)
- )
- },
- size: 8
- },
- // slong, 32 bit signed int:
- 9: {
- getValue: function (dataView, dataOffset, littleEndian) {
- return dataView.getInt32(dataOffset, littleEndian)
- },
- size: 4
- },
- // srational, two slongs, first is numerator, second is denominator:
- 10: {
- getValue: function (dataView, dataOffset, littleEndian) {
- return (
- dataView.getInt32(dataOffset, littleEndian) /
- dataView.getInt32(dataOffset + 4, littleEndian)
- )
- },
- size: 8
- }
- }
- // undefined, 8-bit byte, value depending on field:
- ExifTagTypes[7] = ExifTagTypes[1]
-
- /**
- * Returns Exif tag value.
- *
- * @param {DataView} dataView Data view interface
- * @param {number} tiffOffset TIFF offset
- * @param {number} offset Tag offset
- * @param {number} type Tag type
- * @param {number} length Tag length
- * @param {boolean} littleEndian Little endian encoding
- * @returns {object} Tag value
- */
- function getExifValue(
- dataView,
- tiffOffset,
- offset,
- type,
- length,
- littleEndian
- ) {
- var tagType = ExifTagTypes[type]
- var tagSize
- var dataOffset
- var values
- var i
- var str
- var c
- if (!tagType) {
- console.log('Invalid Exif data: Invalid tag type.')
- return
- }
- tagSize = tagType.size * length
- // Determine if the value is contained in the dataOffset bytes,
- // or if the value at the dataOffset is a pointer to the actual data:
- dataOffset =
- tagSize > 4
- ? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
- : offset + 8
- if (dataOffset + tagSize > dataView.byteLength) {
- console.log('Invalid Exif data: Invalid data offset.')
- return
- }
- if (length === 1) {
- return tagType.getValue(dataView, dataOffset, littleEndian)
- }
- values = []
- for (i = 0; i < length; i += 1) {
- values[i] = tagType.getValue(
- dataView,
- dataOffset + i * tagType.size,
- littleEndian
- )
- }
- if (tagType.ascii) {
- str = ''
- // Concatenate the chars:
- for (i = 0; i < values.length; i += 1) {
- c = values[i]
- // Ignore the terminating NULL byte(s):
- if (c === '\u0000') {
- break
- }
- str += c
- }
- return str
- }
- return values
- }
-
- /**
- * Determines if the given tag should be included.
- *
- * @param {object} includeTags Map of tags to include
- * @param {object} excludeTags Map of tags to exclude
- * @param {number|string} tagCode Tag code to check
- * @returns {boolean} True if the tag should be included
- */
- function shouldIncludeTag(includeTags, excludeTags, tagCode) {
- return (
- (!includeTags || includeTags[tagCode]) &&
- (!excludeTags || excludeTags[tagCode] !== true)
- )
- }
-
- /**
- * Parses Exif tags.
- *
- * @param {DataView} dataView Data view interface
- * @param {number} tiffOffset TIFF offset
- * @param {number} dirOffset Directory offset
- * @param {boolean} littleEndian Little endian encoding
- * @param {ExifMap} tags Map to store parsed exif tags
- * @param {ExifMap} tagOffsets Map to store parsed exif tag offsets
- * @param {object} includeTags Map of tags to include
- * @param {object} excludeTags Map of tags to exclude
- * @returns {number} Next directory offset
- */
- function parseExifTags(
- dataView,
- tiffOffset,
- dirOffset,
- littleEndian,
- tags,
- tagOffsets,
- includeTags,
- excludeTags
- ) {
- var tagsNumber, dirEndOffset, i, tagOffset, tagNumber, tagValue
- if (dirOffset + 6 > dataView.byteLength) {
- console.log('Invalid Exif data: Invalid directory offset.')
- return
- }
- tagsNumber = dataView.getUint16(dirOffset, littleEndian)
- dirEndOffset = dirOffset + 2 + 12 * tagsNumber
- if (dirEndOffset + 4 > dataView.byteLength) {
- console.log('Invalid Exif data: Invalid directory size.')
- return
- }
- for (i = 0; i < tagsNumber; i += 1) {
- tagOffset = dirOffset + 2 + 12 * i
- tagNumber = dataView.getUint16(tagOffset, littleEndian)
- if (!shouldIncludeTag(includeTags, excludeTags, tagNumber)) continue
- tagValue = getExifValue(
- dataView,
- tiffOffset,
- tagOffset,
- dataView.getUint16(tagOffset + 2, littleEndian), // tag type
- dataView.getUint32(tagOffset + 4, littleEndian), // tag length
- littleEndian
- )
- tags[tagNumber] = tagValue
- if (tagOffsets) {
- tagOffsets[tagNumber] = tagOffset
- }
- }
- // Return the offset to the next directory:
- return dataView.getUint32(dirEndOffset, littleEndian)
- }
-
- /**
- * Parses tags in a given IFD (Image File Directory).
- *
- * @param {object} data Data object to store exif tags and offsets
- * @param {number|string} tagCode IFD tag code
- * @param {DataView} dataView Data view interface
- * @param {number} tiffOffset TIFF offset
- * @param {boolean} littleEndian Little endian encoding
- * @param {object} includeTags Map of tags to include
- * @param {object} excludeTags Map of tags to exclude
- */
- function parseExifIFD(
- data,
- tagCode,
- dataView,
- tiffOffset,
- littleEndian,
- includeTags,
- excludeTags
- ) {
- var dirOffset = data.exif[tagCode]
- if (dirOffset) {
- data.exif[tagCode] = new ExifMap(tagCode)
- if (data.exifOffsets) {
- data.exifOffsets[tagCode] = new ExifMap(tagCode)
- }
- parseExifTags(
- dataView,
- tiffOffset,
- tiffOffset + dirOffset,
- littleEndian,
- data.exif[tagCode],
- data.exifOffsets && data.exifOffsets[tagCode],
- includeTags && includeTags[tagCode],
- excludeTags && excludeTags[tagCode]
- )
- }
- }
-
- loadImage.parseExifData = function (dataView, offset, length, data, options) {
- if (options.disableExif) {
- return
- }
- var includeTags = options.includeExifTags
- var excludeTags = options.excludeExifTags || {
- 0x8769: {
- // ExifIFDPointer
- 0x927c: true // MakerNote
- }
- }
- var tiffOffset = offset + 10
- var littleEndian
- var dirOffset
- var thumbnailIFD
- // Check for the ASCII code for "Exif" (0x45786966):
- if (dataView.getUint32(offset + 4) !== 0x45786966) {
- // No Exif data, might be XMP data instead
- return
- }
- if (tiffOffset + 8 > dataView.byteLength) {
- console.log('Invalid Exif data: Invalid segment size.')
- return
- }
- // Check for the two null bytes:
- if (dataView.getUint16(offset + 8) !== 0x0000) {
- console.log('Invalid Exif data: Missing byte alignment offset.')
- return
- }
- // Check the byte alignment:
- switch (dataView.getUint16(tiffOffset)) {
- case 0x4949:
- littleEndian = true
- break
- case 0x4d4d:
- littleEndian = false
- break
- default:
- console.log('Invalid Exif data: Invalid byte alignment marker.')
- return
- }
- // Check for the TIFF tag marker (0x002A):
- if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) {
- console.log('Invalid Exif data: Missing TIFF marker.')
- return
- }
- // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
- dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
- // Create the exif object to store the tags:
- data.exif = new ExifMap()
- if (!options.disableExifOffsets) {
- data.exifOffsets = new ExifMap()
- data.exifTiffOffset = tiffOffset
- data.exifLittleEndian = littleEndian
- }
- // Parse the tags of the main image directory (IFD0) and retrieve the
- // offset to the next directory (IFD1), usually the thumbnail directory:
- dirOffset = parseExifTags(
- dataView,
- tiffOffset,
- tiffOffset + dirOffset,
- littleEndian,
- data.exif,
- data.exifOffsets,
- includeTags,
- excludeTags
- )
- if (dirOffset && shouldIncludeTag(includeTags, excludeTags, 'ifd1')) {
- data.exif.ifd1 = dirOffset
- if (data.exifOffsets) {
- data.exifOffsets.ifd1 = tiffOffset + dirOffset
- }
- }
- Object.keys(data.exif.ifds).forEach(function (tagCode) {
- parseExifIFD(
- data,
- tagCode,
- dataView,
- tiffOffset,
- littleEndian,
- includeTags,
- excludeTags
- )
- })
- thumbnailIFD = data.exif.ifd1
- // Check for JPEG Thumbnail offset and data length:
- if (thumbnailIFD && thumbnailIFD[0x0201]) {
- thumbnailIFD[0x0201] = getExifThumbnail(
- dataView,
- tiffOffset + thumbnailIFD[0x0201],
- thumbnailIFD[0x0202] // Thumbnail data length
- )
- }
- }
-
- // Registers the Exif parser for the APP1 JPEG metadata segment:
- loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
-
- loadImage.exifWriters = {
- // Orientation writer:
- 0x0112: function (buffer, data, value) {
- var orientationOffset = data.exifOffsets[0x0112]
- if (!orientationOffset) return buffer
- var view = new DataView(buffer, orientationOffset + 8, 2)
- view.setUint16(0, value, data.exifLittleEndian)
- return buffer
- }
- }
-
- loadImage.writeExifData = function (buffer, data, id, value) {
- loadImage.exifWriters[data.exif.map[id]](buffer, data, value)
- }
-
- loadImage.ExifMap = ExifMap
-
- // Adds the following properties to the parseMetaData callback data:
- // - exif: The parsed Exif tags
- // - exifOffsets: The parsed Exif tag offsets
- // - exifTiffOffset: TIFF header offset (used for offset pointers)
- // - exifLittleEndian: little endian order if true, big endian if false
-
- // Adds the following options to the parseMetaData method:
- // - disableExif: Disables Exif parsing when true.
- // - disableExifOffsets: Disables storing Exif tag offsets when true.
- // - includeExifTags: A map of Exif tags to include for parsing.
- // - excludeExifTags: A map of Exif tags to exclude from parsing.
- })
|