load-image-exif.js 8.8 KB

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