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.
 
 
 
 
 
 

260 lines
8.5 KiB

  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 metadata 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, Promise, DataView, Uint8Array, ArrayBuffer */
  16. ;(function (factory) {
  17. 'use strict'
  18. if (typeof define === 'function' && define.amd) {
  19. // Register as an anonymous AMD module:
  20. define(['jquery/fileUploader/vendor/blueimp-load-image/js/load-image'], factory)
  21. } else if (typeof module === 'object' && module.exports) {
  22. factory(require('jquery/fileUploader/vendor/blueimp-load-image/js/load-image'))
  23. } else {
  24. // Browser globals:
  25. factory(window.loadImage)
  26. }
  27. })(function (loadImage) {
  28. 'use strict'
  29. var global = loadImage.global
  30. var originalTransform = loadImage.transform
  31. var blobSlice =
  32. global.Blob &&
  33. (Blob.prototype.slice ||
  34. Blob.prototype.webkitSlice ||
  35. Blob.prototype.mozSlice)
  36. var bufferSlice =
  37. (global.ArrayBuffer && ArrayBuffer.prototype.slice) ||
  38. function (begin, end) {
  39. // Polyfill for IE10, which does not support ArrayBuffer.slice
  40. // eslint-disable-next-line no-param-reassign
  41. end = end || this.byteLength - begin
  42. var arr1 = new Uint8Array(this, begin, end)
  43. var arr2 = new Uint8Array(end)
  44. arr2.set(arr1)
  45. return arr2.buffer
  46. }
  47. var metaDataParsers = {
  48. jpeg: {
  49. 0xffe1: [], // APP1 marker
  50. 0xffed: [] // APP13 marker
  51. }
  52. }
  53. /**
  54. * Parses image metadata and calls the callback with an object argument
  55. * with the following property:
  56. * - imageHead: The complete image head as ArrayBuffer
  57. * The options argument accepts an object and supports the following
  58. * properties:
  59. * - maxMetaDataSize: Defines the maximum number of bytes to parse.
  60. * - disableImageHead: Disables creating the imageHead property.
  61. *
  62. * @param {Blob} file Blob object
  63. * @param {Function} [callback] Callback function
  64. * @param {object} [options] Parsing options
  65. * @param {object} [data] Result data object
  66. * @returns {Promise<object>|undefined} Returns Promise if no callback given.
  67. */
  68. function parseMetaData(file, callback, options, data) {
  69. var that = this
  70. /**
  71. * Promise executor
  72. *
  73. * @param {Function} resolve Resolution function
  74. * @param {Function} reject Rejection function
  75. * @returns {undefined} Undefined
  76. */
  77. function executor(resolve, reject) {
  78. if (
  79. !(
  80. global.DataView &&
  81. blobSlice &&
  82. file &&
  83. file.size >= 12 &&
  84. file.type === 'image/jpeg'
  85. )
  86. ) {
  87. // Nothing to parse
  88. return resolve(data)
  89. }
  90. // 256 KiB should contain all EXIF/ICC/IPTC segments:
  91. var maxMetaDataSize = options.maxMetaDataSize || 262144
  92. if (
  93. !loadImage.readFile(
  94. blobSlice.call(file, 0, maxMetaDataSize),
  95. function (buffer) {
  96. // Note on endianness:
  97. // Since the marker and length bytes in JPEG files are always
  98. // stored in big endian order, we can leave the endian parameter
  99. // of the DataView methods undefined, defaulting to big endian.
  100. var dataView = new DataView(buffer)
  101. // Check for the JPEG marker (0xffd8):
  102. if (dataView.getUint16(0) !== 0xffd8) {
  103. return reject(
  104. new Error('Invalid JPEG file: Missing JPEG marker.')
  105. )
  106. }
  107. var offset = 2
  108. var maxOffset = dataView.byteLength - 4
  109. var headLength = offset
  110. var markerBytes
  111. var markerLength
  112. var parsers
  113. var i
  114. while (offset < maxOffset) {
  115. markerBytes = dataView.getUint16(offset)
  116. // Search for APPn (0xffeN) and COM (0xfffe) markers,
  117. // which contain application-specific metadata like
  118. // Exif, ICC and IPTC data and text comments:
  119. if (
  120. (markerBytes >= 0xffe0 && markerBytes <= 0xffef) ||
  121. markerBytes === 0xfffe
  122. ) {
  123. // The marker bytes (2) are always followed by
  124. // the length bytes (2), indicating the length of the
  125. // marker segment, which includes the length bytes,
  126. // but not the marker bytes, so we add 2:
  127. markerLength = dataView.getUint16(offset + 2) + 2
  128. if (offset + markerLength > dataView.byteLength) {
  129. // eslint-disable-next-line no-console
  130. console.log('Invalid JPEG metadata: Invalid segment size.')
  131. break
  132. }
  133. parsers = metaDataParsers.jpeg[markerBytes]
  134. if (parsers && !options.disableMetaDataParsers) {
  135. for (i = 0; i < parsers.length; i += 1) {
  136. parsers[i].call(
  137. that,
  138. dataView,
  139. offset,
  140. markerLength,
  141. data,
  142. options
  143. )
  144. }
  145. }
  146. offset += markerLength
  147. headLength = offset
  148. } else {
  149. // Not an APPn or COM marker, probably safe to
  150. // assume that this is the end of the metadata
  151. break
  152. }
  153. }
  154. // Meta length must be longer than JPEG marker (2)
  155. // plus APPn marker (2), followed by length bytes (2):
  156. if (!options.disableImageHead && headLength > 6) {
  157. data.imageHead = bufferSlice.call(buffer, 0, headLength)
  158. }
  159. resolve(data)
  160. },
  161. reject,
  162. 'readAsArrayBuffer'
  163. )
  164. ) {
  165. // No support for the FileReader interface, nothing to parse
  166. resolve(data)
  167. }
  168. }
  169. options = options || {} // eslint-disable-line no-param-reassign
  170. if (global.Promise && typeof callback !== 'function') {
  171. options = callback || {} // eslint-disable-line no-param-reassign
  172. data = options // eslint-disable-line no-param-reassign
  173. return new Promise(executor)
  174. }
  175. data = data || {} // eslint-disable-line no-param-reassign
  176. return executor(callback, callback)
  177. }
  178. /**
  179. * Replaces the head of a JPEG Blob
  180. *
  181. * @param {Blob} blob Blob object
  182. * @param {ArrayBuffer} oldHead Old JPEG head
  183. * @param {ArrayBuffer} newHead New JPEG head
  184. * @returns {Blob} Combined Blob
  185. */
  186. function replaceJPEGHead(blob, oldHead, newHead) {
  187. if (!blob || !oldHead || !newHead) return null
  188. return new Blob([newHead, blobSlice.call(blob, oldHead.byteLength)], {
  189. type: 'image/jpeg'
  190. })
  191. }
  192. /**
  193. * Replaces the image head of a JPEG blob with the given one.
  194. * Returns a Promise or calls the callback with the new Blob.
  195. *
  196. * @param {Blob} blob Blob object
  197. * @param {ArrayBuffer} head New JPEG head
  198. * @param {Function} [callback] Callback function
  199. * @returns {Promise<Blob|null>|undefined} Combined Blob
  200. */
  201. function replaceHead(blob, head, callback) {
  202. var options = { maxMetaDataSize: 256, disableMetaDataParsers: true }
  203. if (!callback && global.Promise) {
  204. return parseMetaData(blob, options).then(function (data) {
  205. return replaceJPEGHead(blob, data.imageHead, head)
  206. })
  207. }
  208. parseMetaData(
  209. blob,
  210. function (data) {
  211. callback(replaceJPEGHead(blob, data.imageHead, head))
  212. },
  213. options
  214. )
  215. }
  216. loadImage.transform = function (img, options, callback, file, data) {
  217. if (loadImage.requiresMetaData(options)) {
  218. data = data || {} // eslint-disable-line no-param-reassign
  219. parseMetaData(
  220. file,
  221. function (result) {
  222. if (result !== data) {
  223. // eslint-disable-next-line no-console
  224. if (global.console) console.log(result)
  225. result = data // eslint-disable-line no-param-reassign
  226. }
  227. originalTransform.call(
  228. loadImage,
  229. img,
  230. options,
  231. callback,
  232. file,
  233. result
  234. )
  235. },
  236. options,
  237. data
  238. )
  239. } else {
  240. originalTransform.apply(loadImage, arguments)
  241. }
  242. }
  243. loadImage.blobSlice = blobSlice
  244. loadImage.bufferSlice = bufferSlice
  245. loadImage.replaceHead = replaceHead
  246. loadImage.parseMetaData = parseMetaData
  247. loadImage.metaDataParsers = metaDataParsers
  248. })