You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

461 rivejä
13 KiB

  1. /*
  2. * JavaScript Load Image Exif Parser
  3. * https://github.com/blueimp/JavaScript-Load-Image
  4. *
  5. * Copyright 2013, Sebastian Tschan
  6. * https://blueimp.net
  7. *
  8. * Licensed under the MIT license:
  9. * https://opensource.org/licenses/MIT
  10. */
  11. /* global define, module, require, DataView */
  12. /* eslint-disable no-console */
  13. ;(function (factory) {
  14. 'use strict'
  15. if (typeof define === 'function' && define.amd) {
  16. // Register as an anonymous AMD module:
  17. define(['jquery/fileUploader/vendor/blueimp-load-image/js/load-image', 'jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta'], factory)
  18. } else if (typeof module === 'object' && module.exports) {
  19. factory(require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'), require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image-meta'))
  20. } else {
  21. // Browser globals:
  22. factory(window.loadImage)
  23. }
  24. })(function (loadImage) {
  25. 'use strict'
  26. /**
  27. * Exif tag map
  28. *
  29. * @name ExifMap
  30. * @class
  31. * @param {number|string} tagCode IFD tag code
  32. */
  33. function ExifMap(tagCode) {
  34. if (tagCode) {
  35. Object.defineProperty(this, 'map', {
  36. value: this.ifds[tagCode].map
  37. })
  38. Object.defineProperty(this, 'tags', {
  39. value: (this.tags && this.tags[tagCode]) || {}
  40. })
  41. }
  42. }
  43. ExifMap.prototype.map = {
  44. Orientation: 0x0112,
  45. Thumbnail: 'ifd1',
  46. Blob: 0x0201, // Alias for JPEGInterchangeFormat
  47. Exif: 0x8769,
  48. GPSInfo: 0x8825,
  49. Interoperability: 0xa005
  50. }
  51. ExifMap.prototype.ifds = {
  52. ifd1: { name: 'Thumbnail', map: ExifMap.prototype.map },
  53. 0x8769: { name: 'Exif', map: {} },
  54. 0x8825: { name: 'GPSInfo', map: {} },
  55. 0xa005: { name: 'Interoperability', map: {} }
  56. }
  57. /**
  58. * Retrieves exif tag value
  59. *
  60. * @param {number|string} id Exif tag code or name
  61. * @returns {object} Exif tag value
  62. */
  63. ExifMap.prototype.get = function (id) {
  64. return this[id] || this[this.map[id]]
  65. }
  66. /**
  67. * Returns the Exif Thumbnail data as Blob.
  68. *
  69. * @param {DataView} dataView Data view interface
  70. * @param {number} offset Thumbnail data offset
  71. * @param {number} length Thumbnail data length
  72. * @returns {undefined|Blob} Returns the Thumbnail Blob or undefined
  73. */
  74. function getExifThumbnail(dataView, offset, length) {
  75. if (!length) return
  76. if (offset + length > dataView.byteLength) {
  77. console.log('Invalid Exif data: Invalid thumbnail data.')
  78. return
  79. }
  80. return new Blob(
  81. [loadImage.bufferSlice.call(dataView.buffer, offset, offset + length)],
  82. {
  83. type: 'image/jpeg'
  84. }
  85. )
  86. }
  87. var ExifTagTypes = {
  88. // byte, 8-bit unsigned int:
  89. 1: {
  90. getValue: function (dataView, dataOffset) {
  91. return dataView.getUint8(dataOffset)
  92. },
  93. size: 1
  94. },
  95. // ascii, 8-bit byte:
  96. 2: {
  97. getValue: function (dataView, dataOffset) {
  98. return String.fromCharCode(dataView.getUint8(dataOffset))
  99. },
  100. size: 1,
  101. ascii: true
  102. },
  103. // short, 16 bit int:
  104. 3: {
  105. getValue: function (dataView, dataOffset, littleEndian) {
  106. return dataView.getUint16(dataOffset, littleEndian)
  107. },
  108. size: 2
  109. },
  110. // long, 32 bit int:
  111. 4: {
  112. getValue: function (dataView, dataOffset, littleEndian) {
  113. return dataView.getUint32(dataOffset, littleEndian)
  114. },
  115. size: 4
  116. },
  117. // rational = two long values, first is numerator, second is denominator:
  118. 5: {
  119. getValue: function (dataView, dataOffset, littleEndian) {
  120. return (
  121. dataView.getUint32(dataOffset, littleEndian) /
  122. dataView.getUint32(dataOffset + 4, littleEndian)
  123. )
  124. },
  125. size: 8
  126. },
  127. // slong, 32 bit signed int:
  128. 9: {
  129. getValue: function (dataView, dataOffset, littleEndian) {
  130. return dataView.getInt32(dataOffset, littleEndian)
  131. },
  132. size: 4
  133. },
  134. // srational, two slongs, first is numerator, second is denominator:
  135. 10: {
  136. getValue: function (dataView, dataOffset, littleEndian) {
  137. return (
  138. dataView.getInt32(dataOffset, littleEndian) /
  139. dataView.getInt32(dataOffset + 4, littleEndian)
  140. )
  141. },
  142. size: 8
  143. }
  144. }
  145. // undefined, 8-bit byte, value depending on field:
  146. ExifTagTypes[7] = ExifTagTypes[1]
  147. /**
  148. * Returns Exif tag value.
  149. *
  150. * @param {DataView} dataView Data view interface
  151. * @param {number} tiffOffset TIFF offset
  152. * @param {number} offset Tag offset
  153. * @param {number} type Tag type
  154. * @param {number} length Tag length
  155. * @param {boolean} littleEndian Little endian encoding
  156. * @returns {object} Tag value
  157. */
  158. function getExifValue(
  159. dataView,
  160. tiffOffset,
  161. offset,
  162. type,
  163. length,
  164. littleEndian
  165. ) {
  166. var tagType = ExifTagTypes[type]
  167. var tagSize
  168. var dataOffset
  169. var values
  170. var i
  171. var str
  172. var c
  173. if (!tagType) {
  174. console.log('Invalid Exif data: Invalid tag type.')
  175. return
  176. }
  177. tagSize = tagType.size * length
  178. // Determine if the value is contained in the dataOffset bytes,
  179. // or if the value at the dataOffset is a pointer to the actual data:
  180. dataOffset =
  181. tagSize > 4
  182. ? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
  183. : offset + 8
  184. if (dataOffset + tagSize > dataView.byteLength) {
  185. console.log('Invalid Exif data: Invalid data offset.')
  186. return
  187. }
  188. if (length === 1) {
  189. return tagType.getValue(dataView, dataOffset, littleEndian)
  190. }
  191. values = []
  192. for (i = 0; i < length; i += 1) {
  193. values[i] = tagType.getValue(
  194. dataView,
  195. dataOffset + i * tagType.size,
  196. littleEndian
  197. )
  198. }
  199. if (tagType.ascii) {
  200. str = ''
  201. // Concatenate the chars:
  202. for (i = 0; i < values.length; i += 1) {
  203. c = values[i]
  204. // Ignore the terminating NULL byte(s):
  205. if (c === '\u0000') {
  206. break
  207. }
  208. str += c
  209. }
  210. return str
  211. }
  212. return values
  213. }
  214. /**
  215. * Determines if the given tag should be included.
  216. *
  217. * @param {object} includeTags Map of tags to include
  218. * @param {object} excludeTags Map of tags to exclude
  219. * @param {number|string} tagCode Tag code to check
  220. * @returns {boolean} True if the tag should be included
  221. */
  222. function shouldIncludeTag(includeTags, excludeTags, tagCode) {
  223. return (
  224. (!includeTags || includeTags[tagCode]) &&
  225. (!excludeTags || excludeTags[tagCode] !== true)
  226. )
  227. }
  228. /**
  229. * Parses Exif tags.
  230. *
  231. * @param {DataView} dataView Data view interface
  232. * @param {number} tiffOffset TIFF offset
  233. * @param {number} dirOffset Directory offset
  234. * @param {boolean} littleEndian Little endian encoding
  235. * @param {ExifMap} tags Map to store parsed exif tags
  236. * @param {ExifMap} tagOffsets Map to store parsed exif tag offsets
  237. * @param {object} includeTags Map of tags to include
  238. * @param {object} excludeTags Map of tags to exclude
  239. * @returns {number} Next directory offset
  240. */
  241. function parseExifTags(
  242. dataView,
  243. tiffOffset,
  244. dirOffset,
  245. littleEndian,
  246. tags,
  247. tagOffsets,
  248. includeTags,
  249. excludeTags
  250. ) {
  251. var tagsNumber, dirEndOffset, i, tagOffset, tagNumber, tagValue
  252. if (dirOffset + 6 > dataView.byteLength) {
  253. console.log('Invalid Exif data: Invalid directory offset.')
  254. return
  255. }
  256. tagsNumber = dataView.getUint16(dirOffset, littleEndian)
  257. dirEndOffset = dirOffset + 2 + 12 * tagsNumber
  258. if (dirEndOffset + 4 > dataView.byteLength) {
  259. console.log('Invalid Exif data: Invalid directory size.')
  260. return
  261. }
  262. for (i = 0; i < tagsNumber; i += 1) {
  263. tagOffset = dirOffset + 2 + 12 * i
  264. tagNumber = dataView.getUint16(tagOffset, littleEndian)
  265. if (!shouldIncludeTag(includeTags, excludeTags, tagNumber)) continue
  266. tagValue = getExifValue(
  267. dataView,
  268. tiffOffset,
  269. tagOffset,
  270. dataView.getUint16(tagOffset + 2, littleEndian), // tag type
  271. dataView.getUint32(tagOffset + 4, littleEndian), // tag length
  272. littleEndian
  273. )
  274. tags[tagNumber] = tagValue
  275. if (tagOffsets) {
  276. tagOffsets[tagNumber] = tagOffset
  277. }
  278. }
  279. // Return the offset to the next directory:
  280. return dataView.getUint32(dirEndOffset, littleEndian)
  281. }
  282. /**
  283. * Parses tags in a given IFD (Image File Directory).
  284. *
  285. * @param {object} data Data object to store exif tags and offsets
  286. * @param {number|string} tagCode IFD tag code
  287. * @param {DataView} dataView Data view interface
  288. * @param {number} tiffOffset TIFF offset
  289. * @param {boolean} littleEndian Little endian encoding
  290. * @param {object} includeTags Map of tags to include
  291. * @param {object} excludeTags Map of tags to exclude
  292. */
  293. function parseExifIFD(
  294. data,
  295. tagCode,
  296. dataView,
  297. tiffOffset,
  298. littleEndian,
  299. includeTags,
  300. excludeTags
  301. ) {
  302. var dirOffset = data.exif[tagCode]
  303. if (dirOffset) {
  304. data.exif[tagCode] = new ExifMap(tagCode)
  305. if (data.exifOffsets) {
  306. data.exifOffsets[tagCode] = new ExifMap(tagCode)
  307. }
  308. parseExifTags(
  309. dataView,
  310. tiffOffset,
  311. tiffOffset + dirOffset,
  312. littleEndian,
  313. data.exif[tagCode],
  314. data.exifOffsets && data.exifOffsets[tagCode],
  315. includeTags && includeTags[tagCode],
  316. excludeTags && excludeTags[tagCode]
  317. )
  318. }
  319. }
  320. loadImage.parseExifData = function (dataView, offset, length, data, options) {
  321. if (options.disableExif) {
  322. return
  323. }
  324. var includeTags = options.includeExifTags
  325. var excludeTags = options.excludeExifTags || {
  326. 0x8769: {
  327. // ExifIFDPointer
  328. 0x927c: true // MakerNote
  329. }
  330. }
  331. var tiffOffset = offset + 10
  332. var littleEndian
  333. var dirOffset
  334. var thumbnailIFD
  335. // Check for the ASCII code for "Exif" (0x45786966):
  336. if (dataView.getUint32(offset + 4) !== 0x45786966) {
  337. // No Exif data, might be XMP data instead
  338. return
  339. }
  340. if (tiffOffset + 8 > dataView.byteLength) {
  341. console.log('Invalid Exif data: Invalid segment size.')
  342. return
  343. }
  344. // Check for the two null bytes:
  345. if (dataView.getUint16(offset + 8) !== 0x0000) {
  346. console.log('Invalid Exif data: Missing byte alignment offset.')
  347. return
  348. }
  349. // Check the byte alignment:
  350. switch (dataView.getUint16(tiffOffset)) {
  351. case 0x4949:
  352. littleEndian = true
  353. break
  354. case 0x4d4d:
  355. littleEndian = false
  356. break
  357. default:
  358. console.log('Invalid Exif data: Invalid byte alignment marker.')
  359. return
  360. }
  361. // Check for the TIFF tag marker (0x002A):
  362. if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) {
  363. console.log('Invalid Exif data: Missing TIFF marker.')
  364. return
  365. }
  366. // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
  367. dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
  368. // Create the exif object to store the tags:
  369. data.exif = new ExifMap()
  370. if (!options.disableExifOffsets) {
  371. data.exifOffsets = new ExifMap()
  372. data.exifTiffOffset = tiffOffset
  373. data.exifLittleEndian = littleEndian
  374. }
  375. // Parse the tags of the main image directory (IFD0) and retrieve the
  376. // offset to the next directory (IFD1), usually the thumbnail directory:
  377. dirOffset = parseExifTags(
  378. dataView,
  379. tiffOffset,
  380. tiffOffset + dirOffset,
  381. littleEndian,
  382. data.exif,
  383. data.exifOffsets,
  384. includeTags,
  385. excludeTags
  386. )
  387. if (dirOffset && shouldIncludeTag(includeTags, excludeTags, 'ifd1')) {
  388. data.exif.ifd1 = dirOffset
  389. if (data.exifOffsets) {
  390. data.exifOffsets.ifd1 = tiffOffset + dirOffset
  391. }
  392. }
  393. Object.keys(data.exif.ifds).forEach(function (tagCode) {
  394. parseExifIFD(
  395. data,
  396. tagCode,
  397. dataView,
  398. tiffOffset,
  399. littleEndian,
  400. includeTags,
  401. excludeTags
  402. )
  403. })
  404. thumbnailIFD = data.exif.ifd1
  405. // Check for JPEG Thumbnail offset and data length:
  406. if (thumbnailIFD && thumbnailIFD[0x0201]) {
  407. thumbnailIFD[0x0201] = getExifThumbnail(
  408. dataView,
  409. tiffOffset + thumbnailIFD[0x0201],
  410. thumbnailIFD[0x0202] // Thumbnail data length
  411. )
  412. }
  413. }
  414. // Registers the Exif parser for the APP1 JPEG metadata segment:
  415. loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
  416. loadImage.exifWriters = {
  417. // Orientation writer:
  418. 0x0112: function (buffer, data, value) {
  419. var orientationOffset = data.exifOffsets[0x0112]
  420. if (!orientationOffset) return buffer
  421. var view = new DataView(buffer, orientationOffset + 8, 2)
  422. view.setUint16(0, value, data.exifLittleEndian)
  423. return buffer
  424. }
  425. }
  426. loadImage.writeExifData = function (buffer, data, id, value) {
  427. loadImage.exifWriters[data.exif.map[id]](buffer, data, value)
  428. }
  429. loadImage.ExifMap = ExifMap
  430. // Adds the following properties to the parseMetaData callback data:
  431. // - exif: The parsed Exif tags
  432. // - exifOffsets: The parsed Exif tag offsets
  433. // - exifTiffOffset: TIFF header offset (used for offset pointers)
  434. // - exifLittleEndian: little endian order if true, big endian if false
  435. // Adds the following options to the parseMetaData method:
  436. // - disableExif: Disables Exif parsing when true.
  437. // - disableExifOffsets: Disables storing Exif tag offsets when true.
  438. // - includeExifTags: A map of Exif tags to include for parsing.
  439. // - excludeExifTags: A map of Exif tags to exclude from parsing.
  440. })