Files
Chamilo/app/Resources/public/assets/blueimp-load-image/js/load-image-meta.js
2025-08-14 22:33:03 +02:00

260 lines
8.4 KiB
JavaScript

/*
* JavaScript Load Image Meta
* https://github.com/blueimp/JavaScript-Load-Image
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Image metadata handling implementation
* based on the help and contribution of
* Achim Stöhr.
*
* Licensed under the MIT license:
* https://opensource.org/licenses/MIT
*/
/* global define, module, require, Promise, DataView, Uint8Array, ArrayBuffer */
;(function (factory) {
'use strict'
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['./load-image'], factory)
} else if (typeof module === 'object' && module.exports) {
factory(require('./load-image'))
} else {
// Browser globals:
factory(window.loadImage)
}
})(function (loadImage) {
'use strict'
var global = loadImage.global
var originalTransform = loadImage.transform
var blobSlice =
global.Blob &&
(Blob.prototype.slice ||
Blob.prototype.webkitSlice ||
Blob.prototype.mozSlice)
var bufferSlice =
(global.ArrayBuffer && ArrayBuffer.prototype.slice) ||
function (begin, end) {
// Polyfill for IE10, which does not support ArrayBuffer.slice
// eslint-disable-next-line no-param-reassign
end = end || this.byteLength - begin
var arr1 = new Uint8Array(this, begin, end)
var arr2 = new Uint8Array(end)
arr2.set(arr1)
return arr2.buffer
}
var metaDataParsers = {
jpeg: {
0xffe1: [], // APP1 marker
0xffed: [] // APP13 marker
}
}
/**
* Parses image metadata and calls the callback with an object argument
* with the following property:
* - imageHead: The complete image head as ArrayBuffer
* The options argument accepts an object and supports the following
* properties:
* - maxMetaDataSize: Defines the maximum number of bytes to parse.
* - disableImageHead: Disables creating the imageHead property.
*
* @param {Blob} file Blob object
* @param {Function} [callback] Callback function
* @param {object} [options] Parsing options
* @param {object} [data] Result data object
* @returns {Promise<object>|undefined} Returns Promise if no callback given.
*/
function parseMetaData(file, callback, options, data) {
var that = this
/**
* Promise executor
*
* @param {Function} resolve Resolution function
* @param {Function} reject Rejection function
* @returns {undefined} Undefined
*/
function executor(resolve, reject) {
if (
!(
global.DataView &&
blobSlice &&
file &&
file.size >= 12 &&
file.type === 'image/jpeg'
)
) {
// Nothing to parse
return resolve(data)
}
// 256 KiB should contain all EXIF/ICC/IPTC segments:
var maxMetaDataSize = options.maxMetaDataSize || 262144
if (
!loadImage.readFile(
blobSlice.call(file, 0, maxMetaDataSize),
function (buffer) {
// Note on endianness:
// Since the marker and length bytes in JPEG files are always
// stored in big endian order, we can leave the endian parameter
// of the DataView methods undefined, defaulting to big endian.
var dataView = new DataView(buffer)
// Check for the JPEG marker (0xffd8):
if (dataView.getUint16(0) !== 0xffd8) {
return reject(
new Error('Invalid JPEG file: Missing JPEG marker.')
)
}
var offset = 2
var maxOffset = dataView.byteLength - 4
var headLength = offset
var markerBytes
var markerLength
var parsers
var i
while (offset < maxOffset) {
markerBytes = dataView.getUint16(offset)
// Search for APPn (0xffeN) and COM (0xfffe) markers,
// which contain application-specific metadata like
// Exif, ICC and IPTC data and text comments:
if (
(markerBytes >= 0xffe0 && markerBytes <= 0xffef) ||
markerBytes === 0xfffe
) {
// The marker bytes (2) are always followed by
// the length bytes (2), indicating the length of the
// marker segment, which includes the length bytes,
// but not the marker bytes, so we add 2:
markerLength = dataView.getUint16(offset + 2) + 2
if (offset + markerLength > dataView.byteLength) {
// eslint-disable-next-line no-console
console.log('Invalid JPEG metadata: Invalid segment size.')
break
}
parsers = metaDataParsers.jpeg[markerBytes]
if (parsers && !options.disableMetaDataParsers) {
for (i = 0; i < parsers.length; i += 1) {
parsers[i].call(
that,
dataView,
offset,
markerLength,
data,
options
)
}
}
offset += markerLength
headLength = offset
} else {
// Not an APPn or COM marker, probably safe to
// assume that this is the end of the metadata
break
}
}
// Meta length must be longer than JPEG marker (2)
// plus APPn marker (2), followed by length bytes (2):
if (!options.disableImageHead && headLength > 6) {
data.imageHead = bufferSlice.call(buffer, 0, headLength)
}
resolve(data)
},
reject,
'readAsArrayBuffer'
)
) {
// No support for the FileReader interface, nothing to parse
resolve(data)
}
}
options = options || {} // eslint-disable-line no-param-reassign
if (global.Promise && typeof callback !== 'function') {
options = callback || {} // eslint-disable-line no-param-reassign
data = options // eslint-disable-line no-param-reassign
return new Promise(executor)
}
data = data || {} // eslint-disable-line no-param-reassign
return executor(callback, callback)
}
/**
* Replaces the head of a JPEG Blob
*
* @param {Blob} blob Blob object
* @param {ArrayBuffer} oldHead Old JPEG head
* @param {ArrayBuffer} newHead New JPEG head
* @returns {Blob} Combined Blob
*/
function replaceJPEGHead(blob, oldHead, newHead) {
if (!blob || !oldHead || !newHead) return null
return new Blob([newHead, blobSlice.call(blob, oldHead.byteLength)], {
type: 'image/jpeg'
})
}
/**
* Replaces the image head of a JPEG blob with the given one.
* Returns a Promise or calls the callback with the new Blob.
*
* @param {Blob} blob Blob object
* @param {ArrayBuffer} head New JPEG head
* @param {Function} [callback] Callback function
* @returns {Promise<Blob|null>|undefined} Combined Blob
*/
function replaceHead(blob, head, callback) {
var options = { maxMetaDataSize: 256, disableMetaDataParsers: true }
if (!callback && global.Promise) {
return parseMetaData(blob, options).then(function (data) {
return replaceJPEGHead(blob, data.imageHead, head)
})
}
parseMetaData(
blob,
function (data) {
callback(replaceJPEGHead(blob, data.imageHead, head))
},
options
)
}
loadImage.transform = function (img, options, callback, file, data) {
if (loadImage.requiresMetaData(options)) {
data = data || {} // eslint-disable-line no-param-reassign
parseMetaData(
file,
function (result) {
if (result !== data) {
// eslint-disable-next-line no-console
if (global.console) console.log(result)
result = data // eslint-disable-line no-param-reassign
}
originalTransform.call(
loadImage,
img,
options,
callback,
file,
result
)
},
options,
data
)
} else {
originalTransform.apply(loadImage, arguments)
}
}
loadImage.blobSlice = blobSlice
loadImage.bufferSlice = bufferSlice
loadImage.replaceHead = replaceHead
loadImage.parseMetaData = parseMetaData
loadImage.metaDataParsers = metaDataParsers
})