load-image-exif.js 8.8 KB

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