)).
+
+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
-The **loadImage()** function accepts a [File](https://developer.mozilla.org/en/DOM/File) or [Blob](https://developer.mozilla.org/en/DOM/Blob) object or a simple image URL (e.g. "http://example.org/image.png") as first argument.
-If a [File](https://developer.mozilla.org/en/DOM/File) or [Blob](https://developer.mozilla.org/en/DOM/Blob) is passed as parameter, it returns a HTML **img** element if the browser supports the [URL](https://developer.mozilla.org/en/DOM/window.URL) API or a [FileReader](https://developer.mozilla.org/en/DOM/FileReader) object if supported, or **false**.
-It always returns a HTML **img** element when passing an image URL:
+### 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
-document.getElementById('file-input').onchange = function (e) {
- var loadingImage = window.loadImage(
- e.target.files[0],
- function (img) {
- document.body.appendChild(img);
- },
- {maxWidth: 600}
- );
- if (!loadingImage) {
- // Alternative code ...
+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 **img** element or [FileReader](https://developer.mozilla.org/en/DOM/FileReader) object returned by the **loadImage()** function allows to abort the loading process by setting the **onload** and **onerror** event handlers to null:
+The `exif` and `exifOffsets` objects also provide a `get()` method to retrieve
+the tag value/offset via the tag's mapped name:
```js
-document.getElementById('file-input').onchange = function (e) {
- var loadingImage = window.loadImage(
- e.target.files[0],
- function (img) {
- document.body.appendChild(img);
+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)
},
- {maxWidth: 600}
- );
- loadingImage.onload = loadingImage.onerror = null;
-};
+ { orientation: exif.get('Orientation') }
+ )
+ }
+ },
+ { meta: true }
+)
```
-The second argument 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 one argument, which is either a HTML **img** element, a [canvas](https://developer.mozilla.org/en/HTML/Canvas) element, or an [Event](https://developer.mozilla.org/en/DOM/event) object of type "**error**":
+#### Exif IFD
+
+Example code displaying data from the Exif IFD (Image File Directory) that
+contains Exif specified TIFF tags:
```js
-var imageUrl = "http://example.org/image.png";
-window.loadImage(
- imageUrl,
- function (img) {
- if(img.type === "error") {
- console.log("Error loading image " + imageUrl);
- } else {
- document.body.appendChild(img);
- }
- },
- {maxWidth: 600}
-);
+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 }
+)
```
-The optional third argument is a map of options:
+#### GPSInfo IFD
-* **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.
-* **canvas**: Defines if the returned element should be a [canvas](https://developer.mozilla.org/en/HTML/Canvas) element.
-* **noRevoke**: By default, the [created object URL](https://developer.mozilla.org/en/DOM/window.URL.createObjectURL) is revoked after the image has been loaded, except when this option is set to *true*.
+Example code displaying data from the Exif IFD (Image File Directory) that
+contains [GPS](https://en.wikipedia.org/wiki/Global_Positioning_System) info:
-They can be used the following way:
+```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
-window.loadImage(
- fileOrBlobOrUrl,
- function (img) {
- document.body.appendChild(img);
- },
- {
- maxWidth: 600,
- maxHeight: 300,
- minWidth: 100,
- minHeight: 50,
- canvas: true,
- noRevoke: true
+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
}
-);
+ }
+)
```
-All settings are optional. By default, the image is returned as HTML **img** element without any image size restrictions.
+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 script is released under the [MIT license](http://www.opensource.org/licenses/MIT).
+
+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/bin/sync-vendor-libs.sh b/bin/sync-vendor-libs.sh
new file mode 100755
index 0000000..bff8eb3
--- /dev/null
+++ b/bin/sync-vendor-libs.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+cd "$(dirname "$0")/.."
+cp node_modules/blueimp-canvas-to-blob/js/canvas-to-blob.js js/vendor/
+cp node_modules/jquery/dist/jquery.js js/vendor/
+cp node_modules/promise-polyfill/dist/polyfill.js js/vendor/promise-polyfill.js
+cp node_modules/chai/chai.js test/vendor/
+cp node_modules/mocha/mocha.js test/vendor/
+cp node_modules/mocha/mocha.css test/vendor/
diff --git a/css/demo.css b/css/demo.css
new file mode 100644
index 0000000..97e4ec5
--- /dev/null
+++ b/css/demo.css
@@ -0,0 +1,237 @@
+/*
+ * JavaScript Load Image Demo CSS
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+body {
+ max-width: 990px;
+ margin: 0 auto;
+ padding: 1em;
+ font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue',
+ Arial, sans-serif;
+ -webkit-text-size-adjust: 100%;
+ line-height: 1.4;
+ background: #212121;
+ color: #dedede;
+}
+a {
+ color: #61afef;
+ text-decoration: none;
+}
+a:visited {
+ color: #56b6c2;
+}
+a:hover {
+ color: #98c379;
+}
+table {
+ width: 100%;
+ word-wrap: break-word;
+ table-layout: fixed;
+ border-collapse: collapse;
+}
+figure {
+ margin: 0;
+ padding: 0.75em;
+ border-radius: 5px;
+ display: inline-block;
+}
+table,
+figure {
+ margin-bottom: 1.25em;
+}
+tr,
+figure {
+ background: #363636;
+}
+tr:nth-child(odd) {
+ background: #414141;
+}
+td,
+th {
+ padding: 0.5em 0.75em;
+ text-align: left;
+}
+img,
+canvas {
+ max-width: 100%;
+ border: 0;
+ vertical-align: middle;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin-top: 1.5em;
+ margin-bottom: 0.5em;
+}
+h1 {
+ margin-top: 0.5em;
+}
+label {
+ display: inline-block;
+ margin-bottom: 0.25em;
+}
+button,
+select,
+input,
+textarea {
+ -webkit-appearance: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0.5em 0.75em;
+ font-family: inherit;
+ font-size: 100%;
+ line-height: 1.4;
+ background: #414141;
+ color: #dedede;
+ border: 1px solid #363636;
+ border-radius: 5px;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.07);
+}
+input,
+textarea {
+ width: 100%;
+ box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.07);
+}
+textarea {
+ display: block;
+ overflow: auto;
+}
+button {
+ background: #3c76a7;
+ background: linear-gradient(180deg, #3c76a7, #225c8d);
+ border-color: #225c8d;
+ color: #fff;
+}
+button[type='submit'] {
+ background: #6fa349;
+ background: linear-gradient(180deg, #6fa349, #568a30);
+ border-color: #568a30;
+}
+button[type='reset'] {
+ background: #d79435;
+ background: linear-gradient(180deg, #d79435, #be7b1c);
+ border-color: #be7b1c;
+}
+select {
+ display: block;
+ padding-right: 2.25em;
+ background: #3c76a7;
+ background: url('data:image/svg+xml;charset=utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4 5"%3E%3Cpath fill="%23fff" d="M2 0L0 2h4zm0 5L0 3h4z"/%3E%3C/svg%3E')
+ no-repeat right 0.75em center/0.75em,
+ linear-gradient(180deg, #3c76a7, #225c8d);
+ border-color: #225c8d;
+ color: #fff;
+}
+button:active,
+select:active {
+ box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.5);
+}
+select::-ms-expand {
+ display: none;
+}
+option {
+ color: #212121;
+}
+input[type='checkbox'] {
+ -webkit-appearance: checkbox;
+ width: auto;
+ padding: initial;
+ box-shadow: none;
+}
+input[type='file'] {
+ max-width: 100%;
+ padding: 0;
+ background: none;
+ border: 0;
+ border-radius: 0;
+ box-shadow: none;
+}
+
+input[type='file']::-webkit-file-upload-button {
+ -webkit-appearance: none;
+ box-sizing: border-box;
+ padding: 0.5em 0.75em;
+ font-family: inherit;
+ font-size: 100%;
+ line-height: 1.4;
+ background: linear-gradient(180deg, #3c76a7, #225c8d);
+ border: 1px solid #225c8d;
+ color: #fff;
+ border-radius: 5px;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.07);
+}
+input[type='file']::-webkit-file-upload-button:active {
+ box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.5);
+}
+input[type='file']::-ms-browse {
+ box-sizing: border-box;
+ padding: 0.5em 0.75em;
+ font-family: inherit;
+ font-size: 100%;
+ line-height: 1.4;
+ background: linear-gradient(180deg, #3c76a7, #225c8d);
+ border: 1px solid #225c8d;
+ color: #fff;
+ border-radius: 5px;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.07);
+}
+input[type='file']::-ms-browse:active {
+ box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.5);
+}
+
+@media (prefers-color-scheme: light) {
+ body {
+ background: #ececec;
+ color: #212121;
+ }
+ a {
+ color: #225c8d;
+ }
+ a:visited {
+ color: #378f9a;
+ }
+ a:hover {
+ color: #6fa349;
+ }
+ figure,
+ tr {
+ background: #fff;
+ color: #212121;
+ }
+ tr:nth-child(odd) {
+ background: #f6f6f6;
+ }
+ input,
+ textarea {
+ background: #fff;
+ border-color: #d1d1d1;
+ color: #212121;
+ }
+}
+
+#result {
+ display: block;
+}
+
+@media (min-width: 540px) {
+ #navigation {
+ list-style: none;
+ padding: 0;
+ }
+ #navigation li {
+ display: inline-block;
+ }
+ #navigation li:not(:first-child)::before {
+ content: ' | ';
+ }
+}
diff --git a/css/vendor/Jcrop.gif b/css/vendor/Jcrop.gif
new file mode 100755
index 0000000..72ea7cc
Binary files /dev/null and b/css/vendor/Jcrop.gif differ
diff --git a/css/vendor/jquery.Jcrop.css b/css/vendor/jquery.Jcrop.css
new file mode 100755
index 0000000..95f8b9c
--- /dev/null
+++ b/css/vendor/jquery.Jcrop.css
@@ -0,0 +1,165 @@
+/* jquery.Jcrop.css v0.9.12 - MIT License */
+/*
+ The outer-most container in a typical Jcrop instance
+ If you are having difficulty with formatting related to styles
+ on a parent element, place any fixes here or in a like selector
+
+ You can also style this element if you want to add a border, etc
+ A better method for styling can be seen below with .jcrop-light
+ (Add a class to the holder and style elements for that extended class)
+*/
+.jcrop-holder {
+ direction: ltr;
+ text-align: left;
+}
+/* Selection Border */
+.jcrop-vline,
+.jcrop-hline {
+ background: #ffffff url("http://github.com/Jcrop.gif");
+ font-size: 0;
+ position: absolute;
+}
+.jcrop-vline {
+ height: 100%;
+ width: 1px !important;
+}
+.jcrop-vline.right {
+ right: 0;
+}
+.jcrop-hline {
+ height: 1px !important;
+ width: 100%;
+}
+.jcrop-hline.bottom {
+ bottom: 0;
+}
+/* Invisible click targets */
+.jcrop-tracker {
+ height: 100%;
+ width: 100%;
+ /* "turn off" link highlight */
+ -webkit-tap-highlight-color: transparent;
+ /* disable callout, image save panel */
+ -webkit-touch-callout: none;
+ /* disable cut copy paste */
+ -webkit-user-select: none;
+}
+/* Selection Handles */
+.jcrop-handle {
+ background-color: #333333;
+ border: 1px #eeeeee solid;
+ width: 7px;
+ height: 7px;
+ font-size: 1px;
+}
+.jcrop-handle.ord-n {
+ left: 50%;
+ margin-left: -4px;
+ margin-top: -4px;
+ top: 0;
+}
+.jcrop-handle.ord-s {
+ bottom: 0;
+ left: 50%;
+ margin-bottom: -4px;
+ margin-left: -4px;
+}
+.jcrop-handle.ord-e {
+ margin-right: -4px;
+ margin-top: -4px;
+ right: 0;
+ top: 50%;
+}
+.jcrop-handle.ord-w {
+ left: 0;
+ margin-left: -4px;
+ margin-top: -4px;
+ top: 50%;
+}
+.jcrop-handle.ord-nw {
+ left: 0;
+ margin-left: -4px;
+ margin-top: -4px;
+ top: 0;
+}
+.jcrop-handle.ord-ne {
+ margin-right: -4px;
+ margin-top: -4px;
+ right: 0;
+ top: 0;
+}
+.jcrop-handle.ord-se {
+ bottom: 0;
+ margin-bottom: -4px;
+ margin-right: -4px;
+ right: 0;
+}
+.jcrop-handle.ord-sw {
+ bottom: 0;
+ left: 0;
+ margin-bottom: -4px;
+ margin-left: -4px;
+}
+/* Dragbars */
+.jcrop-dragbar.ord-n,
+.jcrop-dragbar.ord-s {
+ height: 7px;
+ width: 100%;
+}
+.jcrop-dragbar.ord-e,
+.jcrop-dragbar.ord-w {
+ height: 100%;
+ width: 7px;
+}
+.jcrop-dragbar.ord-n {
+ margin-top: -4px;
+}
+.jcrop-dragbar.ord-s {
+ bottom: 0;
+ margin-bottom: -4px;
+}
+.jcrop-dragbar.ord-e {
+ margin-right: -4px;
+ right: 0;
+}
+.jcrop-dragbar.ord-w {
+ margin-left: -4px;
+}
+/* The "jcrop-light" class/extension */
+.jcrop-light .jcrop-vline,
+.jcrop-light .jcrop-hline {
+ background: #ffffff;
+ filter: alpha(opacity=70) !important;
+ opacity: .70!important;
+}
+.jcrop-light .jcrop-handle {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ background-color: #000000;
+ border-color: #ffffff;
+ border-radius: 3px;
+}
+/* The "jcrop-dark" class/extension */
+.jcrop-dark .jcrop-vline,
+.jcrop-dark .jcrop-hline {
+ background: #000000;
+ filter: alpha(opacity=70) !important;
+ opacity: 0.7 !important;
+}
+.jcrop-dark .jcrop-handle {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ background-color: #ffffff;
+ border-color: #000000;
+ border-radius: 3px;
+}
+/* Simple macro to turn off the antlines */
+.solid-line .jcrop-vline,
+.solid-line .jcrop-hline {
+ background: #ffffff;
+}
+/* Fix for twitter bootstrap et al. */
+.jcrop-holder img,
+img.jcrop-preview {
+ max-width: none;
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..6bba835
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,15 @@
+version: '3.7'
+services:
+ nginx:
+ image: nginx:alpine
+ ports:
+ - 127.0.0.1:80:80
+ volumes:
+ - .:/usr/share/nginx/html:ro
+ mocha:
+ image: blueimp/mocha-chrome
+ command: http://nginx/test
+ environment:
+ - WAIT_FOR_HOSTS=nginx:80
+ depends_on:
+ - nginx
diff --git a/index.html b/index.html
index 7440537..05d5ac0 100644
--- a/index.html
+++ b/index.html
@@ -1,118 +1,158 @@
-
+
-
-
-
-JavaScript Load Image
-
-
-
-
-
-
-
-
-
-
-
-
-
- JavaScript Load Image is a function to load images provided as File or Blob objects or via URL.
- It returns an optionally scaled HTML img or canvas element.
-
-
-
-
-
Select an image file
-
-
Notice Or drag & drop an image file onto this webpage.
-
-
-
Result
-
Notice This demo works only in browsers with support for the URL or FileReader API.
-
-
-
-
-
-
-
-
+
+
+
+
+ JavaScript Load Image Demo
+
+ 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 and
+ Exif tags as well as
+ embedded thumbnail images, to overwrite the Exif Orientation value and to
+ restore the complete image header after resizing.
+
+
+ File input
+
+ Select an image file:
+
+
+
+ Or enter an image URL into the following field:
+
+
+ Or drag & drop an image file onto this webpage.
+ Options
+
+ Orientation:
+
+ undefined: Browser default
+ true: Automatic
+ 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
+
+
+
+
+ Image smoothing
+
+ Result
+
+ Edit
+ Crop
+ Cancel
+
+
+
+ Loading images from File objects requires support for the
+ URL
+ or
+ FileReader
+ API.
+
+
+
+
Image metadata
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+