load-image-meta.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. /*
  2. * JavaScript Load Image Meta
  3. * https://github.com/blueimp/JavaScript-Load-Image
  4. *
  5. * Copyright 2013, Sebastian Tschan
  6. * https://blueimp.net
  7. *
  8. * Image meta data handling implementation
  9. * based on the help and contribution of
  10. * Achim Stöhr.
  11. *
  12. * Licensed under the MIT license:
  13. * https://opensource.org/licenses/MIT
  14. */
  15. /* global define, module, require, DataView, Uint8Array */
  16. ;(function(factory) {
  17. 'use strict'
  18. if (typeof define === 'function' && define.amd) {
  19. // Register as an anonymous AMD module:
  20. define(['./load-image'], factory)
  21. } else if (typeof module === 'object' && module.exports) {
  22. factory(require('./load-image'))
  23. } else {
  24. // Browser globals:
  25. factory(window.loadImage)
  26. }
  27. })(function(loadImage) {
  28. 'use strict'
  29. var hasblobSlice =
  30. typeof Blob !== 'undefined' &&
  31. (Blob.prototype.slice ||
  32. Blob.prototype.webkitSlice ||
  33. Blob.prototype.mozSlice)
  34. loadImage.blobSlice =
  35. hasblobSlice &&
  36. function() {
  37. var slice = this.slice || this.webkitSlice || this.mozSlice
  38. return slice.apply(this, arguments)
  39. }
  40. loadImage.metaDataParsers = {
  41. jpeg: {
  42. 0xffe1: [], // APP1 marker
  43. 0xffed: [] // APP13 marker
  44. }
  45. }
  46. // Parses image meta data and calls the callback with an object argument
  47. // with the following properties:
  48. // * imageHead: The complete image head as ArrayBuffer (Uint8Array for IE10)
  49. // The options argument accepts an object and supports the following
  50. // properties:
  51. // * maxMetaDataSize: Defines the maximum number of bytes to parse.
  52. // * disableImageHead: Disables creating the imageHead property.
  53. loadImage.parseMetaData = function(file, callback, options, data) {
  54. // eslint-disable-next-line no-param-reassign
  55. options = options || {}
  56. // eslint-disable-next-line no-param-reassign
  57. data = data || {}
  58. var that = this
  59. // 256 KiB should contain all EXIF/ICC/IPTC segments:
  60. var maxMetaDataSize = options.maxMetaDataSize || 262144
  61. var noMetaData = !(
  62. typeof DataView !== 'undefined' &&
  63. file &&
  64. file.size >= 12 &&
  65. file.type === 'image/jpeg' &&
  66. loadImage.blobSlice
  67. )
  68. if (
  69. noMetaData ||
  70. !loadImage.readFile(
  71. loadImage.blobSlice.call(file, 0, maxMetaDataSize),
  72. function(e) {
  73. if (e.target.error) {
  74. // FileReader error
  75. // eslint-disable-next-line no-console
  76. console.log(e.target.error)
  77. callback(data)
  78. return
  79. }
  80. // Note on endianness:
  81. // Since the marker and length bytes in JPEG files are always
  82. // stored in big endian order, we can leave the endian parameter
  83. // of the DataView methods undefined, defaulting to big endian.
  84. var buffer = e.target.result
  85. var dataView = new DataView(buffer)
  86. var offset = 2
  87. var maxOffset = dataView.byteLength - 4
  88. var headLength = offset
  89. var markerBytes
  90. var markerLength
  91. var parsers
  92. var i
  93. // Check for the JPEG marker (0xffd8):
  94. if (dataView.getUint16(0) === 0xffd8) {
  95. while (offset < maxOffset) {
  96. markerBytes = dataView.getUint16(offset)
  97. // Search for APPn (0xffeN) and COM (0xfffe) markers,
  98. // which contain application-specific meta-data like
  99. // Exif, ICC and IPTC data and text comments:
  100. if (
  101. (markerBytes >= 0xffe0 && markerBytes <= 0xffef) ||
  102. markerBytes === 0xfffe
  103. ) {
  104. // The marker bytes (2) are always followed by
  105. // the length bytes (2), indicating the length of the
  106. // marker segment, which includes the length bytes,
  107. // but not the marker bytes, so we add 2:
  108. markerLength = dataView.getUint16(offset + 2) + 2
  109. if (offset + markerLength > dataView.byteLength) {
  110. // eslint-disable-next-line no-console
  111. console.log('Invalid meta data: Invalid segment size.')
  112. break
  113. }
  114. parsers = loadImage.metaDataParsers.jpeg[markerBytes]
  115. if (parsers) {
  116. for (i = 0; i < parsers.length; i += 1) {
  117. parsers[i].call(
  118. that,
  119. dataView,
  120. offset,
  121. markerLength,
  122. data,
  123. options
  124. )
  125. }
  126. }
  127. offset += markerLength
  128. headLength = offset
  129. } else {
  130. // Not an APPn or COM marker, probably safe to
  131. // assume that this is the end of the meta data
  132. break
  133. }
  134. }
  135. // Meta length must be longer than JPEG marker (2)
  136. // plus APPn marker (2), followed by length bytes (2):
  137. if (!options.disableImageHead && headLength > 6) {
  138. if (buffer.slice) {
  139. data.imageHead = buffer.slice(0, headLength)
  140. } else {
  141. // Workaround for IE10, which does not yet
  142. // support ArrayBuffer.slice:
  143. data.imageHead = new Uint8Array(buffer).subarray(0, headLength)
  144. }
  145. }
  146. } else {
  147. // eslint-disable-next-line no-console
  148. console.log('Invalid JPEG file: Missing JPEG marker.')
  149. }
  150. callback(data)
  151. },
  152. 'readAsArrayBuffer'
  153. )
  154. ) {
  155. callback(data)
  156. }
  157. }
  158. // Determines if meta data should be loaded automatically:
  159. loadImage.hasMetaOption = function(options) {
  160. return options && options.meta
  161. }
  162. var originalTransform = loadImage.transform
  163. loadImage.transform = function(img, options, callback, file, data) {
  164. if (loadImage.hasMetaOption(options)) {
  165. loadImage.parseMetaData(
  166. file,
  167. function(data) {
  168. originalTransform.call(loadImage, img, options, callback, file, data)
  169. },
  170. options,
  171. data
  172. )
  173. } else {
  174. originalTransform.apply(loadImage, arguments)
  175. }
  176. }
  177. })