load-image-exif.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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 */
  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(['./load-image', './load-image-meta'], factory)
  18. } else if (typeof module === 'object' && module.exports) {
  19. factory(require('./load-image'), require('./load-image-meta'))
  20. } else {
  21. // Browser globals:
  22. factory(window.loadImage)
  23. }
  24. })(function(loadImage) {
  25. 'use strict'
  26. loadImage.ExifMap = function() {
  27. return this
  28. }
  29. loadImage.ExifMap.prototype.map = {
  30. Orientation: 0x0112
  31. }
  32. loadImage.ExifMap.prototype.get = function(id) {
  33. return this[id] || this[this.map[id]]
  34. }
  35. loadImage.getExifThumbnail = function(dataView, offset, length) {
  36. if (!length || offset + length > dataView.byteLength) {
  37. console.log('Invalid Exif data: Invalid thumbnail data.')
  38. return
  39. }
  40. return loadImage.createObjectURL(
  41. new Blob([dataView.buffer.slice(offset, offset + length)])
  42. )
  43. }
  44. loadImage.exifTagTypes = {
  45. // byte, 8-bit unsigned int:
  46. 1: {
  47. getValue: function(dataView, dataOffset) {
  48. return dataView.getUint8(dataOffset)
  49. },
  50. size: 1
  51. },
  52. // ascii, 8-bit byte:
  53. 2: {
  54. getValue: function(dataView, dataOffset) {
  55. return String.fromCharCode(dataView.getUint8(dataOffset))
  56. },
  57. size: 1,
  58. ascii: true
  59. },
  60. // short, 16 bit int:
  61. 3: {
  62. getValue: function(dataView, dataOffset, littleEndian) {
  63. return dataView.getUint16(dataOffset, littleEndian)
  64. },
  65. size: 2
  66. },
  67. // long, 32 bit int:
  68. 4: {
  69. getValue: function(dataView, dataOffset, littleEndian) {
  70. return dataView.getUint32(dataOffset, littleEndian)
  71. },
  72. size: 4
  73. },
  74. // rational = two long values, first is numerator, second is denominator:
  75. 5: {
  76. getValue: function(dataView, dataOffset, littleEndian) {
  77. return (
  78. dataView.getUint32(dataOffset, littleEndian) /
  79. dataView.getUint32(dataOffset + 4, littleEndian)
  80. )
  81. },
  82. size: 8
  83. },
  84. // slong, 32 bit signed int:
  85. 9: {
  86. getValue: function(dataView, dataOffset, littleEndian) {
  87. return dataView.getInt32(dataOffset, littleEndian)
  88. },
  89. size: 4
  90. },
  91. // srational, two slongs, first is numerator, second is denominator:
  92. 10: {
  93. getValue: function(dataView, dataOffset, littleEndian) {
  94. return (
  95. dataView.getInt32(dataOffset, littleEndian) /
  96. dataView.getInt32(dataOffset + 4, littleEndian)
  97. )
  98. },
  99. size: 8
  100. }
  101. }
  102. // undefined, 8-bit byte, value depending on field:
  103. loadImage.exifTagTypes[7] = loadImage.exifTagTypes[1]
  104. loadImage.getExifValue = function(
  105. dataView,
  106. tiffOffset,
  107. offset,
  108. type,
  109. length,
  110. littleEndian
  111. ) {
  112. var tagType = loadImage.exifTagTypes[type]
  113. var tagSize
  114. var dataOffset
  115. var values
  116. var i
  117. var str
  118. var c
  119. if (!tagType) {
  120. console.log('Invalid Exif data: Invalid tag type.')
  121. return
  122. }
  123. tagSize = tagType.size * length
  124. // Determine if the value is contained in the dataOffset bytes,
  125. // or if the value at the dataOffset is a pointer to the actual data:
  126. dataOffset =
  127. tagSize > 4
  128. ? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
  129. : offset + 8
  130. if (dataOffset + tagSize > dataView.byteLength) {
  131. console.log('Invalid Exif data: Invalid data offset.')
  132. return
  133. }
  134. if (length === 1) {
  135. return tagType.getValue(dataView, dataOffset, littleEndian)
  136. }
  137. values = []
  138. for (i = 0; i < length; i += 1) {
  139. values[i] = tagType.getValue(
  140. dataView,
  141. dataOffset + i * tagType.size,
  142. littleEndian
  143. )
  144. }
  145. if (tagType.ascii) {
  146. str = ''
  147. // Concatenate the chars:
  148. for (i = 0; i < values.length; i += 1) {
  149. c = values[i]
  150. // Ignore the terminating NULL byte(s):
  151. if (c === '\u0000') {
  152. break
  153. }
  154. str += c
  155. }
  156. return str
  157. }
  158. return values
  159. }
  160. loadImage.parseExifTag = function(
  161. dataView,
  162. tiffOffset,
  163. offset,
  164. littleEndian,
  165. data
  166. ) {
  167. var tag = dataView.getUint16(offset, littleEndian)
  168. data.exif[tag] = loadImage.getExifValue(
  169. dataView,
  170. tiffOffset,
  171. offset,
  172. dataView.getUint16(offset + 2, littleEndian), // tag type
  173. dataView.getUint32(offset + 4, littleEndian), // tag length
  174. littleEndian
  175. )
  176. }
  177. loadImage.parseExifTags = function(
  178. dataView,
  179. tiffOffset,
  180. dirOffset,
  181. littleEndian,
  182. data
  183. ) {
  184. var tagsNumber, dirEndOffset, i
  185. if (dirOffset + 6 > dataView.byteLength) {
  186. console.log('Invalid Exif data: Invalid directory offset.')
  187. return
  188. }
  189. tagsNumber = dataView.getUint16(dirOffset, littleEndian)
  190. dirEndOffset = dirOffset + 2 + 12 * tagsNumber
  191. if (dirEndOffset + 4 > dataView.byteLength) {
  192. console.log('Invalid Exif data: Invalid directory size.')
  193. return
  194. }
  195. for (i = 0; i < tagsNumber; i += 1) {
  196. this.parseExifTag(
  197. dataView,
  198. tiffOffset,
  199. dirOffset + 2 + 12 * i, // tag offset
  200. littleEndian,
  201. data
  202. )
  203. }
  204. // Return the offset to the next directory:
  205. return dataView.getUint32(dirEndOffset, littleEndian)
  206. }
  207. loadImage.parseExifData = function(dataView, offset, length, data, options) {
  208. if (options.disableExif) {
  209. return
  210. }
  211. var tiffOffset = offset + 10
  212. var littleEndian
  213. var dirOffset
  214. var thumbnailData
  215. // Check for the ASCII code for "Exif" (0x45786966):
  216. if (dataView.getUint32(offset + 4) !== 0x45786966) {
  217. // No Exif data, might be XMP data instead
  218. return
  219. }
  220. if (tiffOffset + 8 > dataView.byteLength) {
  221. console.log('Invalid Exif data: Invalid segment size.')
  222. return
  223. }
  224. // Check for the two null bytes:
  225. if (dataView.getUint16(offset + 8) !== 0x0000) {
  226. console.log('Invalid Exif data: Missing byte alignment offset.')
  227. return
  228. }
  229. // Check the byte alignment:
  230. switch (dataView.getUint16(tiffOffset)) {
  231. case 0x4949:
  232. littleEndian = true
  233. break
  234. case 0x4d4d:
  235. littleEndian = false
  236. break
  237. default:
  238. console.log('Invalid Exif data: Invalid byte alignment marker.')
  239. return
  240. }
  241. // Check for the TIFF tag marker (0x002A):
  242. if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) {
  243. console.log('Invalid Exif data: Missing TIFF marker.')
  244. return
  245. }
  246. // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
  247. dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
  248. // Create the exif object to store the tags:
  249. data.exif = new loadImage.ExifMap()
  250. // Parse the tags of the main image directory and retrieve the
  251. // offset to the next directory, usually the thumbnail directory:
  252. dirOffset = loadImage.parseExifTags(
  253. dataView,
  254. tiffOffset,
  255. tiffOffset + dirOffset,
  256. littleEndian,
  257. data
  258. )
  259. if (dirOffset && !options.disableExifThumbnail) {
  260. thumbnailData = { exif: {} }
  261. dirOffset = loadImage.parseExifTags(
  262. dataView,
  263. tiffOffset,
  264. tiffOffset + dirOffset,
  265. littleEndian,
  266. thumbnailData
  267. )
  268. // Check for JPEG Thumbnail offset:
  269. if (thumbnailData.exif[0x0201]) {
  270. data.exif.Thumbnail = loadImage.getExifThumbnail(
  271. dataView,
  272. tiffOffset + thumbnailData.exif[0x0201],
  273. thumbnailData.exif[0x0202] // Thumbnail data length
  274. )
  275. }
  276. }
  277. // Check for Exif Sub IFD Pointer:
  278. if (data.exif[0x8769] && !options.disableExifSub) {
  279. loadImage.parseExifTags(
  280. dataView,
  281. tiffOffset,
  282. tiffOffset + data.exif[0x8769], // directory offset
  283. littleEndian,
  284. data
  285. )
  286. }
  287. // Check for GPS Info IFD Pointer:
  288. if (data.exif[0x8825] && !options.disableExifGps) {
  289. loadImage.parseExifTags(
  290. dataView,
  291. tiffOffset,
  292. tiffOffset + data.exif[0x8825], // directory offset
  293. littleEndian,
  294. data
  295. )
  296. }
  297. }
  298. // Registers the Exif parser for the APP1 JPEG meta data segment:
  299. loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
  300. // Adds the following properties to the parseMetaData callback data:
  301. // * exif: The exif tags, parsed by the parseExifData method
  302. // Adds the following options to the parseMetaData method:
  303. // * disableExif: Disables Exif parsing.
  304. // * disableExifThumbnail: Disables parsing of the Exif Thumbnail.
  305. // * disableExifSub: Disables parsing of the Exif Sub IFD.
  306. // * disableExifGps: Disables parsing of the Exif GPS Info IFD.
  307. })