',
+
+ options: {
+ classes: {},
+ disabled: false,
+
+ // Callbacks
+ create: null
+ },
+
+ _createWidget: function (options, element) {
+ element = $(element || this.defaultElement || this)[0];
+ this.element = $(element);
+ this.uuid = widgetUuid++;
+ this.eventNamespace = '.' + this.widgetName + this.uuid;
+
+ this.bindings = $();
+ this.hoverable = $();
+ this.focusable = $();
+ this.classesElementLookup = {};
+
+ if (element !== this) {
+ $.data(element, this.widgetFullName, this);
+ this._on(true, this.element, {
+ remove: function (event) {
+ if (event.target === element) {
+ this.destroy();
+ }
+ }
+ });
+ this.document = $(
+ element.style
+ ? // Element within the document
+ element.ownerDocument
+ : // Element is window or document
+ element.document || element
+ );
+ this.window = $(
+ this.document[0].defaultView || this.document[0].parentWindow
+ );
+ }
+
+ this.options = $.widget.extend(
+ {},
+ this.options,
+ this._getCreateOptions(),
+ options
+ );
+
+ this._create();
+
+ if (this.options.disabled) {
+ this._setOptionDisabled(this.options.disabled);
+ }
+
+ this._trigger('create', null, this._getCreateEventData());
+ this._init();
+ },
+
+ _getCreateOptions: function () {
+ return {};
+ },
+
+ _getCreateEventData: $.noop,
+
+ _create: $.noop,
+
+ _init: $.noop,
+
+ destroy: function () {
+ var that = this;
+
+ this._destroy();
+ $.each(this.classesElementLookup, function (key, value) {
+ that._removeClass(value, key);
+ });
+
+ // We can probably remove the unbind calls in 2.0
+ // all event bindings should go through this._on()
+ this.element.off(this.eventNamespace).removeData(this.widgetFullName);
+ this.widget().off(this.eventNamespace).removeAttr('aria-disabled');
+
+ // Clean up events and states
+ this.bindings.off(this.eventNamespace);
+ },
+
+ _destroy: $.noop,
+
+ widget: function () {
+ return this.element;
+ },
+
+ option: function (key, value) {
+ var options = key;
+ var parts;
+ var curOption;
+ var i;
+
+ if (arguments.length === 0) {
+ // Don't return a reference to the internal hash
+ return $.widget.extend({}, this.options);
+ }
+
+ if (typeof key === 'string') {
+ // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+ options = {};
+ parts = key.split('.');
+ key = parts.shift();
+ if (parts.length) {
+ curOption = options[key] = $.widget.extend({}, this.options[key]);
+ for (i = 0; i < parts.length - 1; i++) {
+ curOption[parts[i]] = curOption[parts[i]] || {};
+ curOption = curOption[parts[i]];
+ }
+ key = parts.pop();
+ if (arguments.length === 1) {
+ return curOption[key] === undefined ? null : curOption[key];
+ }
+ curOption[key] = value;
+ } else {
+ if (arguments.length === 1) {
+ return this.options[key] === undefined ? null : this.options[key];
+ }
+ options[key] = value;
+ }
+ }
+
+ this._setOptions(options);
+
+ return this;
+ },
+
+ _setOptions: function (options) {
+ var key;
+
+ for (key in options) {
+ this._setOption(key, options[key]);
+ }
+
+ return this;
+ },
+
+ _setOption: function (key, value) {
+ if (key === 'classes') {
+ this._setOptionClasses(value);
+ }
+
+ this.options[key] = value;
+
+ if (key === 'disabled') {
+ this._setOptionDisabled(value);
+ }
+
+ return this;
+ },
+
+ _setOptionClasses: function (value) {
+ var classKey, elements, currentElements;
+
+ for (classKey in value) {
+ currentElements = this.classesElementLookup[classKey];
+ if (
+ value[classKey] === this.options.classes[classKey] ||
+ !currentElements ||
+ !currentElements.length
+ ) {
+ continue;
+ }
+
+ // We are doing this to create a new jQuery object because the _removeClass() call
+ // on the next line is going to destroy the reference to the current elements being
+ // tracked. We need to save a copy of this collection so that we can add the new classes
+ // below.
+ elements = $(currentElements.get());
+ this._removeClass(currentElements, classKey);
+
+ // We don't use _addClass() here, because that uses this.options.classes
+ // for generating the string of classes. We want to use the value passed in from
+ // _setOption(), this is the new value of the classes option which was passed to
+ // _setOption(). We pass this value directly to _classes().
+ elements.addClass(
+ this._classes({
+ element: elements,
+ keys: classKey,
+ classes: value,
+ add: true
+ })
+ );
+ }
+ },
+
+ _setOptionDisabled: function (value) {
+ this._toggleClass(
+ this.widget(),
+ this.widgetFullName + '-disabled',
+ null,
+ !!value
+ );
+
+ // If the widget is becoming disabled, then nothing is interactive
+ if (value) {
+ this._removeClass(this.hoverable, null, 'ui-state-hover');
+ this._removeClass(this.focusable, null, 'ui-state-focus');
+ }
+ },
+
+ enable: function () {
+ return this._setOptions({ disabled: false });
+ },
+
+ disable: function () {
+ return this._setOptions({ disabled: true });
+ },
+
+ _classes: function (options) {
+ var full = [];
+ var that = this;
+
+ options = $.extend(
+ {
+ element: this.element,
+ classes: this.options.classes || {}
+ },
+ options
+ );
+
+ function bindRemoveEvent() {
+ options.element.each(function (_, element) {
+ var isTracked = $.map(that.classesElementLookup, function (elements) {
+ return elements;
+ }).some(function (elements) {
+ return elements.is(element);
+ });
+
+ if (!isTracked) {
+ that._on($(element), {
+ remove: '_untrackClassesElement'
+ });
+ }
+ });
+ }
+
+ function processClassString(classes, checkOption) {
+ var current, i;
+ for (i = 0; i < classes.length; i++) {
+ current = that.classesElementLookup[classes[i]] || $();
+ if (options.add) {
+ bindRemoveEvent();
+ current = $(
+ $.uniqueSort(current.get().concat(options.element.get()))
+ );
+ } else {
+ current = $(current.not(options.element).get());
+ }
+ that.classesElementLookup[classes[i]] = current;
+ full.push(classes[i]);
+ if (checkOption && options.classes[classes[i]]) {
+ full.push(options.classes[classes[i]]);
+ }
+ }
+ }
+
+ if (options.keys) {
+ processClassString(options.keys.match(/\S+/g) || [], true);
+ }
+ if (options.extra) {
+ processClassString(options.extra.match(/\S+/g) || []);
+ }
+
+ return full.join(' ');
+ },
+
+ _untrackClassesElement: function (event) {
+ var that = this;
+ $.each(that.classesElementLookup, function (key, value) {
+ if ($.inArray(event.target, value) !== -1) {
+ that.classesElementLookup[key] = $(value.not(event.target).get());
+ }
+ });
+
+ this._off($(event.target));
+ },
+
+ _removeClass: function (element, keys, extra) {
+ return this._toggleClass(element, keys, extra, false);
+ },
+
+ _addClass: function (element, keys, extra) {
+ return this._toggleClass(element, keys, extra, true);
+ },
+
+ _toggleClass: function (element, keys, extra, add) {
+ add = typeof add === 'boolean' ? add : extra;
+ var shift = typeof element === 'string' || element === null,
+ options = {
+ extra: shift ? keys : extra,
+ keys: shift ? element : keys,
+ element: shift ? this.element : element,
+ add: add
+ };
+ options.element.toggleClass(this._classes(options), add);
+ return this;
+ },
+
+ _on: function (suppressDisabledCheck, element, handlers) {
+ var delegateElement;
+ var instance = this;
+
+ // No suppressDisabledCheck flag, shuffle arguments
+ if (typeof suppressDisabledCheck !== 'boolean') {
+ handlers = element;
+ element = suppressDisabledCheck;
+ suppressDisabledCheck = false;
+ }
+
+ // No element argument, shuffle and use this.element
+ if (!handlers) {
+ handlers = element;
+ element = this.element;
+ delegateElement = this.widget();
+ } else {
+ element = delegateElement = $(element);
+ this.bindings = this.bindings.add(element);
+ }
+
+ $.each(handlers, function (event, handler) {
+ function handlerProxy() {
+ // Allow widgets to customize the disabled handling
+ // - disabled as an array instead of boolean
+ // - disabled class as method for disabling individual parts
+ if (
+ !suppressDisabledCheck &&
+ (instance.options.disabled === true ||
+ $(this).hasClass('ui-state-disabled'))
+ ) {
+ return;
+ }
+ return (typeof handler === 'string'
+ ? instance[handler]
+ : handler
+ ).apply(instance, arguments);
+ }
+
+ // Copy the guid so direct unbinding works
+ if (typeof handler !== 'string') {
+ handlerProxy.guid = handler.guid =
+ handler.guid || handlerProxy.guid || $.guid++;
+ }
+
+ var match = event.match(/^([\w:-]*)\s*(.*)$/);
+ var eventName = match[1] + instance.eventNamespace;
+ var selector = match[2];
+
+ if (selector) {
+ delegateElement.on(eventName, selector, handlerProxy);
+ } else {
+ element.on(eventName, handlerProxy);
+ }
+ });
+ },
+
+ _off: function (element, eventName) {
+ eventName =
+ (eventName || '').split(' ').join(this.eventNamespace + ' ') +
+ this.eventNamespace;
+ element.off(eventName);
+
+ // Clear the stack to avoid memory leaks (#10056)
+ this.bindings = $(this.bindings.not(element).get());
+ this.focusable = $(this.focusable.not(element).get());
+ this.hoverable = $(this.hoverable.not(element).get());
+ },
+
+ _delay: function (handler, delay) {
+ var instance = this;
+ function handlerProxy() {
+ return (typeof handler === 'string'
+ ? instance[handler]
+ : handler
+ ).apply(instance, arguments);
+ }
+ return setTimeout(handlerProxy, delay || 0);
+ },
+
+ _hoverable: function (element) {
+ this.hoverable = this.hoverable.add(element);
+ this._on(element, {
+ mouseenter: function (event) {
+ this._addClass($(event.currentTarget), null, 'ui-state-hover');
+ },
+ mouseleave: function (event) {
+ this._removeClass($(event.currentTarget), null, 'ui-state-hover');
+ }
+ });
+ },
+
+ _focusable: function (element) {
+ this.focusable = this.focusable.add(element);
+ this._on(element, {
+ focusin: function (event) {
+ this._addClass($(event.currentTarget), null, 'ui-state-focus');
+ },
+ focusout: function (event) {
+ this._removeClass($(event.currentTarget), null, 'ui-state-focus');
+ }
+ });
+ },
+
+ _trigger: function (type, event, data) {
+ var prop, orig;
+ var callback = this.options[type];
+
+ data = data || {};
+ event = $.Event(event);
+ event.type = (type === this.widgetEventPrefix
+ ? type
+ : this.widgetEventPrefix + type
+ ).toLowerCase();
+
+ // The original event may come from any element
+ // so we need to reset the target on the new event
+ event.target = this.element[0];
+
+ // Copy original event properties over to the new event
+ orig = event.originalEvent;
+ if (orig) {
+ for (prop in orig) {
+ if (!(prop in event)) {
+ event[prop] = orig[prop];
+ }
+ }
+ }
+
+ this.element.trigger(event, data);
+ return !(
+ ($.isFunction(callback) &&
+ callback.apply(this.element[0], [event].concat(data)) === false) ||
+ event.isDefaultPrevented()
+ );
+ }
+ };
+
+ $.each({ show: 'fadeIn', hide: 'fadeOut' }, function (method, defaultEffect) {
+ $.Widget.prototype['_' + method] = function (element, options, callback) {
+ if (typeof options === 'string') {
+ options = { effect: options };
+ }
+
+ var hasOptions;
+ var effectName = !options
+ ? method
+ : options === true || typeof options === 'number'
+ ? defaultEffect
+ : options.effect || defaultEffect;
+
+ options = options || {};
+ if (typeof options === 'number') {
+ options = { duration: options };
+ }
+
+ hasOptions = !$.isEmptyObject(options);
+ options.complete = callback;
+
+ if (options.delay) {
+ element.delay(options.delay);
+ }
+
+ if (hasOptions && $.effects && $.effects.effect[effectName]) {
+ element[method](options);
+ } else if (effectName !== method && element[effectName]) {
+ element[effectName](options.duration, options.easing, callback);
+ } else {
+ element.queue(function (next) {
+ $(this)[method]();
+ if (callback) {
+ callback.call(element[0]);
+ }
+ next();
+ });
+ }
+ };
+ });
+});
diff --git a/node_modules/blueimp-file-upload/package.json b/node_modules/blueimp-file-upload/package.json
new file mode 100644
index 0000000..d6e328b
--- /dev/null
+++ b/node_modules/blueimp-file-upload/package.json
@@ -0,0 +1,150 @@
+{
+ "_from": "blueimp-file-upload",
+ "_id": "blueimp-file-upload@10.31.0",
+ "_inBundle": false,
+ "_integrity": "sha512-dGAxOf9+SsMDvZPTsIUpe0cOk57taMJYE3FocHEUebhn5BnDbu1hcWOmrP8oswIe6V61Qcm9UDuhq/Pv1t/eRw==",
+ "_location": "/blueimp-file-upload",
+ "_phantomChildren": {},
+ "_requested": {
+ "type": "tag",
+ "registry": true,
+ "raw": "blueimp-file-upload",
+ "name": "blueimp-file-upload",
+ "escapedName": "blueimp-file-upload",
+ "rawSpec": "",
+ "saveSpec": null,
+ "fetchSpec": "latest"
+ },
+ "_requiredBy": [
+ "#USER",
+ "/"
+ ],
+ "_resolved": "https://registry.npmjs.org/blueimp-file-upload/-/blueimp-file-upload-10.31.0.tgz",
+ "_shasum": "46fc4fea4e678b57afbd9008f179e59fc3aa3e84",
+ "_spec": "blueimp-file-upload",
+ "_where": "/var/vagrant/ilias_5-4/ilias/Customizing/global/plugins/Modules/Cloud/CloudHook/OneDrive",
+ "author": {
+ "name": "Sebastian Tschan",
+ "url": "https://blueimp.net"
+ },
+ "bugs": {
+ "url": "https://github.com/blueimp/jQuery-File-Upload/issues"
+ },
+ "bundleDependencies": false,
+ "dependencies": {
+ "blueimp-canvas-to-blob": "3",
+ "blueimp-load-image": "5",
+ "blueimp-tmpl": "3"
+ },
+ "deprecated": false,
+ "description": "File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery. Supports cross-domain, chunked and resumable file uploads. Works with any server-side platform (Google App Engine, PHP, Python, Ruby on Rails, Java, etc.) that supports standard HTML form file uploads.",
+ "devDependencies": {
+ "eslint": "7",
+ "eslint-config-blueimp": "2",
+ "eslint-config-prettier": "6",
+ "eslint-plugin-jsdoc": "29",
+ "eslint-plugin-prettier": "3",
+ "prettier": "2",
+ "stylelint": "13",
+ "stylelint-config-prettier": "8",
+ "stylelint-config-recommended": "3"
+ },
+ "eslintConfig": {
+ "extends": [
+ "blueimp",
+ "plugin:jsdoc/recommended",
+ "plugin:prettier/recommended"
+ ],
+ "env": {
+ "browser": true
+ }
+ },
+ "eslintIgnore": [
+ "js/*.min.js",
+ "test/vendor"
+ ],
+ "files": [
+ "css/jquery.fileupload-noscript.css",
+ "css/jquery.fileupload-ui-noscript.css",
+ "css/jquery.fileupload-ui.css",
+ "css/jquery.fileupload.css",
+ "img/loading.gif",
+ "img/progressbar.gif",
+ "js/cors/jquery.postmessage-transport.js",
+ "js/cors/jquery.xdr-transport.js",
+ "js/vendor/jquery.ui.widget.js",
+ "js/jquery.fileupload-audio.js",
+ "js/jquery.fileupload-image.js",
+ "js/jquery.fileupload-process.js",
+ "js/jquery.fileupload-ui.js",
+ "js/jquery.fileupload-validate.js",
+ "js/jquery.fileupload-video.js",
+ "js/jquery.fileupload.js",
+ "js/jquery.iframe-transport.js"
+ ],
+ "homepage": "https://github.com/blueimp/jQuery-File-Upload",
+ "keywords": [
+ "jquery",
+ "file",
+ "upload",
+ "widget",
+ "multiple",
+ "selection",
+ "drag",
+ "drop",
+ "progress",
+ "preview",
+ "cross-domain",
+ "cross-site",
+ "chunk",
+ "resume",
+ "gae",
+ "go",
+ "python",
+ "php",
+ "bootstrap"
+ ],
+ "license": "MIT",
+ "main": "js/jquery.fileupload.js",
+ "name": "blueimp-file-upload",
+ "optionalDependencies": {
+ "blueimp-canvas-to-blob": "3",
+ "blueimp-load-image": "5",
+ "blueimp-tmpl": "3"
+ },
+ "peerDependencies": {
+ "jquery": ">=1.7"
+ },
+ "prettier": {
+ "arrowParens": "avoid",
+ "proseWrap": "always",
+ "singleQuote": true,
+ "trailingComma": "none"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/blueimp/jQuery-File-Upload.git"
+ },
+ "scripts": {
+ "lint": "stylelint '**/*.css' && eslint .",
+ "posttest": "docker-compose down -v",
+ "postversion": "git push --tags origin master && npm publish",
+ "preversion": "npm test",
+ "test": "npm run lint && npm run unit && npm run wdio && npm run wdio -- conf/firefox.js",
+ "unit": "docker-compose run --rm mocha",
+ "wdio": "docker-compose run --rm wdio"
+ },
+ "stylelint": {
+ "extends": [
+ "stylelint-config-recommended",
+ "stylelint-config-prettier"
+ ],
+ "ignoreFiles": [
+ "css/*.min.css",
+ "css/vendor/*",
+ "test/vendor/*"
+ ]
+ },
+ "title": "jQuery File Upload",
+ "version": "10.31.0"
+}
diff --git a/node_modules/blueimp-load-image/LICENSE.txt b/node_modules/blueimp-load-image/LICENSE.txt
new file mode 100644
index 0000000..d6a9d74
--- /dev/null
+++ b/node_modules/blueimp-load-image/LICENSE.txt
@@ -0,0 +1,20 @@
+MIT License
+
+Copyright © 2011 Sebastian Tschan, https://blueimp.net
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/blueimp-load-image/README.md b/node_modules/blueimp-load-image/README.md
new file mode 100644
index 0000000..5759a12
--- /dev/null
+++ b/node_modules/blueimp-load-image/README.md
@@ -0,0 +1,1070 @@
+# JavaScript Load Image
+
+> A JavaScript library to load and transform image files.
+
+## Contents
+
+- [Demo](https://blueimp.github.io/JavaScript-Load-Image/)
+- [Description](#description)
+- [Setup](#setup)
+- [Usage](#usage)
+ - [Image loading](#image-loading)
+ - [Image scaling](#image-scaling)
+- [Requirements](#requirements)
+- [Browser support](#browser-support)
+- [API](#api)
+ - [Callback](#callback)
+ - [Function signature](#function-signature)
+ - [Cancel image loading](#cancel-image-loading)
+ - [Callback arguments](#callback-arguments)
+ - [Error handling](#error-handling)
+ - [Promise](#promise)
+- [Options](#options)
+ - [maxWidth](#maxwidth)
+ - [maxHeight](#maxheight)
+ - [minWidth](#minwidth)
+ - [minHeight](#minheight)
+ - [sourceWidth](#sourcewidth)
+ - [sourceHeight](#sourceheight)
+ - [top](#top)
+ - [right](#right)
+ - [bottom](#bottom)
+ - [left](#left)
+ - [contain](#contain)
+ - [cover](#cover)
+ - [aspectRatio](#aspectratio)
+ - [pixelRatio](#pixelratio)
+ - [downsamplingRatio](#downsamplingratio)
+ - [imageSmoothingEnabled](#imagesmoothingenabled)
+ - [imageSmoothingQuality](#imagesmoothingquality)
+ - [crop](#crop)
+ - [orientation](#orientation)
+ - [meta](#meta)
+ - [canvas](#canvas)
+ - [crossOrigin](#crossorigin)
+ - [noRevoke](#norevoke)
+- [Metadata parsing](#metadata-parsing)
+ - [Image head](#image-head)
+ - [Exif parser](#exif-parser)
+ - [Exif Thumbnail](#exif-thumbnail)
+ - [Exif IFD](#exif-ifd)
+ - [GPSInfo IFD](#gpsinfo-ifd)
+ - [Interoperability IFD](#interoperability-ifd)
+ - [Exif parser options](#exif-parser-options)
+ - [Exif writer](#exif-writer)
+ - [IPTC parser](#iptc-parser)
+ - [IPTC parser options](#iptc-parser-options)
+- [License](#license)
+- [Credits](#credits)
+
+## Description
+
+JavaScript Load Image is a library to load images provided as `File` or `Blob`
+objects or via `URL`. It returns an optionally **scaled**, **cropped** or
+**rotated** HTML `img` or `canvas` element.
+
+It also provides methods to parse image metadata to extract
+[IPTC](https://iptc.org/standards/photo-metadata/) and
+[Exif](https://en.wikipedia.org/wiki/Exif) tags as well as embedded thumbnail
+images, to overwrite the Exif Orientation value and to restore the complete
+image header after resizing.
+
+## Setup
+
+Install via [NPM](https://www.npmjs.com/package/blueimp-load-image):
+
+```sh
+npm install blueimp-load-image
+```
+
+This will install the JavaScript files inside
+`./node_modules/blueimp-load-image/js/` relative to your current directory, from
+where you can copy them into a folder that is served by your web server.
+
+Next include the combined and minified JavaScript Load Image script in your HTML
+markup:
+
+```html
+
+```
+
+Or alternatively, choose which components you want to include:
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Usage
+
+### Image loading
+
+In your application code, use the `loadImage()` function with
+[callback](#callback) style:
+
+```js
+document.getElementById('file-input').onchange = function () {
+ loadImage(
+ this.files[0],
+ function (img) {
+ document.body.appendChild(img)
+ },
+ { maxWidth: 600 } // Options
+ )
+}
+```
+
+Or use the [Promise](#promise) based API like this ([requires](#requirements) a
+polyfill for older browsers):
+
+```js
+document.getElementById('file-input').onchange = function () {
+ loadImage(this.files[0], { maxWidth: 600 }).then(function (data) {
+ document.body.appendChild(data.image)
+ })
+}
+```
+
+With
+[async/await](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await)
+(requires a modern browser or a code transpiler like
+[Babel](https://babeljs.io/) or [TypeScript](https://www.typescriptlang.org/)):
+
+```js
+document.getElementById('file-input').onchange = async function () {
+ let data = await loadImage(this.files[0], { maxWidth: 600 })
+ document.body.appendChild(data.image)
+}
+```
+
+### Image scaling
+
+It is also possible to use the image scaling functionality directly with an
+existing image:
+
+```js
+var scaledImage = loadImage.scale(
+ img, // img or canvas element
+ { maxWidth: 600 }
+)
+```
+
+## Requirements
+
+The JavaScript Load Image library has zero dependencies, but benefits from the
+following two
+[polyfills](https://developer.mozilla.org/en-US/docs/Glossary/Polyfill):
+
+- [blueimp-canvas-to-blob](https://github.com/blueimp/JavaScript-Canvas-to-Blob)
+ for browsers without native
+ [HTMLCanvasElement.toBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
+ support, to create `Blob` objects out of `canvas` elements.
+- [promise-polyfill](https://github.com/taylorhakes/promise-polyfill) to be able
+ to use the
+ [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+ based `loadImage` API in Browsers without native `Promise` support.
+
+## Browser support
+
+Browsers which implement the following APIs support all options:
+
+- Loading images from File and Blob objects:
+ - [URL.createObjectURL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL)
+ or
+ [FileReader.readAsDataURL](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL)
+- Parsing meta data:
+ - [FileReader.readAsArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsArrayBuffer)
+ - [Blob.slice](https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice)
+ - [DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView)
+ (no [BigInt](https://developer.mozilla.org/en-US/docs/Glossary/BigInt)
+ support required)
+- Parsing meta data from images loaded via URL:
+ - [fetch Response.blob](https://developer.mozilla.org/en-US/docs/Web/API/Body/blob)
+ or
+ [XMLHttpRequest.responseType blob](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType#blob)
+- Promise based API:
+ - [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+
+This includes (but is not limited to) the following browsers:
+
+- Chrome 32+
+- Firefox 29+
+- Safari 8+
+- Mobile Chrome 42+ (Android)
+- Mobile Firefox 50+ (Android)
+- Mobile Safari 8+ (iOS)
+- Edge 74+
+- Edge Legacy 12+
+- Internet Explorer 10+ `*`
+
+`*` Internet Explorer [requires](#requirements) a polyfill for the `Promise`
+based API.
+
+Loading an image from a URL and applying transformations (scaling, cropping and
+rotating - except `orientation:true`, which requires reading meta data) is
+supported by all browsers which implement the
+[HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement)
+interface.
+
+Loading an image from a URL and scaling it in size is supported by all browsers
+which implement the
+[img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) element and
+has been tested successfully with browser engines as old as Internet Explorer 5
+(via
+[IE11's emulation mode](
)).
+
+The `loadImage()` function applies options using
+[progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement)
+and falls back to a configuration that is supported by the browser, e.g. if the
+`canvas` element is not supported, an equivalent `img` element is returned.
+
+## API
+
+### Callback
+
+#### Function signature
+
+The `loadImage()` function accepts a
+[File](https://developer.mozilla.org/en-US/docs/Web/API/File) or
+[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) object or an image
+URL as first argument.
+
+If a [File](https://developer.mozilla.org/en-US/docs/Web/API/File) or
+[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) is passed as
+parameter, it returns an HTML `img` element if the browser supports the
+[URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) API, alternatively a
+[FileReader](https://developer.mozilla.org/en-US/docs/Web/API/FileReader) object
+if the `FileReader` API is supported, or `false`.
+
+It always returns an HTML
+[img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Img) element
+when passing an image URL:
+
+```js
+var loadingImage = loadImage(
+ 'https://example.org/image.png',
+ function (img) {
+ document.body.appendChild(img)
+ },
+ { maxWidth: 600 }
+)
+```
+
+#### Cancel image loading
+
+Some browsers (e.g. Chrome) will cancel the image loading process if the `src`
+property of an `img` element is changed.
+To avoid unnecessary requests, we can use the
+[data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs)
+of a 1x1 pixel transparent GIF image as `src` target to cancel the original
+image download.
+
+To disable callback handling, we can also unset the image event handlers and for
+maximum browser compatibility, cancel the file reading process if the returned
+object is a
+[FileReader](https://developer.mozilla.org/en-US/docs/Web/API/FileReader)
+instance:
+
+```js
+var loadingImage = loadImage(
+ 'https://example.org/image.png',
+ function (img) {
+ document.body.appendChild(img)
+ },
+ { maxWidth: 600 }
+)
+
+if (loadingImage) {
+ // Unset event handling for the loading image:
+ loadingImage.onload = loadingImage.onerror = null
+
+ // Cancel image loading process:
+ if (loadingImage.abort) {
+ // FileReader instance, stop the file reading process:
+ loadingImage.abort()
+ } else {
+ // HTMLImageElement element, cancel the original image request by changing
+ // the target source to the data URL of a 1x1 pixel transparent image GIF:
+ loadingImage.src =
+ 'data:image/gif;base64,' +
+ 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
+ }
+}
+```
+
+**Please note:**
+The `img` element (or `FileReader` instance) for the loading image is only
+returned when using the callback style API and not available with the
+[Promise](#promise) based API.
+
+#### Callback arguments
+
+For the callback style API, the second argument to `loadImage()` must be a
+`callback` function, which is called when the image has been loaded or an error
+occurred while loading the image.
+
+The callback function is passed two arguments:
+
+1. An HTML [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
+ element or
+ [canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)
+ element, or an
+ [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) object of
+ type `error`.
+2. An object with the original image dimensions as properties and potentially
+ additional [metadata](#metadata-parsing).
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ document.body.appendChild(img)
+ console.log('Original image width: ', data.originalWidth)
+ console.log('Original image height: ', data.originalHeight)
+ },
+ { maxWidth: 600, meta: true }
+)
+```
+
+**Please note:**
+The original image dimensions reflect the natural width and height of the loaded
+image before applying any transformation.
+For consistent values across browsers, [metadata](#metadata-parsing) parsing has
+to be enabled via `meta:true`, so `loadImage` can detect automatic image
+orientation and normalize the dimensions.
+
+#### Error handling
+
+Example code implementing error handling:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ if (img.type === 'error') {
+ console.error('Error loading image file')
+ } else {
+ document.body.appendChild(img)
+ }
+ },
+ { maxWidth: 600 }
+)
+```
+
+### Promise
+
+If the `loadImage()` function is called without a `callback` function as second
+argument and the
+[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+API is available, it returns a `Promise` object:
+
+```js
+loadImage(fileOrBlobOrUrl, { maxWidth: 600, meta: true })
+ .then(function (data) {
+ document.body.appendChild(data.image)
+ console.log('Original image width: ', data.originalWidth)
+ console.log('Original image height: ', data.originalHeight)
+ })
+ .catch(function (err) {
+ // Handling image loading errors
+ console.log(err)
+ })
+```
+
+The `Promise` resolves with an object with the following properties:
+
+- `image`: An HTML
+ [img](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) or
+ [canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) element.
+- `originalWidth`: The original width of the image.
+- `originalHeight`: The original height of the image.
+
+Please also read the note about original image dimensions normalization in the
+[callback arguments](#callback-arguments) section.
+
+If [metadata](#metadata-parsing) has been parsed, additional properties might be
+present on the object.
+
+If image loading fails, the `Promise` rejects with an
+[Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) object of type
+`error`.
+
+## Options
+
+The optional options argument to `loadImage()` allows to configure the image
+loading.
+
+It can be used the following way with the callback style:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img) {
+ document.body.appendChild(img)
+ },
+ {
+ maxWidth: 600,
+ maxHeight: 300,
+ minWidth: 100,
+ minHeight: 50,
+ canvas: true
+ }
+)
+```
+
+Or the following way with the `Promise` based API:
+
+```js
+loadImage(fileOrBlobOrUrl, {
+ maxWidth: 600,
+ maxHeight: 300,
+ minWidth: 100,
+ minHeight: 50,
+ canvas: true
+}).then(function (data) {
+ document.body.appendChild(data.image)
+})
+```
+
+All settings are optional. By default, the image is returned as HTML `img`
+element without any image size restrictions.
+
+### maxWidth
+
+Defines the maximum width of the `img`/`canvas` element.
+
+### maxHeight
+
+Defines the maximum height of the `img`/`canvas` element.
+
+### minWidth
+
+Defines the minimum width of the `img`/`canvas` element.
+
+### minHeight
+
+Defines the minimum height of the `img`/`canvas` element.
+
+### sourceWidth
+
+The width of the sub-rectangle of the source image to draw into the destination
+canvas.
+Defaults to the source image width and requires `canvas: true`.
+
+### sourceHeight
+
+The height of the sub-rectangle of the source image to draw into the destination
+canvas.
+Defaults to the source image height and requires `canvas: true`.
+
+### top
+
+The top margin of the sub-rectangle of the source image.
+Defaults to `0` and requires `canvas: true`.
+
+### right
+
+The right margin of the sub-rectangle of the source image.
+Defaults to `0` and requires `canvas: true`.
+
+### bottom
+
+The bottom margin of the sub-rectangle of the source image.
+Defaults to `0` and requires `canvas: true`.
+
+### left
+
+The left margin of the sub-rectangle of the source image.
+Defaults to `0` and requires `canvas: true`.
+
+### contain
+
+Scales the image up/down to contain it in the max dimensions if set to `true`.
+This emulates the CSS feature
+[background-image: contain](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Backgrounds_and_Borders/Resizing_background_images#contain).
+
+### cover
+
+Scales the image up/down to cover the max dimensions with the image dimensions
+if set to `true`.
+This emulates the CSS feature
+[background-image: cover](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Backgrounds_and_Borders/Resizing_background_images#cover).
+
+### aspectRatio
+
+Crops the image to the given aspect ratio (e.g. `16/9`).
+Setting the `aspectRatio` also enables the `crop` option.
+
+### pixelRatio
+
+Defines the ratio of the canvas pixels to the physical image pixels on the
+screen.
+Should be set to
+[window.devicePixelRatio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio)
+unless the scaled image is not rendered on screen.
+Defaults to `1` and requires `canvas: true`.
+
+### downsamplingRatio
+
+Defines the ratio in which the image is downsampled (scaled down in steps).
+By default, images are downsampled in one step.
+With a ratio of `0.5`, each step scales the image to half the size, before
+reaching the target dimensions.
+Requires `canvas: true`.
+
+### imageSmoothingEnabled
+
+If set to `false`,
+[disables image smoothing](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled).
+Defaults to `true` and requires `canvas: true`.
+
+### imageSmoothingQuality
+
+Sets the
+[quality of image smoothing](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality).
+Possible values: `'low'`, `'medium'`, `'high'`
+Defaults to `'low'` and requires `canvas: true`.
+
+### crop
+
+Crops the image to the `maxWidth`/`maxHeight` constraints if set to `true`.
+Enabling the `crop` option also enables the `canvas` option.
+
+### orientation
+
+Transform the canvas according to the specified Exif orientation, which can be
+an `integer` in the range of `1` to `8` or the boolean value `true`.
+
+When set to `true`, it will set the orientation value based on the Exif data of
+the image, which will be parsed automatically if the Exif extension is
+available.
+
+Exif orientation values to correctly display the letter F:
+
+```
+ 1 2
+ ██████ ██████
+ ██ ██
+ ████ ████
+ ██ ██
+ ██ ██
+
+ 3 4
+ ██ ██
+ ██ ██
+ ████ ████
+ ██ ██
+ ██████ ██████
+
+ 5 6
+██████████ ██
+██ ██ ██ ██
+██ ██████████
+
+ 7 8
+ ██ ██████████
+ ██ ██ ██ ██
+██████████ ██
+```
+
+Setting `orientation` to `true` enables the `canvas` and `meta` options, unless
+the browser supports automatic image orientation (see
+[browser support for image-orientation](https://caniuse.com/#feat=css-image-orientation)).
+
+Setting `orientation` to `1` enables the `canvas` and `meta` options if the
+browser does support automatic image orientation (to allow reset of the
+orientation).
+
+Setting `orientation` to an integer in the range of `2` to `8` always enables
+the `canvas` option and also enables the `meta` option if the browser supports
+automatic image orientation (again to allow reset).
+
+### meta
+
+Automatically parses the image metadata if set to `true`.
+
+If metadata has been found, the data object passed as second argument to the
+callback function has additional properties (see
+[metadata parsing](#metadata-parsing)).
+
+If the file is given as URL and the browser supports the
+[fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or the
+XHR
+[responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType)
+`blob`, fetches the file as `Blob` to be able to parse the metadata.
+
+### canvas
+
+Returns the image as
+[canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) element if
+set to `true`.
+
+### crossOrigin
+
+Sets the `crossOrigin` property on the `img` element for loading
+[CORS enabled images](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image).
+
+### noRevoke
+
+By default, the
+[created object URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL)
+is revoked after the image has been loaded, except when this option is set to
+`true`.
+
+## Metadata parsing
+
+If the Load Image Meta extension is included, it is possible to parse image meta
+data automatically with the `meta` option:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ console.log('Original image head: ', data.imageHead)
+ console.log('Exif data: ', data.exif) // requires exif extension
+ console.log('IPTC data: ', data.iptc) // requires iptc extension
+ },
+ { meta: true }
+)
+```
+
+Or alternatively via `loadImage.parseMetaData`, which can be used with an
+available `File` or `Blob` object as first argument:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('Original image head: ', data.imageHead)
+ console.log('Exif data: ', data.exif) // requires exif extension
+ console.log('IPTC data: ', data.iptc) // requires iptc extension
+ },
+ {
+ maxMetaDataSize: 262144
+ }
+)
+```
+
+Or using the
+[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+based API:
+
+```js
+loadImage
+ .parseMetaData(fileOrBlob, {
+ maxMetaDataSize: 262144
+ })
+ .then(function (data) {
+ console.log('Original image head: ', data.imageHead)
+ console.log('Exif data: ', data.exif) // requires exif extension
+ console.log('IPTC data: ', data.iptc) // requires iptc extension
+ })
+```
+
+The Metadata extension adds additional options used for the `parseMetaData`
+method:
+
+- `maxMetaDataSize`: Maximum number of bytes of metadata to parse.
+- `disableImageHead`: Disable parsing the original image head.
+- `disableMetaDataParsers`: Disable parsing metadata (image head only)
+
+### Image head
+
+Resized JPEG images can be combined with their original image head via
+`loadImage.replaceHead`, which requires the resized image as `Blob` object as
+first argument and an `ArrayBuffer` image head as second argument.
+
+With callback style, the third argument must be a `callback` function, which is
+called with the new `Blob` object:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ if (data.imageHead) {
+ img.toBlob(function (blob) {
+ loadImage.replaceHead(blob, data.imageHead, function (newBlob) {
+ // do something with the new Blob object
+ })
+ }, 'image/jpeg')
+ }
+ },
+ { meta: true, canvas: true, maxWidth: 800 }
+)
+```
+
+Or using the
+[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+based API like this:
+
+```js
+loadImage(fileOrBlobOrUrl, { meta: true, canvas: true, maxWidth: 800 })
+ .then(function (data) {
+ if (!data.imageHead) throw new Error('Could not parse image metadata')
+ return new Promise(function (resolve) {
+ data.image.toBlob(function (blob) {
+ data.blob = blob
+ resolve(data)
+ }, 'image/jpeg')
+ })
+ })
+ .then(function (data) {
+ return loadImage.replaceHead(data.blob, data.imageHead)
+ })
+ .then(function (blob) {
+ // do something with the new Blob object
+ })
+ .catch(function (err) {
+ console.error(err)
+ })
+```
+
+**Please note:**
+`Blob` objects of resized images can be created via
+[HTMLCanvasElement.toBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob).
+[blueimp-canvas-to-blob](https://github.com/blueimp/JavaScript-Canvas-to-Blob)
+provides a polyfill for browsers without native `canvas.toBlob()` support.
+
+### Exif parser
+
+If you include the Load Image Exif Parser extension, the argument passed to the
+callback for `parseMetaData` will contain the following additional properties if
+Exif data could be found in the given image:
+
+- `exif`: The parsed Exif tags
+- `exifOffsets`: The parsed Exif tag offsets
+- `exifTiffOffset`: TIFF header offset (used for offset pointers)
+- `exifLittleEndian`: little endian order if true, big endian if false
+
+The `exif` object stores the parsed Exif tags:
+
+```js
+var orientation = data.exif[0x0112] // Orientation
+```
+
+The `exif` and `exifOffsets` objects also provide a `get()` method to retrieve
+the tag value/offset via the tag's mapped name:
+
+```js
+var orientation = data.exif.get('Orientation')
+var orientationOffset = data.exifOffsets.get('Orientation')
+```
+
+By default, only the following names are mapped:
+
+- `Orientation`
+- `Thumbnail` (see [Exif Thumbnail](#exif-thumbnail))
+- `Exif` (see [Exif IFD](#exif-ifd))
+- `GPSInfo` (see [GPSInfo IFD](#gpsinfo-ifd))
+- `Interoperability` (see [Interoperability IFD](#interoperability-ifd))
+
+If you also include the Load Image Exif Map library, additional tag mappings
+become available, as well as three additional methods:
+
+- `exif.getText()`
+- `exif.getName()`
+- `exif.getAll()`
+
+```js
+var orientationText = data.exif.getText('Orientation') // e.g. "Rotate 90° CW"
+
+var name = data.exif.getName(0x0112) // "Orientation"
+
+// A map of all parsed tags with their mapped names/text as keys/values:
+var allTags = data.exif.getAll()
+```
+
+#### Exif Thumbnail
+
+Example code displaying a thumbnail image embedded into the Exif metadata:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var exif = data.exif
+ var thumbnail = exif && exif.get('Thumbnail')
+ var blob = thumbnail && thumbnail.get('Blob')
+ if (blob) {
+ loadImage(
+ blob,
+ function (thumbImage) {
+ document.body.appendChild(thumbImage)
+ },
+ { orientation: exif.get('Orientation') }
+ )
+ }
+ },
+ { meta: true }
+)
+```
+
+#### Exif IFD
+
+Example code displaying data from the Exif IFD (Image File Directory) that
+contains Exif specified TIFF tags:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var exifIFD = data.exif && data.exif.get('Exif')
+ if (exifIFD) {
+ // Map of all Exif IFD tags with their mapped names/text as keys/values:
+ console.log(exifIFD.getAll())
+ // A specific Exif IFD tag value:
+ console.log(exifIFD.get('UserComment'))
+ }
+ },
+ { meta: true }
+)
+```
+
+#### GPSInfo IFD
+
+Example code displaying data from the Exif IFD (Image File Directory) that
+contains [GPS](https://en.wikipedia.org/wiki/Global_Positioning_System) info:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var gpsInfo = data.exif && data.exif.get('GPSInfo')
+ if (gpsInfo) {
+ // Map of all GPSInfo tags with their mapped names/text as keys/values:
+ console.log(gpsInfo.getAll())
+ // A specific GPSInfo tag value:
+ console.log(gpsInfo.get('GPSLatitude'))
+ }
+ },
+ { meta: true }
+)
+```
+
+#### Interoperability IFD
+
+Example code displaying data from the Exif IFD (Image File Directory) that
+contains Interoperability data:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ var interoperabilityData = data.exif && data.exif.get('Interoperability')
+ if (interoperabilityData) {
+ // The InteroperabilityIndex tag value:
+ console.log(interoperabilityData.get('InteroperabilityIndex'))
+ }
+ },
+ { meta: true }
+)
+```
+
+#### Exif parser options
+
+The Exif parser adds additional options:
+
+- `disableExif`: Disables Exif parsing when `true`.
+- `disableExifOffsets`: Disables storing Exif tag offsets when `true`.
+- `includeExifTags`: A map of Exif tags to include for parsing (includes all but
+ the excluded tags by default).
+- `excludeExifTags`: A map of Exif tags to exclude from parsing (defaults to
+ exclude `Exif` `MakerNote`).
+
+An example parsing only Orientation, Thumbnail and ExifVersion tags:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('Exif data: ', data.exif)
+ },
+ {
+ includeExifTags: {
+ 0x0112: true, // Orientation
+ ifd1: {
+ 0x0201: true, // JPEGInterchangeFormat (Thumbnail data offset)
+ 0x0202: true // JPEGInterchangeFormatLength (Thumbnail data length)
+ },
+ 0x8769: {
+ // ExifIFDPointer
+ 0x9000: true // ExifVersion
+ }
+ }
+ }
+)
+```
+
+An example excluding `Exif` `MakerNote` and `GPSInfo`:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('Exif data: ', data.exif)
+ },
+ {
+ excludeExifTags: {
+ 0x8769: {
+ // ExifIFDPointer
+ 0x927c: true // MakerNote
+ },
+ 0x8825: true // GPSInfoIFDPointer
+ }
+ }
+)
+```
+
+### Exif writer
+
+The Exif parser extension also includes a minimal writer that allows to override
+the Exif `Orientation` value in the parsed `imageHead` `ArrayBuffer`:
+
+```js
+loadImage(
+ fileOrBlobOrUrl,
+ function (img, data) {
+ if (data.imageHead && data.exif) {
+ // Reset Exif Orientation data:
+ loadImage.writeExifData(data.imageHead, data, 'Orientation', 1)
+ img.toBlob(function (blob) {
+ loadImage.replaceHead(blob, data.imageHead, function (newBlob) {
+ // do something with newBlob
+ })
+ }, 'image/jpeg')
+ }
+ },
+ { meta: true, orientation: true, canvas: true, maxWidth: 800 }
+)
+```
+
+**Please note:**
+The Exif writer relies on the Exif tag offsets being available as
+`data.exifOffsets` property, which requires that Exif data has been parsed from
+the image.
+The Exif writer can only change existing values, not add new tags, e.g. it
+cannot add an Exif `Orientation` tag for an image that does not have one.
+
+### IPTC parser
+
+If you include the Load Image IPTC Parser extension, the argument passed to the
+callback for `parseMetaData` will contain the following additional properties if
+IPTC data could be found in the given image:
+
+- `iptc`: The parsed IPTC tags
+- `iptcOffsets`: The parsed IPTC tag offsets
+
+The `iptc` object stores the parsed IPTC tags:
+
+```js
+var objectname = data.iptc[5]
+```
+
+The `iptc` and `iptcOffsets` objects also provide a `get()` method to retrieve
+the tag value/offset via the tag's mapped name:
+
+```js
+var objectname = data.iptc.get('ObjectName')
+```
+
+By default, only the following names are mapped:
+
+- `ObjectName`
+
+If you also include the Load Image IPTC Map library, additional tag mappings
+become available, as well as three additional methods:
+
+- `iptc.getText()`
+- `iptc.getName()`
+- `iptc.getAll()`
+
+```js
+var keywords = data.iptc.getText('Keywords') // e.g.: ['Weather','Sky']
+
+var name = data.iptc.getName(5) // ObjectName
+
+// A map of all parsed tags with their mapped names/text as keys/values:
+var allTags = data.iptc.getAll()
+```
+
+#### IPTC parser options
+
+The IPTC parser adds additional options:
+
+- `disableIptc`: Disables IPTC parsing when true.
+- `disableIptcOffsets`: Disables storing IPTC tag offsets when `true`.
+- `includeIptcTags`: A map of IPTC tags to include for parsing (includes all but
+ the excluded tags by default).
+- `excludeIptcTags`: A map of IPTC tags to exclude from parsing (defaults to
+ exclude `ObjectPreviewData`).
+
+An example parsing only the `ObjectName` tag:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('IPTC data: ', data.iptc)
+ },
+ {
+ includeIptcTags: {
+ 5: true // ObjectName
+ }
+ }
+)
+```
+
+An example excluding `ApplicationRecordVersion` and `ObjectPreviewData`:
+
+```js
+loadImage.parseMetaData(
+ fileOrBlob,
+ function (data) {
+ console.log('IPTC data: ', data.iptc)
+ },
+ {
+ excludeIptcTags: {
+ 0: true, // ApplicationRecordVersion
+ 202: true // ObjectPreviewData
+ }
+ }
+)
+```
+
+## License
+
+The JavaScript Load Image library is released under the
+[MIT license](https://opensource.org/licenses/MIT).
+
+## Credits
+
+- Original image metadata handling implemented with the help and contribution of
+ Achim Stöhr.
+- Original Exif tags mapping based on Jacob Seidelin's
+ [exif-js](https://github.com/exif-js/exif-js) library.
+- Original IPTC parser implementation by
+ [Dave Bevan](https://github.com/bevand10).
diff --git a/node_modules/blueimp-load-image/js/index.js b/node_modules/blueimp-load-image/js/index.js
new file mode 100644
index 0000000..38e1794
--- /dev/null
+++ b/node_modules/blueimp-load-image/js/index.js
@@ -0,0 +1,12 @@
+/* global module, require */
+
+module.exports = require('./load-image')
+
+require('./load-image-scale')
+require('./load-image-meta')
+require('./load-image-fetch')
+require('./load-image-exif')
+require('./load-image-exif-map')
+require('./load-image-iptc')
+require('./load-image-iptc-map')
+require('./load-image-orientation')
diff --git a/node_modules/blueimp-load-image/js/load-image-exif-map.js b/node_modules/blueimp-load-image/js/load-image-exif-map.js
new file mode 100644
index 0000000..63f8e59
--- /dev/null
+++ b/node_modules/blueimp-load-image/js/load-image-exif-map.js
@@ -0,0 +1,420 @@
+/*
+ * JavaScript Load Image Exif Map
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Exif tags mapping based on
+ * https://github.com/jseidelin/exif-js
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['./load-image', './load-image-exif'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('./load-image'), require('./load-image-exif'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ var ExifMapProto = loadImage.ExifMap.prototype
+
+ ExifMapProto.tags = {
+ // =================
+ // TIFF tags (IFD0):
+ // =================
+ 0x0100: 'ImageWidth',
+ 0x0101: 'ImageHeight',
+ 0x0102: 'BitsPerSample',
+ 0x0103: 'Compression',
+ 0x0106: 'PhotometricInterpretation',
+ 0x0112: 'Orientation',
+ 0x0115: 'SamplesPerPixel',
+ 0x011c: 'PlanarConfiguration',
+ 0x0212: 'YCbCrSubSampling',
+ 0x0213: 'YCbCrPositioning',
+ 0x011a: 'XResolution',
+ 0x011b: 'YResolution',
+ 0x0128: 'ResolutionUnit',
+ 0x0111: 'StripOffsets',
+ 0x0116: 'RowsPerStrip',
+ 0x0117: 'StripByteCounts',
+ 0x0201: 'JPEGInterchangeFormat',
+ 0x0202: 'JPEGInterchangeFormatLength',
+ 0x012d: 'TransferFunction',
+ 0x013e: 'WhitePoint',
+ 0x013f: 'PrimaryChromaticities',
+ 0x0211: 'YCbCrCoefficients',
+ 0x0214: 'ReferenceBlackWhite',
+ 0x0132: 'DateTime',
+ 0x010e: 'ImageDescription',
+ 0x010f: 'Make',
+ 0x0110: 'Model',
+ 0x0131: 'Software',
+ 0x013b: 'Artist',
+ 0x8298: 'Copyright',
+ 0x8769: {
+ // ExifIFDPointer
+ 0x9000: 'ExifVersion', // EXIF version
+ 0xa000: 'FlashpixVersion', // Flashpix format version
+ 0xa001: 'ColorSpace', // Color space information tag
+ 0xa002: 'PixelXDimension', // Valid width of meaningful image
+ 0xa003: 'PixelYDimension', // Valid height of meaningful image
+ 0xa500: 'Gamma',
+ 0x9101: 'ComponentsConfiguration', // Information about channels
+ 0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel
+ 0x927c: 'MakerNote', // Any desired information written by the manufacturer
+ 0x9286: 'UserComment', // Comments by user
+ 0xa004: 'RelatedSoundFile', // Name of related sound file
+ 0x9003: 'DateTimeOriginal', // Date and time when the original image was generated
+ 0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally
+ 0x9010: 'OffsetTime', // Time zone when the image file was last changed
+ 0x9011: 'OffsetTimeOriginal', // Time zone when the image was stored digitally
+ 0x9012: 'OffsetTimeDigitized', // Time zone when the image was stored digitally
+ 0x9290: 'SubSecTime', // Fractions of seconds for DateTime
+ 0x9291: 'SubSecTimeOriginal', // Fractions of seconds for DateTimeOriginal
+ 0x9292: 'SubSecTimeDigitized', // Fractions of seconds for DateTimeDigitized
+ 0x829a: 'ExposureTime', // Exposure time (in seconds)
+ 0x829d: 'FNumber',
+ 0x8822: 'ExposureProgram', // Exposure program
+ 0x8824: 'SpectralSensitivity', // Spectral sensitivity
+ 0x8827: 'PhotographicSensitivity', // EXIF 2.3, ISOSpeedRatings in EXIF 2.2
+ 0x8828: 'OECF', // Optoelectric conversion factor
+ 0x8830: 'SensitivityType',
+ 0x8831: 'StandardOutputSensitivity',
+ 0x8832: 'RecommendedExposureIndex',
+ 0x8833: 'ISOSpeed',
+ 0x8834: 'ISOSpeedLatitudeyyy',
+ 0x8835: 'ISOSpeedLatitudezzz',
+ 0x9201: 'ShutterSpeedValue', // Shutter speed
+ 0x9202: 'ApertureValue', // Lens aperture
+ 0x9203: 'BrightnessValue', // Value of brightness
+ 0x9204: 'ExposureBias', // Exposure bias
+ 0x9205: 'MaxApertureValue', // Smallest F number of lens
+ 0x9206: 'SubjectDistance', // Distance to subject in meters
+ 0x9207: 'MeteringMode', // Metering mode
+ 0x9208: 'LightSource', // Kind of light source
+ 0x9209: 'Flash', // Flash status
+ 0x9214: 'SubjectArea', // Location and area of main subject
+ 0x920a: 'FocalLength', // Focal length of the lens in mm
+ 0xa20b: 'FlashEnergy', // Strobe energy in BCPS
+ 0xa20c: 'SpatialFrequencyResponse',
+ 0xa20e: 'FocalPlaneXResolution', // Number of pixels in width direction per FPRUnit
+ 0xa20f: 'FocalPlaneYResolution', // Number of pixels in height direction per FPRUnit
+ 0xa210: 'FocalPlaneResolutionUnit', // Unit for measuring the focal plane resolution
+ 0xa214: 'SubjectLocation', // Location of subject in image
+ 0xa215: 'ExposureIndex', // Exposure index selected on camera
+ 0xa217: 'SensingMethod', // Image sensor type
+ 0xa300: 'FileSource', // Image source (3 == DSC)
+ 0xa301: 'SceneType', // Scene type (1 == directly photographed)
+ 0xa302: 'CFAPattern', // Color filter array geometric pattern
+ 0xa401: 'CustomRendered', // Special processing
+ 0xa402: 'ExposureMode', // Exposure mode
+ 0xa403: 'WhiteBalance', // 1 = auto white balance, 2 = manual
+ 0xa404: 'DigitalZoomRatio', // Digital zoom ratio
+ 0xa405: 'FocalLengthIn35mmFilm',
+ 0xa406: 'SceneCaptureType', // Type of scene
+ 0xa407: 'GainControl', // Degree of overall image gain adjustment
+ 0xa408: 'Contrast', // Direction of contrast processing applied by camera
+ 0xa409: 'Saturation', // Direction of saturation processing applied by camera
+ 0xa40a: 'Sharpness', // Direction of sharpness processing applied by camera
+ 0xa40b: 'DeviceSettingDescription',
+ 0xa40c: 'SubjectDistanceRange', // Distance to subject
+ 0xa420: 'ImageUniqueID', // Identifier assigned uniquely to each image
+ 0xa430: 'CameraOwnerName',
+ 0xa431: 'BodySerialNumber',
+ 0xa432: 'LensSpecification',
+ 0xa433: 'LensMake',
+ 0xa434: 'LensModel',
+ 0xa435: 'LensSerialNumber'
+ },
+ 0x8825: {
+ // GPSInfoIFDPointer
+ 0x0000: 'GPSVersionID',
+ 0x0001: 'GPSLatitudeRef',
+ 0x0002: 'GPSLatitude',
+ 0x0003: 'GPSLongitudeRef',
+ 0x0004: 'GPSLongitude',
+ 0x0005: 'GPSAltitudeRef',
+ 0x0006: 'GPSAltitude',
+ 0x0007: 'GPSTimeStamp',
+ 0x0008: 'GPSSatellites',
+ 0x0009: 'GPSStatus',
+ 0x000a: 'GPSMeasureMode',
+ 0x000b: 'GPSDOP',
+ 0x000c: 'GPSSpeedRef',
+ 0x000d: 'GPSSpeed',
+ 0x000e: 'GPSTrackRef',
+ 0x000f: 'GPSTrack',
+ 0x0010: 'GPSImgDirectionRef',
+ 0x0011: 'GPSImgDirection',
+ 0x0012: 'GPSMapDatum',
+ 0x0013: 'GPSDestLatitudeRef',
+ 0x0014: 'GPSDestLatitude',
+ 0x0015: 'GPSDestLongitudeRef',
+ 0x0016: 'GPSDestLongitude',
+ 0x0017: 'GPSDestBearingRef',
+ 0x0018: 'GPSDestBearing',
+ 0x0019: 'GPSDestDistanceRef',
+ 0x001a: 'GPSDestDistance',
+ 0x001b: 'GPSProcessingMethod',
+ 0x001c: 'GPSAreaInformation',
+ 0x001d: 'GPSDateStamp',
+ 0x001e: 'GPSDifferential',
+ 0x001f: 'GPSHPositioningError'
+ },
+ 0xa005: {
+ // InteroperabilityIFDPointer
+ 0x0001: 'InteroperabilityIndex'
+ }
+ }
+
+ // IFD1 directory can contain any IFD0 tags:
+ ExifMapProto.tags.ifd1 = ExifMapProto.tags
+
+ ExifMapProto.stringValues = {
+ ExposureProgram: {
+ 0: 'Undefined',
+ 1: 'Manual',
+ 2: 'Normal program',
+ 3: 'Aperture priority',
+ 4: 'Shutter priority',
+ 5: 'Creative program',
+ 6: 'Action program',
+ 7: 'Portrait mode',
+ 8: 'Landscape mode'
+ },
+ MeteringMode: {
+ 0: 'Unknown',
+ 1: 'Average',
+ 2: 'CenterWeightedAverage',
+ 3: 'Spot',
+ 4: 'MultiSpot',
+ 5: 'Pattern',
+ 6: 'Partial',
+ 255: 'Other'
+ },
+ LightSource: {
+ 0: 'Unknown',
+ 1: 'Daylight',
+ 2: 'Fluorescent',
+ 3: 'Tungsten (incandescent light)',
+ 4: 'Flash',
+ 9: 'Fine weather',
+ 10: 'Cloudy weather',
+ 11: 'Shade',
+ 12: 'Daylight fluorescent (D 5700 - 7100K)',
+ 13: 'Day white fluorescent (N 4600 - 5400K)',
+ 14: 'Cool white fluorescent (W 3900 - 4500K)',
+ 15: 'White fluorescent (WW 3200 - 3700K)',
+ 17: 'Standard light A',
+ 18: 'Standard light B',
+ 19: 'Standard light C',
+ 20: 'D55',
+ 21: 'D65',
+ 22: 'D75',
+ 23: 'D50',
+ 24: 'ISO studio tungsten',
+ 255: 'Other'
+ },
+ Flash: {
+ 0x0000: 'Flash did not fire',
+ 0x0001: 'Flash fired',
+ 0x0005: 'Strobe return light not detected',
+ 0x0007: 'Strobe return light detected',
+ 0x0009: 'Flash fired, compulsory flash mode',
+ 0x000d: 'Flash fired, compulsory flash mode, return light not detected',
+ 0x000f: 'Flash fired, compulsory flash mode, return light detected',
+ 0x0010: 'Flash did not fire, compulsory flash mode',
+ 0x0018: 'Flash did not fire, auto mode',
+ 0x0019: 'Flash fired, auto mode',
+ 0x001d: 'Flash fired, auto mode, return light not detected',
+ 0x001f: 'Flash fired, auto mode, return light detected',
+ 0x0020: 'No flash function',
+ 0x0041: 'Flash fired, red-eye reduction mode',
+ 0x0045: 'Flash fired, red-eye reduction mode, return light not detected',
+ 0x0047: 'Flash fired, red-eye reduction mode, return light detected',
+ 0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode',
+ 0x004d: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
+ 0x004f: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
+ 0x0059: 'Flash fired, auto mode, red-eye reduction mode',
+ 0x005d: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
+ 0x005f: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
+ },
+ SensingMethod: {
+ 1: 'Undefined',
+ 2: 'One-chip color area sensor',
+ 3: 'Two-chip color area sensor',
+ 4: 'Three-chip color area sensor',
+ 5: 'Color sequential area sensor',
+ 7: 'Trilinear sensor',
+ 8: 'Color sequential linear sensor'
+ },
+ SceneCaptureType: {
+ 0: 'Standard',
+ 1: 'Landscape',
+ 2: 'Portrait',
+ 3: 'Night scene'
+ },
+ SceneType: {
+ 1: 'Directly photographed'
+ },
+ CustomRendered: {
+ 0: 'Normal process',
+ 1: 'Custom process'
+ },
+ WhiteBalance: {
+ 0: 'Auto white balance',
+ 1: 'Manual white balance'
+ },
+ GainControl: {
+ 0: 'None',
+ 1: 'Low gain up',
+ 2: 'High gain up',
+ 3: 'Low gain down',
+ 4: 'High gain down'
+ },
+ Contrast: {
+ 0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'
+ },
+ Saturation: {
+ 0: 'Normal',
+ 1: 'Low saturation',
+ 2: 'High saturation'
+ },
+ Sharpness: {
+ 0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'
+ },
+ SubjectDistanceRange: {
+ 0: 'Unknown',
+ 1: 'Macro',
+ 2: 'Close view',
+ 3: 'Distant view'
+ },
+ FileSource: {
+ 3: 'DSC'
+ },
+ ComponentsConfiguration: {
+ 0: '',
+ 1: 'Y',
+ 2: 'Cb',
+ 3: 'Cr',
+ 4: 'R',
+ 5: 'G',
+ 6: 'B'
+ },
+ Orientation: {
+ 1: 'Original',
+ 2: 'Horizontal flip',
+ 3: 'Rotate 180° CCW',
+ 4: 'Vertical flip',
+ 5: 'Vertical flip + Rotate 90° CW',
+ 6: 'Rotate 90° CW',
+ 7: 'Horizontal flip + Rotate 90° CW',
+ 8: 'Rotate 90° CCW'
+ }
+ }
+
+ ExifMapProto.getText = function (name) {
+ var value = this.get(name)
+ switch (name) {
+ case 'LightSource':
+ case 'Flash':
+ case 'MeteringMode':
+ case 'ExposureProgram':
+ case 'SensingMethod':
+ case 'SceneCaptureType':
+ case 'SceneType':
+ case 'CustomRendered':
+ case 'WhiteBalance':
+ case 'GainControl':
+ case 'Contrast':
+ case 'Saturation':
+ case 'Sharpness':
+ case 'SubjectDistanceRange':
+ case 'FileSource':
+ case 'Orientation':
+ return this.stringValues[name][value]
+ case 'ExifVersion':
+ case 'FlashpixVersion':
+ if (!value) return
+ return String.fromCharCode(value[0], value[1], value[2], value[3])
+ case 'ComponentsConfiguration':
+ if (!value) return
+ return (
+ this.stringValues[name][value[0]] +
+ this.stringValues[name][value[1]] +
+ this.stringValues[name][value[2]] +
+ this.stringValues[name][value[3]]
+ )
+ case 'GPSVersionID':
+ if (!value) return
+ return value[0] + '.' + value[1] + '.' + value[2] + '.' + value[3]
+ }
+ return String(value)
+ }
+
+ ExifMapProto.getAll = function () {
+ var map = {}
+ var prop
+ var obj
+ var name
+ for (prop in this) {
+ if (Object.prototype.hasOwnProperty.call(this, prop)) {
+ obj = this[prop]
+ if (obj && obj.getAll) {
+ map[this.ifds[prop].name] = obj.getAll()
+ } else {
+ name = this.tags[prop]
+ if (name) map[name] = this.getText(name)
+ }
+ }
+ }
+ return map
+ }
+
+ ExifMapProto.getName = function (tagCode) {
+ var name = this.tags[tagCode]
+ if (typeof name === 'object') return this.ifds[tagCode].name
+ return name
+ }
+
+ // Extend the map of tag names to tag codes:
+ ;(function () {
+ var tags = ExifMapProto.tags
+ var prop
+ var ifd
+ var subTags
+ // Map the tag names to tags:
+ for (prop in tags) {
+ if (Object.prototype.hasOwnProperty.call(tags, prop)) {
+ ifd = ExifMapProto.ifds[prop]
+ if (ifd) {
+ subTags = tags[prop]
+ for (prop in subTags) {
+ if (Object.prototype.hasOwnProperty.call(subTags, prop)) {
+ ifd.map[subTags[prop]] = Number(prop)
+ }
+ }
+ } else {
+ ExifMapProto.map[tags[prop]] = Number(prop)
+ }
+ }
+ }
+ })()
+})
diff --git a/node_modules/blueimp-load-image/js/load-image-exif.js b/node_modules/blueimp-load-image/js/load-image-exif.js
new file mode 100644
index 0000000..7428eef
--- /dev/null
+++ b/node_modules/blueimp-load-image/js/load-image-exif.js
@@ -0,0 +1,460 @@
+/*
+ * JavaScript Load Image Exif Parser
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require, DataView */
+
+/* eslint-disable no-console */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['./load-image', './load-image-meta'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('./load-image'), require('./load-image-meta'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ /**
+ * Exif tag map
+ *
+ * @name ExifMap
+ * @class
+ * @param {number|string} tagCode IFD tag code
+ */
+ function ExifMap(tagCode) {
+ if (tagCode) {
+ Object.defineProperty(this, 'map', {
+ value: this.ifds[tagCode].map
+ })
+ Object.defineProperty(this, 'tags', {
+ value: (this.tags && this.tags[tagCode]) || {}
+ })
+ }
+ }
+
+ ExifMap.prototype.map = {
+ Orientation: 0x0112,
+ Thumbnail: 'ifd1',
+ Blob: 0x0201, // Alias for JPEGInterchangeFormat
+ Exif: 0x8769,
+ GPSInfo: 0x8825,
+ Interoperability: 0xa005
+ }
+
+ ExifMap.prototype.ifds = {
+ ifd1: { name: 'Thumbnail', map: ExifMap.prototype.map },
+ 0x8769: { name: 'Exif', map: {} },
+ 0x8825: { name: 'GPSInfo', map: {} },
+ 0xa005: { name: 'Interoperability', map: {} }
+ }
+
+ /**
+ * Retrieves exif tag value
+ *
+ * @param {number|string} id Exif tag code or name
+ * @returns {object} Exif tag value
+ */
+ ExifMap.prototype.get = function (id) {
+ return this[id] || this[this.map[id]]
+ }
+
+ /**
+ * Returns the Exif Thumbnail data as Blob.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Thumbnail data offset
+ * @param {number} length Thumbnail data length
+ * @returns {undefined|Blob} Returns the Thumbnail Blob or undefined
+ */
+ function getExifThumbnail(dataView, offset, length) {
+ if (!length) return
+ if (offset + length > dataView.byteLength) {
+ console.log('Invalid Exif data: Invalid thumbnail data.')
+ return
+ }
+ return new Blob(
+ [loadImage.bufferSlice.call(dataView.buffer, offset, offset + length)],
+ {
+ type: 'image/jpeg'
+ }
+ )
+ }
+
+ var ExifTagTypes = {
+ // byte, 8-bit unsigned int:
+ 1: {
+ getValue: function (dataView, dataOffset) {
+ return dataView.getUint8(dataOffset)
+ },
+ size: 1
+ },
+ // ascii, 8-bit byte:
+ 2: {
+ getValue: function (dataView, dataOffset) {
+ return String.fromCharCode(dataView.getUint8(dataOffset))
+ },
+ size: 1,
+ ascii: true
+ },
+ // short, 16 bit int:
+ 3: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return dataView.getUint16(dataOffset, littleEndian)
+ },
+ size: 2
+ },
+ // long, 32 bit int:
+ 4: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return dataView.getUint32(dataOffset, littleEndian)
+ },
+ size: 4
+ },
+ // rational = two long values, first is numerator, second is denominator:
+ 5: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return (
+ dataView.getUint32(dataOffset, littleEndian) /
+ dataView.getUint32(dataOffset + 4, littleEndian)
+ )
+ },
+ size: 8
+ },
+ // slong, 32 bit signed int:
+ 9: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return dataView.getInt32(dataOffset, littleEndian)
+ },
+ size: 4
+ },
+ // srational, two slongs, first is numerator, second is denominator:
+ 10: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return (
+ dataView.getInt32(dataOffset, littleEndian) /
+ dataView.getInt32(dataOffset + 4, littleEndian)
+ )
+ },
+ size: 8
+ }
+ }
+ // undefined, 8-bit byte, value depending on field:
+ ExifTagTypes[7] = ExifTagTypes[1]
+
+ /**
+ * Returns Exif tag value.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} tiffOffset TIFF offset
+ * @param {number} offset Tag offset
+ * @param {number} type Tag type
+ * @param {number} length Tag length
+ * @param {boolean} littleEndian Little endian encoding
+ * @returns {object} Tag value
+ */
+ function getExifValue(
+ dataView,
+ tiffOffset,
+ offset,
+ type,
+ length,
+ littleEndian
+ ) {
+ var tagType = ExifTagTypes[type]
+ var tagSize
+ var dataOffset
+ var values
+ var i
+ var str
+ var c
+ if (!tagType) {
+ console.log('Invalid Exif data: Invalid tag type.')
+ return
+ }
+ tagSize = tagType.size * length
+ // Determine if the value is contained in the dataOffset bytes,
+ // or if the value at the dataOffset is a pointer to the actual data:
+ dataOffset =
+ tagSize > 4
+ ? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
+ : offset + 8
+ if (dataOffset + tagSize > dataView.byteLength) {
+ console.log('Invalid Exif data: Invalid data offset.')
+ return
+ }
+ if (length === 1) {
+ return tagType.getValue(dataView, dataOffset, littleEndian)
+ }
+ values = []
+ for (i = 0; i < length; i += 1) {
+ values[i] = tagType.getValue(
+ dataView,
+ dataOffset + i * tagType.size,
+ littleEndian
+ )
+ }
+ if (tagType.ascii) {
+ str = ''
+ // Concatenate the chars:
+ for (i = 0; i < values.length; i += 1) {
+ c = values[i]
+ // Ignore the terminating NULL byte(s):
+ if (c === '\u0000') {
+ break
+ }
+ str += c
+ }
+ return str
+ }
+ return values
+ }
+
+ /**
+ * Determines if the given tag should be included.
+ *
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ * @param {number|string} tagCode Tag code to check
+ * @returns {boolean} True if the tag should be included
+ */
+ function shouldIncludeTag(includeTags, excludeTags, tagCode) {
+ return (
+ (!includeTags || includeTags[tagCode]) &&
+ (!excludeTags || excludeTags[tagCode] !== true)
+ )
+ }
+
+ /**
+ * Parses Exif tags.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} tiffOffset TIFF offset
+ * @param {number} dirOffset Directory offset
+ * @param {boolean} littleEndian Little endian encoding
+ * @param {ExifMap} tags Map to store parsed exif tags
+ * @param {ExifMap} tagOffsets Map to store parsed exif tag offsets
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ * @returns {number} Next directory offset
+ */
+ function parseExifTags(
+ dataView,
+ tiffOffset,
+ dirOffset,
+ littleEndian,
+ tags,
+ tagOffsets,
+ includeTags,
+ excludeTags
+ ) {
+ var tagsNumber, dirEndOffset, i, tagOffset, tagNumber, tagValue
+ if (dirOffset + 6 > dataView.byteLength) {
+ console.log('Invalid Exif data: Invalid directory offset.')
+ return
+ }
+ tagsNumber = dataView.getUint16(dirOffset, littleEndian)
+ dirEndOffset = dirOffset + 2 + 12 * tagsNumber
+ if (dirEndOffset + 4 > dataView.byteLength) {
+ console.log('Invalid Exif data: Invalid directory size.')
+ return
+ }
+ for (i = 0; i < tagsNumber; i += 1) {
+ tagOffset = dirOffset + 2 + 12 * i
+ tagNumber = dataView.getUint16(tagOffset, littleEndian)
+ if (!shouldIncludeTag(includeTags, excludeTags, tagNumber)) continue
+ tagValue = getExifValue(
+ dataView,
+ tiffOffset,
+ tagOffset,
+ dataView.getUint16(tagOffset + 2, littleEndian), // tag type
+ dataView.getUint32(tagOffset + 4, littleEndian), // tag length
+ littleEndian
+ )
+ tags[tagNumber] = tagValue
+ if (tagOffsets) {
+ tagOffsets[tagNumber] = tagOffset
+ }
+ }
+ // Return the offset to the next directory:
+ return dataView.getUint32(dirEndOffset, littleEndian)
+ }
+
+ /**
+ * Parses tags in a given IFD (Image File Directory).
+ *
+ * @param {object} data Data object to store exif tags and offsets
+ * @param {number|string} tagCode IFD tag code
+ * @param {DataView} dataView Data view interface
+ * @param {number} tiffOffset TIFF offset
+ * @param {boolean} littleEndian Little endian encoding
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ */
+ function parseExifIFD(
+ data,
+ tagCode,
+ dataView,
+ tiffOffset,
+ littleEndian,
+ includeTags,
+ excludeTags
+ ) {
+ var dirOffset = data.exif[tagCode]
+ if (dirOffset) {
+ data.exif[tagCode] = new ExifMap(tagCode)
+ if (data.exifOffsets) {
+ data.exifOffsets[tagCode] = new ExifMap(tagCode)
+ }
+ parseExifTags(
+ dataView,
+ tiffOffset,
+ tiffOffset + dirOffset,
+ littleEndian,
+ data.exif[tagCode],
+ data.exifOffsets && data.exifOffsets[tagCode],
+ includeTags && includeTags[tagCode],
+ excludeTags && excludeTags[tagCode]
+ )
+ }
+ }
+
+ loadImage.parseExifData = function (dataView, offset, length, data, options) {
+ if (options.disableExif) {
+ return
+ }
+ var includeTags = options.includeExifTags
+ var excludeTags = options.excludeExifTags || {
+ 0x8769: {
+ // ExifIFDPointer
+ 0x927c: true // MakerNote
+ }
+ }
+ var tiffOffset = offset + 10
+ var littleEndian
+ var dirOffset
+ var thumbnailIFD
+ // Check for the ASCII code for "Exif" (0x45786966):
+ if (dataView.getUint32(offset + 4) !== 0x45786966) {
+ // No Exif data, might be XMP data instead
+ return
+ }
+ if (tiffOffset + 8 > dataView.byteLength) {
+ console.log('Invalid Exif data: Invalid segment size.')
+ return
+ }
+ // Check for the two null bytes:
+ if (dataView.getUint16(offset + 8) !== 0x0000) {
+ console.log('Invalid Exif data: Missing byte alignment offset.')
+ return
+ }
+ // Check the byte alignment:
+ switch (dataView.getUint16(tiffOffset)) {
+ case 0x4949:
+ littleEndian = true
+ break
+ case 0x4d4d:
+ littleEndian = false
+ break
+ default:
+ console.log('Invalid Exif data: Invalid byte alignment marker.')
+ return
+ }
+ // Check for the TIFF tag marker (0x002A):
+ if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) {
+ console.log('Invalid Exif data: Missing TIFF marker.')
+ return
+ }
+ // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
+ dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
+ // Create the exif object to store the tags:
+ data.exif = new ExifMap()
+ if (!options.disableExifOffsets) {
+ data.exifOffsets = new ExifMap()
+ data.exifTiffOffset = tiffOffset
+ data.exifLittleEndian = littleEndian
+ }
+ // Parse the tags of the main image directory (IFD0) and retrieve the
+ // offset to the next directory (IFD1), usually the thumbnail directory:
+ dirOffset = parseExifTags(
+ dataView,
+ tiffOffset,
+ tiffOffset + dirOffset,
+ littleEndian,
+ data.exif,
+ data.exifOffsets,
+ includeTags,
+ excludeTags
+ )
+ if (dirOffset && shouldIncludeTag(includeTags, excludeTags, 'ifd1')) {
+ data.exif.ifd1 = dirOffset
+ if (data.exifOffsets) {
+ data.exifOffsets.ifd1 = tiffOffset + dirOffset
+ }
+ }
+ Object.keys(data.exif.ifds).forEach(function (tagCode) {
+ parseExifIFD(
+ data,
+ tagCode,
+ dataView,
+ tiffOffset,
+ littleEndian,
+ includeTags,
+ excludeTags
+ )
+ })
+ thumbnailIFD = data.exif.ifd1
+ // Check for JPEG Thumbnail offset and data length:
+ if (thumbnailIFD && thumbnailIFD[0x0201]) {
+ thumbnailIFD[0x0201] = getExifThumbnail(
+ dataView,
+ tiffOffset + thumbnailIFD[0x0201],
+ thumbnailIFD[0x0202] // Thumbnail data length
+ )
+ }
+ }
+
+ // Registers the Exif parser for the APP1 JPEG metadata segment:
+ loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
+
+ loadImage.exifWriters = {
+ // Orientation writer:
+ 0x0112: function (buffer, data, value) {
+ var orientationOffset = data.exifOffsets[0x0112]
+ if (!orientationOffset) return buffer
+ var view = new DataView(buffer, orientationOffset + 8, 2)
+ view.setUint16(0, value, data.exifLittleEndian)
+ return buffer
+ }
+ }
+
+ loadImage.writeExifData = function (buffer, data, id, value) {
+ return loadImage.exifWriters[data.exif.map[id]](buffer, data, value)
+ }
+
+ loadImage.ExifMap = ExifMap
+
+ // Adds the following properties to the parseMetaData callback data:
+ // - exif: The parsed Exif tags
+ // - exifOffsets: The parsed Exif tag offsets
+ // - exifTiffOffset: TIFF header offset (used for offset pointers)
+ // - exifLittleEndian: little endian order if true, big endian if false
+
+ // Adds the following options to the parseMetaData method:
+ // - disableExif: Disables Exif parsing when true.
+ // - disableExifOffsets: Disables storing Exif tag offsets when true.
+ // - includeExifTags: A map of Exif tags to include for parsing.
+ // - excludeExifTags: A map of Exif tags to exclude from parsing.
+})
diff --git a/node_modules/blueimp-load-image/js/load-image-fetch.js b/node_modules/blueimp-load-image/js/load-image-fetch.js
new file mode 100644
index 0000000..0d4ead6
--- /dev/null
+++ b/node_modules/blueimp-load-image/js/load-image-fetch.js
@@ -0,0 +1,103 @@
+/*
+ * JavaScript Load Image Fetch
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2017, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require, Promise */
+
+;(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
+
+ if (
+ global.fetch &&
+ global.Request &&
+ global.Response &&
+ global.Response.prototype.blob
+ ) {
+ loadImage.fetchBlob = function (url, callback, options) {
+ /**
+ * Fetch response handler.
+ *
+ * @param {Response} response Fetch response
+ * @returns {Blob} Fetched Blob.
+ */
+ function responseHandler(response) {
+ return response.blob()
+ }
+ if (global.Promise && typeof callback !== 'function') {
+ return fetch(new Request(url, callback)).then(responseHandler)
+ }
+ fetch(new Request(url, options))
+ .then(responseHandler)
+ .then(callback)
+ [
+ // Avoid parsing error in IE<9, where catch is a reserved word.
+ // eslint-disable-next-line dot-notation
+ 'catch'
+ ](function (err) {
+ callback(null, err)
+ })
+ }
+ } else if (
+ global.XMLHttpRequest &&
+ // https://xhr.spec.whatwg.org/#the-responsetype-attribute
+ new XMLHttpRequest().responseType === ''
+ ) {
+ loadImage.fetchBlob = function (url, callback, options) {
+ /**
+ * Promise executor
+ *
+ * @param {Function} resolve Resolution function
+ * @param {Function} reject Rejection function
+ */
+ function executor(resolve, reject) {
+ options = options || {} // eslint-disable-line no-param-reassign
+ var req = new XMLHttpRequest()
+ req.open(options.method || 'GET', url)
+ if (options.headers) {
+ Object.keys(options.headers).forEach(function (key) {
+ req.setRequestHeader(key, options.headers[key])
+ })
+ }
+ req.withCredentials = options.credentials === 'include'
+ req.responseType = 'blob'
+ req.onload = function () {
+ resolve(req.response)
+ }
+ req.onerror = req.onabort = req.ontimeout = function (err) {
+ if (resolve === reject) {
+ // Not using Promises
+ reject(null, err)
+ } else {
+ reject(err)
+ }
+ }
+ req.send(options.body)
+ }
+ if (global.Promise && typeof callback !== 'function') {
+ options = callback // eslint-disable-line no-param-reassign
+ return new Promise(executor)
+ }
+ return executor(callback, callback)
+ }
+ }
+})
diff --git a/node_modules/blueimp-load-image/js/load-image-iptc-map.js b/node_modules/blueimp-load-image/js/load-image-iptc-map.js
new file mode 100644
index 0000000..165d17d
--- /dev/null
+++ b/node_modules/blueimp-load-image/js/load-image-iptc-map.js
@@ -0,0 +1,169 @@
+/*
+ * JavaScript Load Image IPTC Map
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * Copyright 2018, Dave Bevan
+ *
+ * IPTC tags mapping based on
+ * https://iptc.org/standards/photo-metadata
+ * https://exiftool.org/TagNames/IPTC.html
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['./load-image', './load-image-iptc'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('./load-image'), require('./load-image-iptc'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ var IptcMapProto = loadImage.IptcMap.prototype
+
+ IptcMapProto.tags = {
+ 0: 'ApplicationRecordVersion',
+ 3: 'ObjectTypeReference',
+ 4: 'ObjectAttributeReference',
+ 5: 'ObjectName',
+ 7: 'EditStatus',
+ 8: 'EditorialUpdate',
+ 10: 'Urgency',
+ 12: 'SubjectReference',
+ 15: 'Category',
+ 20: 'SupplementalCategories',
+ 22: 'FixtureIdentifier',
+ 25: 'Keywords',
+ 26: 'ContentLocationCode',
+ 27: 'ContentLocationName',
+ 30: 'ReleaseDate',
+ 35: 'ReleaseTime',
+ 37: 'ExpirationDate',
+ 38: 'ExpirationTime',
+ 40: 'SpecialInstructions',
+ 42: 'ActionAdvised',
+ 45: 'ReferenceService',
+ 47: 'ReferenceDate',
+ 50: 'ReferenceNumber',
+ 55: 'DateCreated',
+ 60: 'TimeCreated',
+ 62: 'DigitalCreationDate',
+ 63: 'DigitalCreationTime',
+ 65: 'OriginatingProgram',
+ 70: 'ProgramVersion',
+ 75: 'ObjectCycle',
+ 80: 'Byline',
+ 85: 'BylineTitle',
+ 90: 'City',
+ 92: 'Sublocation',
+ 95: 'State',
+ 100: 'CountryCode',
+ 101: 'Country',
+ 103: 'OriginalTransmissionReference',
+ 105: 'Headline',
+ 110: 'Credit',
+ 115: 'Source',
+ 116: 'CopyrightNotice',
+ 118: 'Contact',
+ 120: 'Caption',
+ 121: 'LocalCaption',
+ 122: 'Writer',
+ 125: 'RasterizedCaption',
+ 130: 'ImageType',
+ 131: 'ImageOrientation',
+ 135: 'LanguageIdentifier',
+ 150: 'AudioType',
+ 151: 'AudioSamplingRate',
+ 152: 'AudioSamplingResolution',
+ 153: 'AudioDuration',
+ 154: 'AudioOutcue',
+ 184: 'JobID',
+ 185: 'MasterDocumentID',
+ 186: 'ShortDocumentID',
+ 187: 'UniqueDocumentID',
+ 188: 'OwnerID',
+ 200: 'ObjectPreviewFileFormat',
+ 201: 'ObjectPreviewFileVersion',
+ 202: 'ObjectPreviewData',
+ 221: 'Prefs',
+ 225: 'ClassifyState',
+ 228: 'SimilarityIndex',
+ 230: 'DocumentNotes',
+ 231: 'DocumentHistory',
+ 232: 'ExifCameraInfo',
+ 255: 'CatalogSets'
+ }
+
+ IptcMapProto.stringValues = {
+ 10: {
+ 0: '0 (reserved)',
+ 1: '1 (most urgent)',
+ 2: '2',
+ 3: '3',
+ 4: '4',
+ 5: '5 (normal urgency)',
+ 6: '6',
+ 7: '7',
+ 8: '8 (least urgent)',
+ 9: '9 (user-defined priority)'
+ },
+ 75: {
+ a: 'Morning',
+ b: 'Both Morning and Evening',
+ p: 'Evening'
+ },
+ 131: {
+ L: 'Landscape',
+ P: 'Portrait',
+ S: 'Square'
+ }
+ }
+
+ IptcMapProto.getText = function (id) {
+ var value = this.get(id)
+ var tagCode = this.map[id]
+ var stringValue = this.stringValues[tagCode]
+ if (stringValue) return stringValue[value]
+ return String(value)
+ }
+
+ IptcMapProto.getAll = function () {
+ var map = {}
+ var prop
+ var name
+ for (prop in this) {
+ if (Object.prototype.hasOwnProperty.call(this, prop)) {
+ name = this.tags[prop]
+ if (name) map[name] = this.getText(name)
+ }
+ }
+ return map
+ }
+
+ IptcMapProto.getName = function (tagCode) {
+ return this.tags[tagCode]
+ }
+
+ // Extend the map of tag names to tag codes:
+ ;(function () {
+ var tags = IptcMapProto.tags
+ var map = IptcMapProto.map || {}
+ var prop
+ // Map the tag names to tags:
+ for (prop in tags) {
+ if (Object.prototype.hasOwnProperty.call(tags, prop)) {
+ map[tags[prop]] = Number(prop)
+ }
+ }
+ })()
+})
diff --git a/node_modules/blueimp-load-image/js/load-image-iptc.js b/node_modules/blueimp-load-image/js/load-image-iptc.js
new file mode 100644
index 0000000..04eb796
--- /dev/null
+++ b/node_modules/blueimp-load-image/js/load-image-iptc.js
@@ -0,0 +1,239 @@
+/*
+ * JavaScript Load Image IPTC Parser
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * Copyright 2018, Dave Bevan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, module, require, DataView */
+
+;(function (factory) {
+ 'use strict'
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['./load-image', './load-image-meta'], factory)
+ } else if (typeof module === 'object' && module.exports) {
+ factory(require('./load-image'), require('./load-image-meta'))
+ } else {
+ // Browser globals:
+ factory(window.loadImage)
+ }
+})(function (loadImage) {
+ 'use strict'
+
+ /**
+ * IPTC tag map
+ *
+ * @name IptcMap
+ * @class
+ */
+ function IptcMap() {}
+
+ IptcMap.prototype.map = {
+ ObjectName: 5
+ }
+
+ IptcMap.prototype.types = {
+ 0: 'Uint16', // ApplicationRecordVersion
+ 200: 'Uint16', // ObjectPreviewFileFormat
+ 201: 'Uint16', // ObjectPreviewFileVersion
+ 202: 'binary' // ObjectPreviewData
+ }
+
+ /**
+ * Retrieves IPTC tag value
+ *
+ * @param {number|string} id IPTC tag code or name
+ * @returns {object} IPTC tag value
+ */
+ IptcMap.prototype.get = function (id) {
+ return this[id] || this[this.map[id]]
+ }
+
+ /**
+ * Retrieves string for the given DataView and range
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Offset start
+ * @param {number} length Offset length
+ * @returns {string} String value
+ */
+ function getStringValue(dataView, offset, length) {
+ var outstr = ''
+ var end = offset + length
+ for (var n = offset; n < end; n += 1) {
+ outstr += String.fromCharCode(dataView.getUint8(n))
+ }
+ return outstr
+ }
+
+ /**
+ * Retrieves tag value for the given DataView and range
+ *
+ * @param {number} tagCode tag code
+ * @param {IptcMap} map IPTC tag map
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Range start
+ * @param {number} length Range length
+ * @returns {object} Tag value
+ */
+ function getTagValue(tagCode, map, dataView, offset, length) {
+ if (map.types[tagCode] === 'binary') {
+ return new Blob([dataView.buffer.slice(offset, offset + length)])
+ }
+ if (map.types[tagCode] === 'Uint16') {
+ return dataView.getUint16(offset)
+ }
+ return getStringValue(dataView, offset, length)
+ }
+
+ /**
+ * Combines IPTC value with existing ones.
+ *
+ * @param {object} value Existing IPTC field value
+ * @param {object} newValue New IPTC field value
+ * @returns {object} Resulting IPTC field value
+ */
+ function combineTagValues(value, newValue) {
+ if (value === undefined) return newValue
+ if (value instanceof Array) {
+ value.push(newValue)
+ return value
+ }
+ return [value, newValue]
+ }
+
+ /**
+ * Parses IPTC tags.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} segmentOffset Segment offset
+ * @param {number} segmentLength Segment length
+ * @param {object} data Data export object
+ * @param {object} includeTags Map of tags to include
+ * @param {object} excludeTags Map of tags to exclude
+ */
+ function parseIptcTags(
+ dataView,
+ segmentOffset,
+ segmentLength,
+ data,
+ includeTags,
+ excludeTags
+ ) {
+ var value, tagSize, tagCode
+ var segmentEnd = segmentOffset + segmentLength
+ var offset = segmentOffset
+ while (offset < segmentEnd) {
+ if (
+ dataView.getUint8(offset) === 0x1c && // tag marker
+ dataView.getUint8(offset + 1) === 0x02 // record number, only handles v2
+ ) {
+ tagCode = dataView.getUint8(offset + 2)
+ if (
+ (!includeTags || includeTags[tagCode]) &&
+ (!excludeTags || !excludeTags[tagCode])
+ ) {
+ tagSize = dataView.getInt16(offset + 3)
+ value = getTagValue(tagCode, data.iptc, dataView, offset + 5, tagSize)
+ data.iptc[tagCode] = combineTagValues(data.iptc[tagCode], value)
+ if (data.iptcOffsets) {
+ data.iptcOffsets[tagCode] = offset
+ }
+ }
+ }
+ offset += 1
+ }
+ }
+
+ /**
+ * Tests if field segment starts at offset.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Segment offset
+ * @returns {boolean} True if '8BIM' exists at offset
+ */
+ function isSegmentStart(dataView, offset) {
+ return (
+ dataView.getUint32(offset) === 0x3842494d && // Photoshop segment start
+ dataView.getUint16(offset + 4) === 0x0404 // IPTC segment start
+ )
+ }
+
+ /**
+ * Returns header length.
+ *
+ * @param {DataView} dataView Data view interface
+ * @param {number} offset Segment offset
+ * @returns {number} Header length
+ */
+ function getHeaderLength(dataView, offset) {
+ var length = dataView.getUint8(offset + 7)
+ if (length % 2 !== 0) length += 1
+ // Check for pre photoshop 6 format
+ if (length === 0) {
+ // Always 4
+ length = 4
+ }
+ return length
+ }
+
+ loadImage.parseIptcData = function (dataView, offset, length, data, options) {
+ if (options.disableIptc) {
+ return
+ }
+ var markerLength = offset + length
+ while (offset + 8 < markerLength) {
+ if (isSegmentStart(dataView, offset)) {
+ var headerLength = getHeaderLength(dataView, offset)
+ var segmentOffset = offset + 8 + headerLength
+ if (segmentOffset > markerLength) {
+ // eslint-disable-next-line no-console
+ console.log('Invalid IPTC data: Invalid segment offset.')
+ break
+ }
+ var segmentLength = dataView.getUint16(offset + 6 + headerLength)
+ if (offset + segmentLength > markerLength) {
+ // eslint-disable-next-line no-console
+ console.log('Invalid IPTC data: Invalid segment size.')
+ break
+ }
+ // Create the iptc object to store the tags:
+ data.iptc = new IptcMap()
+ if (!options.disableIptcOffsets) {
+ data.iptcOffsets = new IptcMap()
+ }
+ parseIptcTags(
+ dataView,
+ segmentOffset,
+ segmentLength,
+ data,
+ options.includeIptcTags,
+ options.excludeIptcTags || { 202: true } // ObjectPreviewData
+ )
+ return
+ }
+ // eslint-disable-next-line no-param-reassign
+ offset += 1
+ }
+ }
+
+ // Registers this IPTC parser for the APP13 JPEG metadata segment:
+ loadImage.metaDataParsers.jpeg[0xffed].push(loadImage.parseIptcData)
+
+ loadImage.IptcMap = IptcMap
+
+ // Adds the following properties to the parseMetaData callback data:
+ // - iptc: The iptc tags, parsed by the parseIptcData method
+
+ // Adds the following options to the parseMetaData method:
+ // - disableIptc: Disables IPTC parsing when true.
+ // - disableIptcOffsets: Disables storing IPTC tag offsets when true.
+ // - includeIptcTags: A map of IPTC tags to include for parsing.
+ // - excludeIptcTags: A map of IPTC tags to exclude from parsing.
+})
diff --git a/node_modules/blueimp-load-image/js/load-image-meta.js b/node_modules/blueimp-load-image/js/load-image-meta.js
new file mode 100644
index 0000000..8cc60ac
--- /dev/null
+++ b/node_modules/blueimp-load-image/js/load-image-meta.js
@@ -0,0 +1,259 @@
+/*
+ * 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