├── .github
├── FUNDING.yml
└── workflows
│ └── test.yml
├── .gitignore
├── LICENSE.txt
├── README.md
├── bin
└── sync-vendor-libs.sh
├── css
├── demo.css
└── vendor
│ ├── Jcrop.gif
│ └── jquery.Jcrop.css
├── docker-compose.yml
├── index.html
├── js
├── demo
│ └── demo.js
├── index.js
├── load-image-exif-map.js
├── load-image-exif.js
├── load-image-fetch.js
├── load-image-iptc-map.js
├── load-image-iptc.js
├── load-image-meta.js
├── load-image-orientation.js
├── load-image-scale.js
├── load-image.all.min.js
├── load-image.all.min.js.map
├── load-image.js
└── vendor
│ ├── canvas-to-blob.js
│ ├── jquery.Jcrop.js
│ ├── jquery.js
│ └── promise-polyfill.js
├── package-lock.json
├── package.json
└── test
├── index.html
├── test.js
└── vendor
├── chai.js
├── mocha.css
└── mocha.js
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [blueimp]
2 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | lint:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | node-version: [14, 16]
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: actions/setup-node@v2
14 | with:
15 | node-version: ${{ matrix.node-version }}
16 | - run: npm install
17 | - run: npm run lint
18 |
19 | unit:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@v2
23 | - name: mocha
24 | run: docker-compose run --rm mocha
25 | - name: docker-compose logs
26 | if: always()
27 | run: docker-compose logs nginx
28 | - name: docker-compose down
29 | if: always()
30 | run: docker-compose down -v
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © 2011 Sebastian Tschan, https://blueimp.net
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/bin/sync-vendor-libs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | cd "$(dirname "$0")/.."
3 | cp node_modules/blueimp-canvas-to-blob/js/canvas-to-blob.js js/vendor/
4 | cp node_modules/jquery/dist/jquery.js js/vendor/
5 | cp node_modules/promise-polyfill/dist/polyfill.js js/vendor/promise-polyfill.js
6 | cp node_modules/chai/chai.js test/vendor/
7 | cp node_modules/mocha/mocha.js test/vendor/
8 | cp node_modules/mocha/mocha.css test/vendor/
9 |
--------------------------------------------------------------------------------
/css/demo.css:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript Load Image Demo CSS
3 | * https://github.com/blueimp/JavaScript-Load-Image
4 | *
5 | * Copyright 2013, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Licensed under the MIT license:
9 | * https://opensource.org/licenses/MIT
10 | */
11 |
12 | body {
13 | max-width: 990px;
14 | margin: 0 auto;
15 | padding: 1em;
16 | font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue',
17 | Arial, sans-serif;
18 | -webkit-text-size-adjust: 100%;
19 | line-height: 1.4;
20 | background: #212121;
21 | color: #dedede;
22 | }
23 | a {
24 | color: #61afef;
25 | text-decoration: none;
26 | }
27 | a:visited {
28 | color: #56b6c2;
29 | }
30 | a:hover {
31 | color: #98c379;
32 | }
33 | table {
34 | width: 100%;
35 | word-wrap: break-word;
36 | table-layout: fixed;
37 | border-collapse: collapse;
38 | }
39 | figure {
40 | margin: 0;
41 | padding: 0.75em;
42 | border-radius: 5px;
43 | display: inline-block;
44 | }
45 | table,
46 | figure {
47 | margin-bottom: 1.25em;
48 | }
49 | tr,
50 | figure {
51 | background: #363636;
52 | }
53 | tr:nth-child(odd) {
54 | background: #414141;
55 | }
56 | td,
57 | th {
58 | padding: 0.5em 0.75em;
59 | text-align: left;
60 | }
61 | img,
62 | canvas {
63 | max-width: 100%;
64 | border: 0;
65 | vertical-align: middle;
66 | }
67 | h1,
68 | h2,
69 | h3,
70 | h4,
71 | h5,
72 | h6 {
73 | margin-top: 1.5em;
74 | margin-bottom: 0.5em;
75 | }
76 | h1 {
77 | margin-top: 0.5em;
78 | }
79 | label {
80 | display: inline-block;
81 | margin-bottom: 0.25em;
82 | }
83 | button,
84 | select,
85 | input,
86 | textarea {
87 | -webkit-appearance: none;
88 | box-sizing: border-box;
89 | margin: 0;
90 | padding: 0.5em 0.75em;
91 | font-family: inherit;
92 | font-size: 100%;
93 | line-height: 1.4;
94 | background: #414141;
95 | color: #dedede;
96 | border: 1px solid #363636;
97 | border-radius: 5px;
98 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.07);
99 | }
100 | input,
101 | textarea {
102 | width: 100%;
103 | box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.07);
104 | }
105 | textarea {
106 | display: block;
107 | overflow: auto;
108 | }
109 | button {
110 | background: #3c76a7;
111 | background: linear-gradient(180deg, #3c76a7, #225c8d);
112 | border-color: #225c8d;
113 | color: #fff;
114 | }
115 | button[type='submit'] {
116 | background: #6fa349;
117 | background: linear-gradient(180deg, #6fa349, #568a30);
118 | border-color: #568a30;
119 | }
120 | button[type='reset'] {
121 | background: #d79435;
122 | background: linear-gradient(180deg, #d79435, #be7b1c);
123 | border-color: #be7b1c;
124 | }
125 | select {
126 | display: block;
127 | padding-right: 2.25em;
128 | background: #3c76a7;
129 | 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')
130 | no-repeat right 0.75em center/0.75em,
131 | linear-gradient(180deg, #3c76a7, #225c8d);
132 | border-color: #225c8d;
133 | color: #fff;
134 | }
135 | button:active,
136 | select:active {
137 | box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.5);
138 | }
139 | select::-ms-expand {
140 | display: none;
141 | }
142 | option {
143 | color: #212121;
144 | }
145 | input[type='checkbox'] {
146 | -webkit-appearance: checkbox;
147 | width: auto;
148 | padding: initial;
149 | box-shadow: none;
150 | }
151 | input[type='file'] {
152 | max-width: 100%;
153 | padding: 0;
154 | background: none;
155 | border: 0;
156 | border-radius: 0;
157 | box-shadow: none;
158 | }
159 |
160 | input[type='file']::-webkit-file-upload-button {
161 | -webkit-appearance: none;
162 | box-sizing: border-box;
163 | padding: 0.5em 0.75em;
164 | font-family: inherit;
165 | font-size: 100%;
166 | line-height: 1.4;
167 | background: linear-gradient(180deg, #3c76a7, #225c8d);
168 | border: 1px solid #225c8d;
169 | color: #fff;
170 | border-radius: 5px;
171 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.07);
172 | }
173 | input[type='file']::-webkit-file-upload-button:active {
174 | box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.5);
175 | }
176 | input[type='file']::-ms-browse {
177 | box-sizing: border-box;
178 | padding: 0.5em 0.75em;
179 | font-family: inherit;
180 | font-size: 100%;
181 | line-height: 1.4;
182 | background: linear-gradient(180deg, #3c76a7, #225c8d);
183 | border: 1px solid #225c8d;
184 | color: #fff;
185 | border-radius: 5px;
186 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.07);
187 | }
188 | input[type='file']::-ms-browse:active {
189 | box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.5);
190 | }
191 |
192 | @media (prefers-color-scheme: light) {
193 | body {
194 | background: #ececec;
195 | color: #212121;
196 | }
197 | a {
198 | color: #225c8d;
199 | }
200 | a:visited {
201 | color: #378f9a;
202 | }
203 | a:hover {
204 | color: #6fa349;
205 | }
206 | figure,
207 | tr {
208 | background: #fff;
209 | color: #212121;
210 | }
211 | tr:nth-child(odd) {
212 | background: #f6f6f6;
213 | }
214 | input,
215 | textarea {
216 | background: #fff;
217 | border-color: #d1d1d1;
218 | color: #212121;
219 | }
220 | }
221 |
222 | #result {
223 | display: block;
224 | }
225 |
226 | @media (min-width: 540px) {
227 | #navigation {
228 | list-style: none;
229 | padding: 0;
230 | }
231 | #navigation li {
232 | display: inline-block;
233 | }
234 | #navigation li:not(:first-child)::before {
235 | content: ' | ';
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/css/vendor/Jcrop.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blueimp/JavaScript-Load-Image/5d34ed4ce3472ecb22d920c66cc4d7604526ff24/css/vendor/Jcrop.gif
--------------------------------------------------------------------------------
/css/vendor/jquery.Jcrop.css:
--------------------------------------------------------------------------------
1 | /* jquery.Jcrop.css v0.9.12 - MIT License */
2 | /*
3 | The outer-most container in a typical Jcrop instance
4 | If you are having difficulty with formatting related to styles
5 | on a parent element, place any fixes here or in a like selector
6 |
7 | You can also style this element if you want to add a border, etc
8 | A better method for styling can be seen below with .jcrop-light
9 | (Add a class to the holder and style elements for that extended class)
10 | */
11 | .jcrop-holder {
12 | direction: ltr;
13 | text-align: left;
14 | }
15 | /* Selection Border */
16 | .jcrop-vline,
17 | .jcrop-hline {
18 | background: #ffffff url("Jcrop.gif");
19 | font-size: 0;
20 | position: absolute;
21 | }
22 | .jcrop-vline {
23 | height: 100%;
24 | width: 1px !important;
25 | }
26 | .jcrop-vline.right {
27 | right: 0;
28 | }
29 | .jcrop-hline {
30 | height: 1px !important;
31 | width: 100%;
32 | }
33 | .jcrop-hline.bottom {
34 | bottom: 0;
35 | }
36 | /* Invisible click targets */
37 | .jcrop-tracker {
38 | height: 100%;
39 | width: 100%;
40 | /* "turn off" link highlight */
41 | -webkit-tap-highlight-color: transparent;
42 | /* disable callout, image save panel */
43 | -webkit-touch-callout: none;
44 | /* disable cut copy paste */
45 | -webkit-user-select: none;
46 | }
47 | /* Selection Handles */
48 | .jcrop-handle {
49 | background-color: #333333;
50 | border: 1px #eeeeee solid;
51 | width: 7px;
52 | height: 7px;
53 | font-size: 1px;
54 | }
55 | .jcrop-handle.ord-n {
56 | left: 50%;
57 | margin-left: -4px;
58 | margin-top: -4px;
59 | top: 0;
60 | }
61 | .jcrop-handle.ord-s {
62 | bottom: 0;
63 | left: 50%;
64 | margin-bottom: -4px;
65 | margin-left: -4px;
66 | }
67 | .jcrop-handle.ord-e {
68 | margin-right: -4px;
69 | margin-top: -4px;
70 | right: 0;
71 | top: 50%;
72 | }
73 | .jcrop-handle.ord-w {
74 | left: 0;
75 | margin-left: -4px;
76 | margin-top: -4px;
77 | top: 50%;
78 | }
79 | .jcrop-handle.ord-nw {
80 | left: 0;
81 | margin-left: -4px;
82 | margin-top: -4px;
83 | top: 0;
84 | }
85 | .jcrop-handle.ord-ne {
86 | margin-right: -4px;
87 | margin-top: -4px;
88 | right: 0;
89 | top: 0;
90 | }
91 | .jcrop-handle.ord-se {
92 | bottom: 0;
93 | margin-bottom: -4px;
94 | margin-right: -4px;
95 | right: 0;
96 | }
97 | .jcrop-handle.ord-sw {
98 | bottom: 0;
99 | left: 0;
100 | margin-bottom: -4px;
101 | margin-left: -4px;
102 | }
103 | /* Dragbars */
104 | .jcrop-dragbar.ord-n,
105 | .jcrop-dragbar.ord-s {
106 | height: 7px;
107 | width: 100%;
108 | }
109 | .jcrop-dragbar.ord-e,
110 | .jcrop-dragbar.ord-w {
111 | height: 100%;
112 | width: 7px;
113 | }
114 | .jcrop-dragbar.ord-n {
115 | margin-top: -4px;
116 | }
117 | .jcrop-dragbar.ord-s {
118 | bottom: 0;
119 | margin-bottom: -4px;
120 | }
121 | .jcrop-dragbar.ord-e {
122 | margin-right: -4px;
123 | right: 0;
124 | }
125 | .jcrop-dragbar.ord-w {
126 | margin-left: -4px;
127 | }
128 | /* The "jcrop-light" class/extension */
129 | .jcrop-light .jcrop-vline,
130 | .jcrop-light .jcrop-hline {
131 | background: #ffffff;
132 | filter: alpha(opacity=70) !important;
133 | opacity: .70!important;
134 | }
135 | .jcrop-light .jcrop-handle {
136 | -moz-border-radius: 3px;
137 | -webkit-border-radius: 3px;
138 | background-color: #000000;
139 | border-color: #ffffff;
140 | border-radius: 3px;
141 | }
142 | /* The "jcrop-dark" class/extension */
143 | .jcrop-dark .jcrop-vline,
144 | .jcrop-dark .jcrop-hline {
145 | background: #000000;
146 | filter: alpha(opacity=70) !important;
147 | opacity: 0.7 !important;
148 | }
149 | .jcrop-dark .jcrop-handle {
150 | -moz-border-radius: 3px;
151 | -webkit-border-radius: 3px;
152 | background-color: #ffffff;
153 | border-color: #000000;
154 | border-radius: 3px;
155 | }
156 | /* Simple macro to turn off the antlines */
157 | .solid-line .jcrop-vline,
158 | .solid-line .jcrop-hline {
159 | background: #ffffff;
160 | }
161 | /* Fix for twitter bootstrap et al. */
162 | .jcrop-holder img,
163 | img.jcrop-preview {
164 | max-width: none;
165 | }
166 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 | services:
3 | nginx:
4 | image: nginx:alpine
5 | ports:
6 | - 127.0.0.1:80:80
7 | volumes:
8 | - .:/usr/share/nginx/html:ro
9 | mocha:
10 | image: blueimp/mocha-chrome
11 | command: http://nginx/test
12 | environment:
13 | - WAIT_FOR_HOSTS=nginx:80
14 | depends_on:
15 | - nginx
16 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
19 |
20 | JavaScript Load Image
21 |
25 |
26 |
29 |
30 |
31 |
39 |
40 |
41 | JavaScript Load Image Demo
42 |
43 | JavaScript Load Image
46 | is a library to load images provided as
47 | File
48 | or
49 | Blob
50 | objects or via URL.
51 | It returns an optionally scaled ,
52 | cropped or rotated HTML
53 | img
56 | or
57 | canvas
60 | element.
61 |
62 |
63 | It also provides methods to parse image metadata to extract
64 | IPTC and
65 | Exif tags as well as
66 | embedded thumbnail images, to overwrite the Exif Orientation value and to
67 | restore the complete image header after resizing.
68 |
69 |
89 | File input
90 |
91 | Select an image file:
92 |
93 |
94 |
95 | Or enter an image URL into the following field:
96 |
97 |
98 | Or drag & drop an image file onto this webpage.
99 | Options
100 |
101 | Orientation:
102 |
103 | undefined: Browser default
104 | true: Automatic
105 | 1: Original
106 | 2: Horizontal flip
107 | 3: Rotate 180° CCW
108 | 4: Vertical flip
109 | 5: Vertical flip + Rotate 90° CW
110 | 6: Rotate 90° CW
111 | 7: Horizontal flip + Rotate 90° CW
112 | 8: Rotate 90° CCW
113 |
114 |
115 |
116 |
117 | Image smoothing
118 |
119 | Result
120 |
121 | Edit
122 | Crop
123 | Cancel
124 |
125 |
126 |
127 | Loading images from File objects requires support for the
128 | URL
129 | or
130 | FileReader
133 | API.
134 |
135 |
136 |
137 |
Image metadata
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/js/demo/demo.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript Load Image Demo JS
3 | * https://github.com/blueimp/JavaScript-Load-Image
4 | *
5 | * Copyright 2013, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Licensed under the MIT license:
9 | * https://opensource.org/licenses/MIT
10 | */
11 |
12 | /* global loadImage, $ */
13 |
14 | $(function () {
15 | 'use strict'
16 |
17 | var resultNode = $('#result')
18 | var metaNode = $('#meta')
19 | var thumbNode = $('#thumbnail')
20 | var actionsNode = $('#actions')
21 | var orientationNode = $('#orientation')
22 | var imageSmoothingNode = $('#image-smoothing')
23 | var fileInputNode = $('#file-input')
24 | var urlNode = $('#url')
25 | var editNode = $('#edit')
26 | var cropNode = $('#crop')
27 | var cancelNode = $('#cancel')
28 | var coordinates
29 | var jcropAPI
30 |
31 | /**
32 | * Displays tag data
33 | *
34 | * @param {*} node jQuery node
35 | * @param {object} tags Tags map
36 | * @param {string} title Tags title
37 | */
38 | function displayTagData(node, tags, title) {
39 | var table = $('')
40 | var row = $(' ')
41 | var cell = $(' ')
42 | var headerCell = $(' ')
43 | var prop
44 | table.append(row.clone().append(headerCell.clone().text(title)))
45 | for (prop in tags) {
46 | if (Object.prototype.hasOwnProperty.call(tags, prop)) {
47 | if (typeof tags[prop] === 'object') {
48 | displayTagData(node, tags[prop], prop)
49 | continue
50 | }
51 | table.append(
52 | row
53 | .clone()
54 | .append(cell.clone().text(prop))
55 | .append(cell.clone().text(tags[prop]))
56 | )
57 | }
58 | }
59 | node.append(table).show()
60 | }
61 |
62 | /**
63 | * Displays the thumbnal image
64 | *
65 | * @param {*} node jQuery node
66 | * @param {string} thumbnail Thumbnail URL
67 | * @param {object} [options] Options object
68 | */
69 | function displayThumbnailImage(node, thumbnail, options) {
70 | if (thumbnail) {
71 | var link = $(' ')
72 | .attr('href', loadImage.createObjectURL(thumbnail))
73 | .attr('download', 'thumbnail.jpg')
74 | .appendTo(node)
75 | loadImage(
76 | thumbnail,
77 | function (img) {
78 | link.append(img)
79 | node.show()
80 | },
81 | options
82 | )
83 | }
84 | }
85 |
86 | /**
87 | * Displays metadata
88 | *
89 | * @param {object} [data] Metadata object
90 | */
91 | function displayMetaData(data) {
92 | if (!data) return
93 | metaNode.data(data)
94 | var exif = data.exif
95 | var iptc = data.iptc
96 | if (exif) {
97 | var thumbnail = exif.get('Thumbnail')
98 | if (thumbnail) {
99 | displayThumbnailImage(thumbNode, thumbnail.get('Blob'), {
100 | orientation: exif.get('Orientation')
101 | })
102 | }
103 | displayTagData(metaNode, exif.getAll(), 'TIFF')
104 | }
105 | if (iptc) {
106 | displayTagData(metaNode, iptc.getAll(), 'IPTC')
107 | }
108 | }
109 |
110 | /**
111 | * Removes meta data from the page
112 | */
113 | function removeMetaData() {
114 | metaNode.hide().removeData().find('table').remove()
115 | thumbNode.hide().empty()
116 | }
117 |
118 | /**
119 | * Updates the results view
120 | *
121 | * @param {*} img Image or canvas element
122 | * @param {object} [data] Metadata object
123 | * @param {boolean} [keepMetaData] Keep meta data if true
124 | */
125 | function updateResults(img, data, keepMetaData) {
126 | var isCanvas = window.HTMLCanvasElement && img instanceof HTMLCanvasElement
127 | if (!keepMetaData) {
128 | removeMetaData()
129 | if (data) {
130 | displayMetaData(data)
131 | }
132 | if (isCanvas) {
133 | actionsNode.show()
134 | } else {
135 | actionsNode.hide()
136 | }
137 | }
138 | if (!(isCanvas || img.src)) {
139 | resultNode
140 | .children()
141 | .replaceWith($('Loading image file failed '))
142 | return
143 | }
144 | var content = $(' ').append(img)
145 | resultNode.children().replaceWith(content)
146 | if (data.imageHead) {
147 | if (data.exif) {
148 | // Reset Exif Orientation data:
149 | loadImage.writeExifData(data.imageHead, data, 'Orientation', 1)
150 | }
151 | img.toBlob(function (blob) {
152 | if (!blob) return
153 | loadImage.replaceHead(blob, data.imageHead, function (newBlob) {
154 | content
155 | .attr('href', loadImage.createObjectURL(newBlob))
156 | .attr('download', 'image.jpg')
157 | })
158 | }, 'image/jpeg')
159 | }
160 | }
161 |
162 | /**
163 | * Displays the image
164 | *
165 | * @param {File|Blob|string} file File or Blob object or image URL
166 | */
167 | function displayImage(file) {
168 | var options = {
169 | maxWidth: resultNode.width(),
170 | canvas: true,
171 | pixelRatio: window.devicePixelRatio,
172 | downsamplingRatio: 0.5,
173 | orientation: Number(orientationNode.val()) || true,
174 | imageSmoothingEnabled: imageSmoothingNode.is(':checked'),
175 | meta: true
176 | }
177 | if (!loadImage(file, updateResults, options)) {
178 | removeMetaData()
179 | resultNode
180 | .children()
181 | .replaceWith(
182 | $(
183 | '' +
184 | 'Your browser does not support the URL or FileReader API.' +
185 | ' '
186 | )
187 | )
188 | }
189 | }
190 |
191 | /**
192 | * Handles drop and file selection change events
193 | *
194 | * @param {event} event Drop or file selection change event
195 | */
196 | function fileChangeHandler(event) {
197 | event.preventDefault()
198 | var originalEvent = event.originalEvent
199 | var target = originalEvent.dataTransfer || originalEvent.target
200 | var file = target && target.files && target.files[0]
201 | if (!file) {
202 | return
203 | }
204 | displayImage(file)
205 | }
206 |
207 | /**
208 | * Handles URL change events
209 | */
210 | function urlChangeHandler() {
211 | var url = $(this).val()
212 | if (url) displayImage(url)
213 | }
214 |
215 | // Show the URL/FileReader API requirement message if not supported:
216 | if (
217 | window.createObjectURL ||
218 | window.URL ||
219 | window.webkitURL ||
220 | window.FileReader
221 | ) {
222 | resultNode.children().hide()
223 | } else {
224 | resultNode.children().show()
225 | }
226 |
227 | $(document)
228 | .on('dragover', function (e) {
229 | e.preventDefault()
230 | if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy'
231 | })
232 | .on('drop', fileChangeHandler)
233 |
234 | fileInputNode.on('change', fileChangeHandler)
235 |
236 | urlNode.on('change paste input', urlChangeHandler)
237 |
238 | orientationNode.on('change', function () {
239 | var img = resultNode.find('img, canvas')[0]
240 | if (img) {
241 | updateResults(
242 | loadImage.scale(img, {
243 | maxWidth: resultNode.width() * (window.devicePixelRatio || 1),
244 | pixelRatio: window.devicePixelRatio,
245 | orientation: Number(orientationNode.val()) || true,
246 | imageSmoothingEnabled: imageSmoothingNode.is(':checked')
247 | }),
248 | metaNode.data(),
249 | true
250 | )
251 | }
252 | })
253 |
254 | editNode.on('click', function (event) {
255 | event.preventDefault()
256 | var imgNode = resultNode.find('img, canvas')
257 | var img = imgNode[0]
258 | var pixelRatio = window.devicePixelRatio || 1
259 | var margin = img.width / pixelRatio >= 140 ? 40 : 0
260 | imgNode
261 | // eslint-disable-next-line new-cap
262 | .Jcrop(
263 | {
264 | setSelect: [
265 | margin,
266 | margin,
267 | img.width / pixelRatio - margin,
268 | img.height / pixelRatio - margin
269 | ],
270 | onSelect: function (coords) {
271 | coordinates = coords
272 | },
273 | onRelease: function () {
274 | coordinates = null
275 | }
276 | },
277 | function () {
278 | jcropAPI = this
279 | }
280 | )
281 | .parent()
282 | .on('click', function (event) {
283 | event.preventDefault()
284 | })
285 | })
286 |
287 | cropNode.on('click', function (event) {
288 | event.preventDefault()
289 | var img = resultNode.find('img, canvas')[0]
290 | var pixelRatio = window.devicePixelRatio || 1
291 | if (img && coordinates) {
292 | updateResults(
293 | loadImage.scale(img, {
294 | left: coordinates.x * pixelRatio,
295 | top: coordinates.y * pixelRatio,
296 | sourceWidth: coordinates.w * pixelRatio,
297 | sourceHeight: coordinates.h * pixelRatio,
298 | maxWidth: resultNode.width() * pixelRatio,
299 | contain: true,
300 | pixelRatio: pixelRatio,
301 | imageSmoothingEnabled: imageSmoothingNode.is(':checked')
302 | }),
303 | metaNode.data(),
304 | true
305 | )
306 | coordinates = null
307 | }
308 | })
309 |
310 | cancelNode.on('click', function (event) {
311 | event.preventDefault()
312 | if (jcropAPI) {
313 | jcropAPI.release()
314 | jcropAPI.disable()
315 | }
316 | })
317 | })
318 |
--------------------------------------------------------------------------------
/js/index.js:
--------------------------------------------------------------------------------
1 | /* global module, require */
2 |
3 | module.exports = require('./load-image')
4 |
5 | require('./load-image-scale')
6 | require('./load-image-meta')
7 | require('./load-image-fetch')
8 | require('./load-image-exif')
9 | require('./load-image-exif-map')
10 | require('./load-image-iptc')
11 | require('./load-image-iptc-map')
12 | require('./load-image-orientation')
13 |
--------------------------------------------------------------------------------
/js/load-image-exif-map.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript Load Image Exif Map
3 | * https://github.com/blueimp/JavaScript-Load-Image
4 | *
5 | * Copyright 2013, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Exif tags mapping based on
9 | * https://github.com/jseidelin/exif-js
10 | *
11 | * Licensed under the MIT license:
12 | * https://opensource.org/licenses/MIT
13 | */
14 |
15 | /* global define, module, require */
16 |
17 | ;(function (factory) {
18 | 'use strict'
19 | if (typeof define === 'function' && define.amd) {
20 | // Register as an anonymous AMD module:
21 | define(['./load-image', './load-image-exif'], factory)
22 | } else if (typeof module === 'object' && module.exports) {
23 | factory(require('./load-image'), require('./load-image-exif'))
24 | } else {
25 | // Browser globals:
26 | factory(window.loadImage)
27 | }
28 | })(function (loadImage) {
29 | 'use strict'
30 |
31 | var ExifMapProto = loadImage.ExifMap.prototype
32 |
33 | ExifMapProto.tags = {
34 | // =================
35 | // TIFF tags (IFD0):
36 | // =================
37 | 0x0100: 'ImageWidth',
38 | 0x0101: 'ImageHeight',
39 | 0x0102: 'BitsPerSample',
40 | 0x0103: 'Compression',
41 | 0x0106: 'PhotometricInterpretation',
42 | 0x0112: 'Orientation',
43 | 0x0115: 'SamplesPerPixel',
44 | 0x011c: 'PlanarConfiguration',
45 | 0x0212: 'YCbCrSubSampling',
46 | 0x0213: 'YCbCrPositioning',
47 | 0x011a: 'XResolution',
48 | 0x011b: 'YResolution',
49 | 0x0128: 'ResolutionUnit',
50 | 0x0111: 'StripOffsets',
51 | 0x0116: 'RowsPerStrip',
52 | 0x0117: 'StripByteCounts',
53 | 0x0201: 'JPEGInterchangeFormat',
54 | 0x0202: 'JPEGInterchangeFormatLength',
55 | 0x012d: 'TransferFunction',
56 | 0x013e: 'WhitePoint',
57 | 0x013f: 'PrimaryChromaticities',
58 | 0x0211: 'YCbCrCoefficients',
59 | 0x0214: 'ReferenceBlackWhite',
60 | 0x0132: 'DateTime',
61 | 0x010e: 'ImageDescription',
62 | 0x010f: 'Make',
63 | 0x0110: 'Model',
64 | 0x0131: 'Software',
65 | 0x013b: 'Artist',
66 | 0x8298: 'Copyright',
67 | 0x8769: {
68 | // ExifIFDPointer
69 | 0x9000: 'ExifVersion', // EXIF version
70 | 0xa000: 'FlashpixVersion', // Flashpix format version
71 | 0xa001: 'ColorSpace', // Color space information tag
72 | 0xa002: 'PixelXDimension', // Valid width of meaningful image
73 | 0xa003: 'PixelYDimension', // Valid height of meaningful image
74 | 0xa500: 'Gamma',
75 | 0x9101: 'ComponentsConfiguration', // Information about channels
76 | 0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel
77 | 0x927c: 'MakerNote', // Any desired information written by the manufacturer
78 | 0x9286: 'UserComment', // Comments by user
79 | 0xa004: 'RelatedSoundFile', // Name of related sound file
80 | 0x9003: 'DateTimeOriginal', // Date and time when the original image was generated
81 | 0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally
82 | 0x9010: 'OffsetTime', // Time zone when the image file was last changed
83 | 0x9011: 'OffsetTimeOriginal', // Time zone when the image was stored digitally
84 | 0x9012: 'OffsetTimeDigitized', // Time zone when the image was stored digitally
85 | 0x9290: 'SubSecTime', // Fractions of seconds for DateTime
86 | 0x9291: 'SubSecTimeOriginal', // Fractions of seconds for DateTimeOriginal
87 | 0x9292: 'SubSecTimeDigitized', // Fractions of seconds for DateTimeDigitized
88 | 0x829a: 'ExposureTime', // Exposure time (in seconds)
89 | 0x829d: 'FNumber',
90 | 0x8822: 'ExposureProgram', // Exposure program
91 | 0x8824: 'SpectralSensitivity', // Spectral sensitivity
92 | 0x8827: 'PhotographicSensitivity', // EXIF 2.3, ISOSpeedRatings in EXIF 2.2
93 | 0x8828: 'OECF', // Optoelectric conversion factor
94 | 0x8830: 'SensitivityType',
95 | 0x8831: 'StandardOutputSensitivity',
96 | 0x8832: 'RecommendedExposureIndex',
97 | 0x8833: 'ISOSpeed',
98 | 0x8834: 'ISOSpeedLatitudeyyy',
99 | 0x8835: 'ISOSpeedLatitudezzz',
100 | 0x9201: 'ShutterSpeedValue', // Shutter speed
101 | 0x9202: 'ApertureValue', // Lens aperture
102 | 0x9203: 'BrightnessValue', // Value of brightness
103 | 0x9204: 'ExposureBias', // Exposure bias
104 | 0x9205: 'MaxApertureValue', // Smallest F number of lens
105 | 0x9206: 'SubjectDistance', // Distance to subject in meters
106 | 0x9207: 'MeteringMode', // Metering mode
107 | 0x9208: 'LightSource', // Kind of light source
108 | 0x9209: 'Flash', // Flash status
109 | 0x9214: 'SubjectArea', // Location and area of main subject
110 | 0x920a: 'FocalLength', // Focal length of the lens in mm
111 | 0xa20b: 'FlashEnergy', // Strobe energy in BCPS
112 | 0xa20c: 'SpatialFrequencyResponse',
113 | 0xa20e: 'FocalPlaneXResolution', // Number of pixels in width direction per FPRUnit
114 | 0xa20f: 'FocalPlaneYResolution', // Number of pixels in height direction per FPRUnit
115 | 0xa210: 'FocalPlaneResolutionUnit', // Unit for measuring the focal plane resolution
116 | 0xa214: 'SubjectLocation', // Location of subject in image
117 | 0xa215: 'ExposureIndex', // Exposure index selected on camera
118 | 0xa217: 'SensingMethod', // Image sensor type
119 | 0xa300: 'FileSource', // Image source (3 == DSC)
120 | 0xa301: 'SceneType', // Scene type (1 == directly photographed)
121 | 0xa302: 'CFAPattern', // Color filter array geometric pattern
122 | 0xa401: 'CustomRendered', // Special processing
123 | 0xa402: 'ExposureMode', // Exposure mode
124 | 0xa403: 'WhiteBalance', // 1 = auto white balance, 2 = manual
125 | 0xa404: 'DigitalZoomRatio', // Digital zoom ratio
126 | 0xa405: 'FocalLengthIn35mmFilm',
127 | 0xa406: 'SceneCaptureType', // Type of scene
128 | 0xa407: 'GainControl', // Degree of overall image gain adjustment
129 | 0xa408: 'Contrast', // Direction of contrast processing applied by camera
130 | 0xa409: 'Saturation', // Direction of saturation processing applied by camera
131 | 0xa40a: 'Sharpness', // Direction of sharpness processing applied by camera
132 | 0xa40b: 'DeviceSettingDescription',
133 | 0xa40c: 'SubjectDistanceRange', // Distance to subject
134 | 0xa420: 'ImageUniqueID', // Identifier assigned uniquely to each image
135 | 0xa430: 'CameraOwnerName',
136 | 0xa431: 'BodySerialNumber',
137 | 0xa432: 'LensSpecification',
138 | 0xa433: 'LensMake',
139 | 0xa434: 'LensModel',
140 | 0xa435: 'LensSerialNumber'
141 | },
142 | 0x8825: {
143 | // GPSInfoIFDPointer
144 | 0x0000: 'GPSVersionID',
145 | 0x0001: 'GPSLatitudeRef',
146 | 0x0002: 'GPSLatitude',
147 | 0x0003: 'GPSLongitudeRef',
148 | 0x0004: 'GPSLongitude',
149 | 0x0005: 'GPSAltitudeRef',
150 | 0x0006: 'GPSAltitude',
151 | 0x0007: 'GPSTimeStamp',
152 | 0x0008: 'GPSSatellites',
153 | 0x0009: 'GPSStatus',
154 | 0x000a: 'GPSMeasureMode',
155 | 0x000b: 'GPSDOP',
156 | 0x000c: 'GPSSpeedRef',
157 | 0x000d: 'GPSSpeed',
158 | 0x000e: 'GPSTrackRef',
159 | 0x000f: 'GPSTrack',
160 | 0x0010: 'GPSImgDirectionRef',
161 | 0x0011: 'GPSImgDirection',
162 | 0x0012: 'GPSMapDatum',
163 | 0x0013: 'GPSDestLatitudeRef',
164 | 0x0014: 'GPSDestLatitude',
165 | 0x0015: 'GPSDestLongitudeRef',
166 | 0x0016: 'GPSDestLongitude',
167 | 0x0017: 'GPSDestBearingRef',
168 | 0x0018: 'GPSDestBearing',
169 | 0x0019: 'GPSDestDistanceRef',
170 | 0x001a: 'GPSDestDistance',
171 | 0x001b: 'GPSProcessingMethod',
172 | 0x001c: 'GPSAreaInformation',
173 | 0x001d: 'GPSDateStamp',
174 | 0x001e: 'GPSDifferential',
175 | 0x001f: 'GPSHPositioningError'
176 | },
177 | 0xa005: {
178 | // InteroperabilityIFDPointer
179 | 0x0001: 'InteroperabilityIndex'
180 | }
181 | }
182 |
183 | // IFD1 directory can contain any IFD0 tags:
184 | ExifMapProto.tags.ifd1 = ExifMapProto.tags
185 |
186 | ExifMapProto.stringValues = {
187 | ExposureProgram: {
188 | 0: 'Undefined',
189 | 1: 'Manual',
190 | 2: 'Normal program',
191 | 3: 'Aperture priority',
192 | 4: 'Shutter priority',
193 | 5: 'Creative program',
194 | 6: 'Action program',
195 | 7: 'Portrait mode',
196 | 8: 'Landscape mode'
197 | },
198 | MeteringMode: {
199 | 0: 'Unknown',
200 | 1: 'Average',
201 | 2: 'CenterWeightedAverage',
202 | 3: 'Spot',
203 | 4: 'MultiSpot',
204 | 5: 'Pattern',
205 | 6: 'Partial',
206 | 255: 'Other'
207 | },
208 | LightSource: {
209 | 0: 'Unknown',
210 | 1: 'Daylight',
211 | 2: 'Fluorescent',
212 | 3: 'Tungsten (incandescent light)',
213 | 4: 'Flash',
214 | 9: 'Fine weather',
215 | 10: 'Cloudy weather',
216 | 11: 'Shade',
217 | 12: 'Daylight fluorescent (D 5700 - 7100K)',
218 | 13: 'Day white fluorescent (N 4600 - 5400K)',
219 | 14: 'Cool white fluorescent (W 3900 - 4500K)',
220 | 15: 'White fluorescent (WW 3200 - 3700K)',
221 | 17: 'Standard light A',
222 | 18: 'Standard light B',
223 | 19: 'Standard light C',
224 | 20: 'D55',
225 | 21: 'D65',
226 | 22: 'D75',
227 | 23: 'D50',
228 | 24: 'ISO studio tungsten',
229 | 255: 'Other'
230 | },
231 | Flash: {
232 | 0x0000: 'Flash did not fire',
233 | 0x0001: 'Flash fired',
234 | 0x0005: 'Strobe return light not detected',
235 | 0x0007: 'Strobe return light detected',
236 | 0x0009: 'Flash fired, compulsory flash mode',
237 | 0x000d: 'Flash fired, compulsory flash mode, return light not detected',
238 | 0x000f: 'Flash fired, compulsory flash mode, return light detected',
239 | 0x0010: 'Flash did not fire, compulsory flash mode',
240 | 0x0018: 'Flash did not fire, auto mode',
241 | 0x0019: 'Flash fired, auto mode',
242 | 0x001d: 'Flash fired, auto mode, return light not detected',
243 | 0x001f: 'Flash fired, auto mode, return light detected',
244 | 0x0020: 'No flash function',
245 | 0x0041: 'Flash fired, red-eye reduction mode',
246 | 0x0045: 'Flash fired, red-eye reduction mode, return light not detected',
247 | 0x0047: 'Flash fired, red-eye reduction mode, return light detected',
248 | 0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode',
249 | 0x004d:
250 | 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
251 | 0x004f:
252 | 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
253 | 0x0059: 'Flash fired, auto mode, red-eye reduction mode',
254 | 0x005d:
255 | 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
256 | 0x005f:
257 | 'Flash fired, auto mode, return light detected, red-eye reduction mode'
258 | },
259 | SensingMethod: {
260 | 1: 'Undefined',
261 | 2: 'One-chip color area sensor',
262 | 3: 'Two-chip color area sensor',
263 | 4: 'Three-chip color area sensor',
264 | 5: 'Color sequential area sensor',
265 | 7: 'Trilinear sensor',
266 | 8: 'Color sequential linear sensor'
267 | },
268 | SceneCaptureType: {
269 | 0: 'Standard',
270 | 1: 'Landscape',
271 | 2: 'Portrait',
272 | 3: 'Night scene'
273 | },
274 | SceneType: {
275 | 1: 'Directly photographed'
276 | },
277 | CustomRendered: {
278 | 0: 'Normal process',
279 | 1: 'Custom process'
280 | },
281 | WhiteBalance: {
282 | 0: 'Auto white balance',
283 | 1: 'Manual white balance'
284 | },
285 | GainControl: {
286 | 0: 'None',
287 | 1: 'Low gain up',
288 | 2: 'High gain up',
289 | 3: 'Low gain down',
290 | 4: 'High gain down'
291 | },
292 | Contrast: {
293 | 0: 'Normal',
294 | 1: 'Soft',
295 | 2: 'Hard'
296 | },
297 | Saturation: {
298 | 0: 'Normal',
299 | 1: 'Low saturation',
300 | 2: 'High saturation'
301 | },
302 | Sharpness: {
303 | 0: 'Normal',
304 | 1: 'Soft',
305 | 2: 'Hard'
306 | },
307 | SubjectDistanceRange: {
308 | 0: 'Unknown',
309 | 1: 'Macro',
310 | 2: 'Close view',
311 | 3: 'Distant view'
312 | },
313 | FileSource: {
314 | 3: 'DSC'
315 | },
316 | ComponentsConfiguration: {
317 | 0: '',
318 | 1: 'Y',
319 | 2: 'Cb',
320 | 3: 'Cr',
321 | 4: 'R',
322 | 5: 'G',
323 | 6: 'B'
324 | },
325 | Orientation: {
326 | 1: 'Original',
327 | 2: 'Horizontal flip',
328 | 3: 'Rotate 180° CCW',
329 | 4: 'Vertical flip',
330 | 5: 'Vertical flip + Rotate 90° CW',
331 | 6: 'Rotate 90° CW',
332 | 7: 'Horizontal flip + Rotate 90° CW',
333 | 8: 'Rotate 90° CCW'
334 | }
335 | }
336 |
337 | ExifMapProto.getText = function (name) {
338 | var value = this.get(name)
339 | switch (name) {
340 | case 'LightSource':
341 | case 'Flash':
342 | case 'MeteringMode':
343 | case 'ExposureProgram':
344 | case 'SensingMethod':
345 | case 'SceneCaptureType':
346 | case 'SceneType':
347 | case 'CustomRendered':
348 | case 'WhiteBalance':
349 | case 'GainControl':
350 | case 'Contrast':
351 | case 'Saturation':
352 | case 'Sharpness':
353 | case 'SubjectDistanceRange':
354 | case 'FileSource':
355 | case 'Orientation':
356 | return this.stringValues[name][value]
357 | case 'ExifVersion':
358 | case 'FlashpixVersion':
359 | if (!value) return
360 | return String.fromCharCode(value[0], value[1], value[2], value[3])
361 | case 'ComponentsConfiguration':
362 | if (!value) return
363 | return (
364 | this.stringValues[name][value[0]] +
365 | this.stringValues[name][value[1]] +
366 | this.stringValues[name][value[2]] +
367 | this.stringValues[name][value[3]]
368 | )
369 | case 'GPSVersionID':
370 | if (!value) return
371 | return value[0] + '.' + value[1] + '.' + value[2] + '.' + value[3]
372 | }
373 | return String(value)
374 | }
375 |
376 | ExifMapProto.getAll = function () {
377 | var map = {}
378 | var prop
379 | var obj
380 | var name
381 | for (prop in this) {
382 | if (Object.prototype.hasOwnProperty.call(this, prop)) {
383 | obj = this[prop]
384 | if (obj && obj.getAll) {
385 | map[this.ifds[prop].name] = obj.getAll()
386 | } else {
387 | name = this.tags[prop]
388 | if (name) map[name] = this.getText(name)
389 | }
390 | }
391 | }
392 | return map
393 | }
394 |
395 | ExifMapProto.getName = function (tagCode) {
396 | var name = this.tags[tagCode]
397 | if (typeof name === 'object') return this.ifds[tagCode].name
398 | return name
399 | }
400 |
401 | // Extend the map of tag names to tag codes:
402 | ;(function () {
403 | var tags = ExifMapProto.tags
404 | var prop
405 | var ifd
406 | var subTags
407 | // Map the tag names to tags:
408 | for (prop in tags) {
409 | if (Object.prototype.hasOwnProperty.call(tags, prop)) {
410 | ifd = ExifMapProto.ifds[prop]
411 | if (ifd) {
412 | subTags = tags[prop]
413 | for (prop in subTags) {
414 | if (Object.prototype.hasOwnProperty.call(subTags, prop)) {
415 | ifd.map[subTags[prop]] = Number(prop)
416 | }
417 | }
418 | } else {
419 | ExifMapProto.map[tags[prop]] = Number(prop)
420 | }
421 | }
422 | }
423 | })()
424 | })
425 |
--------------------------------------------------------------------------------
/js/load-image-exif.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript Load Image Exif Parser
3 | * https://github.com/blueimp/JavaScript-Load-Image
4 | *
5 | * Copyright 2013, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Licensed under the MIT license:
9 | * https://opensource.org/licenses/MIT
10 | */
11 |
12 | /* global define, module, require, DataView */
13 |
14 | /* eslint-disable no-console */
15 |
16 | ;(function (factory) {
17 | 'use strict'
18 | if (typeof define === 'function' && define.amd) {
19 | // Register as an anonymous AMD module:
20 | define(['./load-image', './load-image-meta'], factory)
21 | } else if (typeof module === 'object' && module.exports) {
22 | factory(require('./load-image'), require('./load-image-meta'))
23 | } else {
24 | // Browser globals:
25 | factory(window.loadImage)
26 | }
27 | })(function (loadImage) {
28 | 'use strict'
29 |
30 | /**
31 | * Exif tag map
32 | *
33 | * @name ExifMap
34 | * @class
35 | * @param {number|string} tagCode IFD tag code
36 | */
37 | function ExifMap(tagCode) {
38 | if (tagCode) {
39 | Object.defineProperty(this, 'map', {
40 | value: this.ifds[tagCode].map
41 | })
42 | Object.defineProperty(this, 'tags', {
43 | value: (this.tags && this.tags[tagCode]) || {}
44 | })
45 | }
46 | }
47 |
48 | ExifMap.prototype.map = {
49 | Orientation: 0x0112,
50 | Thumbnail: 'ifd1',
51 | Blob: 0x0201, // Alias for JPEGInterchangeFormat
52 | Exif: 0x8769,
53 | GPSInfo: 0x8825,
54 | Interoperability: 0xa005
55 | }
56 |
57 | ExifMap.prototype.ifds = {
58 | ifd1: { name: 'Thumbnail', map: ExifMap.prototype.map },
59 | 0x8769: { name: 'Exif', map: {} },
60 | 0x8825: { name: 'GPSInfo', map: {} },
61 | 0xa005: { name: 'Interoperability', map: {} }
62 | }
63 |
64 | /**
65 | * Retrieves exif tag value
66 | *
67 | * @param {number|string} id Exif tag code or name
68 | * @returns {object} Exif tag value
69 | */
70 | ExifMap.prototype.get = function (id) {
71 | return this[id] || this[this.map[id]]
72 | }
73 |
74 | /**
75 | * Returns the Exif Thumbnail data as Blob.
76 | *
77 | * @param {DataView} dataView Data view interface
78 | * @param {number} offset Thumbnail data offset
79 | * @param {number} length Thumbnail data length
80 | * @returns {undefined|Blob} Returns the Thumbnail Blob or undefined
81 | */
82 | function getExifThumbnail(dataView, offset, length) {
83 | if (!length) return
84 | if (offset + length > dataView.byteLength) {
85 | console.log('Invalid Exif data: Invalid thumbnail data.')
86 | return
87 | }
88 | return new Blob(
89 | [loadImage.bufferSlice.call(dataView.buffer, offset, offset + length)],
90 | {
91 | type: 'image/jpeg'
92 | }
93 | )
94 | }
95 |
96 | var ExifTagTypes = {
97 | // byte, 8-bit unsigned int:
98 | 1: {
99 | getValue: function (dataView, dataOffset) {
100 | return dataView.getUint8(dataOffset)
101 | },
102 | size: 1
103 | },
104 | // ascii, 8-bit byte:
105 | 2: {
106 | getValue: function (dataView, dataOffset) {
107 | return String.fromCharCode(dataView.getUint8(dataOffset))
108 | },
109 | size: 1,
110 | ascii: true
111 | },
112 | // short, 16 bit int:
113 | 3: {
114 | getValue: function (dataView, dataOffset, littleEndian) {
115 | return dataView.getUint16(dataOffset, littleEndian)
116 | },
117 | size: 2
118 | },
119 | // long, 32 bit int:
120 | 4: {
121 | getValue: function (dataView, dataOffset, littleEndian) {
122 | return dataView.getUint32(dataOffset, littleEndian)
123 | },
124 | size: 4
125 | },
126 | // rational = two long values, first is numerator, second is denominator:
127 | 5: {
128 | getValue: function (dataView, dataOffset, littleEndian) {
129 | return (
130 | dataView.getUint32(dataOffset, littleEndian) /
131 | dataView.getUint32(dataOffset + 4, littleEndian)
132 | )
133 | },
134 | size: 8
135 | },
136 | // slong, 32 bit signed int:
137 | 9: {
138 | getValue: function (dataView, dataOffset, littleEndian) {
139 | return dataView.getInt32(dataOffset, littleEndian)
140 | },
141 | size: 4
142 | },
143 | // srational, two slongs, first is numerator, second is denominator:
144 | 10: {
145 | getValue: function (dataView, dataOffset, littleEndian) {
146 | return (
147 | dataView.getInt32(dataOffset, littleEndian) /
148 | dataView.getInt32(dataOffset + 4, littleEndian)
149 | )
150 | },
151 | size: 8
152 | }
153 | }
154 | // undefined, 8-bit byte, value depending on field:
155 | ExifTagTypes[7] = ExifTagTypes[1]
156 |
157 | /**
158 | * Returns Exif tag value.
159 | *
160 | * @param {DataView} dataView Data view interface
161 | * @param {number} tiffOffset TIFF offset
162 | * @param {number} offset Tag offset
163 | * @param {number} type Tag type
164 | * @param {number} length Tag length
165 | * @param {boolean} littleEndian Little endian encoding
166 | * @returns {object} Tag value
167 | */
168 | function getExifValue(
169 | dataView,
170 | tiffOffset,
171 | offset,
172 | type,
173 | length,
174 | littleEndian
175 | ) {
176 | var tagType = ExifTagTypes[type]
177 | var tagSize
178 | var dataOffset
179 | var values
180 | var i
181 | var str
182 | var c
183 | if (!tagType) {
184 | console.log('Invalid Exif data: Invalid tag type.')
185 | return
186 | }
187 | tagSize = tagType.size * length
188 | // Determine if the value is contained in the dataOffset bytes,
189 | // or if the value at the dataOffset is a pointer to the actual data:
190 | dataOffset =
191 | tagSize > 4
192 | ? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
193 | : offset + 8
194 | if (dataOffset + tagSize > dataView.byteLength) {
195 | console.log('Invalid Exif data: Invalid data offset.')
196 | return
197 | }
198 | if (length === 1) {
199 | return tagType.getValue(dataView, dataOffset, littleEndian)
200 | }
201 | values = []
202 | for (i = 0; i < length; i += 1) {
203 | values[i] = tagType.getValue(
204 | dataView,
205 | dataOffset + i * tagType.size,
206 | littleEndian
207 | )
208 | }
209 | if (tagType.ascii) {
210 | str = ''
211 | // Concatenate the chars:
212 | for (i = 0; i < values.length; i += 1) {
213 | c = values[i]
214 | // Ignore the terminating NULL byte(s):
215 | if (c === '\u0000') {
216 | break
217 | }
218 | str += c
219 | }
220 | return str
221 | }
222 | return values
223 | }
224 |
225 | /**
226 | * Determines if the given tag should be included.
227 | *
228 | * @param {object} includeTags Map of tags to include
229 | * @param {object} excludeTags Map of tags to exclude
230 | * @param {number|string} tagCode Tag code to check
231 | * @returns {boolean} True if the tag should be included
232 | */
233 | function shouldIncludeTag(includeTags, excludeTags, tagCode) {
234 | return (
235 | (!includeTags || includeTags[tagCode]) &&
236 | (!excludeTags || excludeTags[tagCode] !== true)
237 | )
238 | }
239 |
240 | /**
241 | * Parses Exif tags.
242 | *
243 | * @param {DataView} dataView Data view interface
244 | * @param {number} tiffOffset TIFF offset
245 | * @param {number} dirOffset Directory offset
246 | * @param {boolean} littleEndian Little endian encoding
247 | * @param {ExifMap} tags Map to store parsed exif tags
248 | * @param {ExifMap} tagOffsets Map to store parsed exif tag offsets
249 | * @param {object} includeTags Map of tags to include
250 | * @param {object} excludeTags Map of tags to exclude
251 | * @returns {number} Next directory offset
252 | */
253 | function parseExifTags(
254 | dataView,
255 | tiffOffset,
256 | dirOffset,
257 | littleEndian,
258 | tags,
259 | tagOffsets,
260 | includeTags,
261 | excludeTags
262 | ) {
263 | var tagsNumber, dirEndOffset, i, tagOffset, tagNumber, tagValue
264 | if (dirOffset + 6 > dataView.byteLength) {
265 | console.log('Invalid Exif data: Invalid directory offset.')
266 | return
267 | }
268 | tagsNumber = dataView.getUint16(dirOffset, littleEndian)
269 | dirEndOffset = dirOffset + 2 + 12 * tagsNumber
270 | if (dirEndOffset + 4 > dataView.byteLength) {
271 | console.log('Invalid Exif data: Invalid directory size.')
272 | return
273 | }
274 | for (i = 0; i < tagsNumber; i += 1) {
275 | tagOffset = dirOffset + 2 + 12 * i
276 | tagNumber = dataView.getUint16(tagOffset, littleEndian)
277 | if (!shouldIncludeTag(includeTags, excludeTags, tagNumber)) continue
278 | tagValue = getExifValue(
279 | dataView,
280 | tiffOffset,
281 | tagOffset,
282 | dataView.getUint16(tagOffset + 2, littleEndian), // tag type
283 | dataView.getUint32(tagOffset + 4, littleEndian), // tag length
284 | littleEndian
285 | )
286 | tags[tagNumber] = tagValue
287 | if (tagOffsets) {
288 | tagOffsets[tagNumber] = tagOffset
289 | }
290 | }
291 | // Return the offset to the next directory:
292 | return dataView.getUint32(dirEndOffset, littleEndian)
293 | }
294 |
295 | /**
296 | * Parses tags in a given IFD (Image File Directory).
297 | *
298 | * @param {object} data Data object to store exif tags and offsets
299 | * @param {number|string} tagCode IFD tag code
300 | * @param {DataView} dataView Data view interface
301 | * @param {number} tiffOffset TIFF offset
302 | * @param {boolean} littleEndian Little endian encoding
303 | * @param {object} includeTags Map of tags to include
304 | * @param {object} excludeTags Map of tags to exclude
305 | */
306 | function parseExifIFD(
307 | data,
308 | tagCode,
309 | dataView,
310 | tiffOffset,
311 | littleEndian,
312 | includeTags,
313 | excludeTags
314 | ) {
315 | var dirOffset = data.exif[tagCode]
316 | if (dirOffset) {
317 | data.exif[tagCode] = new ExifMap(tagCode)
318 | if (data.exifOffsets) {
319 | data.exifOffsets[tagCode] = new ExifMap(tagCode)
320 | }
321 | parseExifTags(
322 | dataView,
323 | tiffOffset,
324 | tiffOffset + dirOffset,
325 | littleEndian,
326 | data.exif[tagCode],
327 | data.exifOffsets && data.exifOffsets[tagCode],
328 | includeTags && includeTags[tagCode],
329 | excludeTags && excludeTags[tagCode]
330 | )
331 | }
332 | }
333 |
334 | loadImage.parseExifData = function (dataView, offset, length, data, options) {
335 | if (options.disableExif) {
336 | return
337 | }
338 | var includeTags = options.includeExifTags
339 | var excludeTags = options.excludeExifTags || {
340 | 0x8769: {
341 | // ExifIFDPointer
342 | 0x927c: true // MakerNote
343 | }
344 | }
345 | var tiffOffset = offset + 10
346 | var littleEndian
347 | var dirOffset
348 | var thumbnailIFD
349 | // Check for the ASCII code for "Exif" (0x45786966):
350 | if (dataView.getUint32(offset + 4) !== 0x45786966) {
351 | // No Exif data, might be XMP data instead
352 | return
353 | }
354 | if (tiffOffset + 8 > dataView.byteLength) {
355 | console.log('Invalid Exif data: Invalid segment size.')
356 | return
357 | }
358 | // Check for the two null bytes:
359 | if (dataView.getUint16(offset + 8) !== 0x0000) {
360 | console.log('Invalid Exif data: Missing byte alignment offset.')
361 | return
362 | }
363 | // Check the byte alignment:
364 | switch (dataView.getUint16(tiffOffset)) {
365 | case 0x4949:
366 | littleEndian = true
367 | break
368 | case 0x4d4d:
369 | littleEndian = false
370 | break
371 | default:
372 | console.log('Invalid Exif data: Invalid byte alignment marker.')
373 | return
374 | }
375 | // Check for the TIFF tag marker (0x002A):
376 | if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) {
377 | console.log('Invalid Exif data: Missing TIFF marker.')
378 | return
379 | }
380 | // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
381 | dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
382 | // Create the exif object to store the tags:
383 | data.exif = new ExifMap()
384 | if (!options.disableExifOffsets) {
385 | data.exifOffsets = new ExifMap()
386 | data.exifTiffOffset = tiffOffset
387 | data.exifLittleEndian = littleEndian
388 | }
389 | // Parse the tags of the main image directory (IFD0) and retrieve the
390 | // offset to the next directory (IFD1), usually the thumbnail directory:
391 | dirOffset = parseExifTags(
392 | dataView,
393 | tiffOffset,
394 | tiffOffset + dirOffset,
395 | littleEndian,
396 | data.exif,
397 | data.exifOffsets,
398 | includeTags,
399 | excludeTags
400 | )
401 | if (dirOffset && shouldIncludeTag(includeTags, excludeTags, 'ifd1')) {
402 | data.exif.ifd1 = dirOffset
403 | if (data.exifOffsets) {
404 | data.exifOffsets.ifd1 = tiffOffset + dirOffset
405 | }
406 | }
407 | Object.keys(data.exif.ifds).forEach(function (tagCode) {
408 | parseExifIFD(
409 | data,
410 | tagCode,
411 | dataView,
412 | tiffOffset,
413 | littleEndian,
414 | includeTags,
415 | excludeTags
416 | )
417 | })
418 | thumbnailIFD = data.exif.ifd1
419 | // Check for JPEG Thumbnail offset and data length:
420 | if (thumbnailIFD && thumbnailIFD[0x0201]) {
421 | thumbnailIFD[0x0201] = getExifThumbnail(
422 | dataView,
423 | tiffOffset + thumbnailIFD[0x0201],
424 | thumbnailIFD[0x0202] // Thumbnail data length
425 | )
426 | }
427 | }
428 |
429 | // Registers the Exif parser for the APP1 JPEG metadata segment:
430 | loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
431 |
432 | loadImage.exifWriters = {
433 | // Orientation writer:
434 | 0x0112: function (buffer, data, value) {
435 | var orientationOffset = data.exifOffsets[0x0112]
436 | if (!orientationOffset) return buffer
437 | var view = new DataView(buffer, orientationOffset + 8, 2)
438 | view.setUint16(0, value, data.exifLittleEndian)
439 | return buffer
440 | }
441 | }
442 |
443 | loadImage.writeExifData = function (buffer, data, id, value) {
444 | return loadImage.exifWriters[data.exif.map[id]](buffer, data, value)
445 | }
446 |
447 | loadImage.ExifMap = ExifMap
448 |
449 | // Adds the following properties to the parseMetaData callback data:
450 | // - exif: The parsed Exif tags
451 | // - exifOffsets: The parsed Exif tag offsets
452 | // - exifTiffOffset: TIFF header offset (used for offset pointers)
453 | // - exifLittleEndian: little endian order if true, big endian if false
454 |
455 | // Adds the following options to the parseMetaData method:
456 | // - disableExif: Disables Exif parsing when true.
457 | // - disableExifOffsets: Disables storing Exif tag offsets when true.
458 | // - includeExifTags: A map of Exif tags to include for parsing.
459 | // - excludeExifTags: A map of Exif tags to exclude from parsing.
460 | })
461 |
--------------------------------------------------------------------------------
/js/load-image-fetch.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript Load Image Fetch
3 | * https://github.com/blueimp/JavaScript-Load-Image
4 | *
5 | * Copyright 2017, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Licensed under the MIT license:
9 | * https://opensource.org/licenses/MIT
10 | */
11 |
12 | /* global define, module, require, Promise */
13 |
14 | ;(function (factory) {
15 | 'use strict'
16 | if (typeof define === 'function' && define.amd) {
17 | // Register as an anonymous AMD module:
18 | define(['./load-image'], factory)
19 | } else if (typeof module === 'object' && module.exports) {
20 | factory(require('./load-image'))
21 | } else {
22 | // Browser globals:
23 | factory(window.loadImage)
24 | }
25 | })(function (loadImage) {
26 | 'use strict'
27 |
28 | var global = loadImage.global
29 |
30 | if (
31 | global.fetch &&
32 | global.Request &&
33 | global.Response &&
34 | global.Response.prototype.blob
35 | ) {
36 | loadImage.fetchBlob = function (url, callback, options) {
37 | /**
38 | * Fetch response handler.
39 | *
40 | * @param {Response} response Fetch response
41 | * @returns {Blob} Fetched Blob.
42 | */
43 | function responseHandler(response) {
44 | return response.blob()
45 | }
46 | if (global.Promise && typeof callback !== 'function') {
47 | return fetch(new Request(url, callback)).then(responseHandler)
48 | }
49 | fetch(new Request(url, options))
50 | .then(responseHandler)
51 | .then(callback)
52 | [
53 | // Avoid parsing error in IE<9, where catch is a reserved word.
54 | // eslint-disable-next-line dot-notation
55 | 'catch'
56 | ](function (err) {
57 | callback(null, err)
58 | })
59 | }
60 | } else if (
61 | global.XMLHttpRequest &&
62 | // https://xhr.spec.whatwg.org/#the-responsetype-attribute
63 | new XMLHttpRequest().responseType === ''
64 | ) {
65 | loadImage.fetchBlob = function (url, callback, options) {
66 | /**
67 | * Promise executor
68 | *
69 | * @param {Function} resolve Resolution function
70 | * @param {Function} reject Rejection function
71 | */
72 | function executor(resolve, reject) {
73 | options = options || {} // eslint-disable-line no-param-reassign
74 | var req = new XMLHttpRequest()
75 | req.open(options.method || 'GET', url)
76 | if (options.headers) {
77 | Object.keys(options.headers).forEach(function (key) {
78 | req.setRequestHeader(key, options.headers[key])
79 | })
80 | }
81 | req.withCredentials = options.credentials === 'include'
82 | req.responseType = 'blob'
83 | req.onload = function () {
84 | resolve(req.response)
85 | }
86 | req.onerror =
87 | req.onabort =
88 | req.ontimeout =
89 | function (err) {
90 | if (resolve === reject) {
91 | // Not using Promises
92 | reject(null, err)
93 | } else {
94 | reject(err)
95 | }
96 | }
97 | req.send(options.body)
98 | }
99 | if (global.Promise && typeof callback !== 'function') {
100 | options = callback // eslint-disable-line no-param-reassign
101 | return new Promise(executor)
102 | }
103 | return executor(callback, callback)
104 | }
105 | }
106 | })
107 |
--------------------------------------------------------------------------------
/js/load-image-iptc-map.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript Load Image IPTC Map
3 | * https://github.com/blueimp/JavaScript-Load-Image
4 | *
5 | * Copyright 2013, Sebastian Tschan
6 | * Copyright 2018, Dave Bevan
7 | *
8 | * IPTC tags mapping based on
9 | * https://iptc.org/standards/photo-metadata
10 | * https://exiftool.org/TagNames/IPTC.html
11 | *
12 | * Licensed under the MIT license:
13 | * https://opensource.org/licenses/MIT
14 | */
15 |
16 | /* global define, module, require */
17 |
18 | ;(function (factory) {
19 | 'use strict'
20 | if (typeof define === 'function' && define.amd) {
21 | // Register as an anonymous AMD module:
22 | define(['./load-image', './load-image-iptc'], factory)
23 | } else if (typeof module === 'object' && module.exports) {
24 | factory(require('./load-image'), require('./load-image-iptc'))
25 | } else {
26 | // Browser globals:
27 | factory(window.loadImage)
28 | }
29 | })(function (loadImage) {
30 | 'use strict'
31 |
32 | var IptcMapProto = loadImage.IptcMap.prototype
33 |
34 | IptcMapProto.tags = {
35 | 0: 'ApplicationRecordVersion',
36 | 3: 'ObjectTypeReference',
37 | 4: 'ObjectAttributeReference',
38 | 5: 'ObjectName',
39 | 7: 'EditStatus',
40 | 8: 'EditorialUpdate',
41 | 10: 'Urgency',
42 | 12: 'SubjectReference',
43 | 15: 'Category',
44 | 20: 'SupplementalCategories',
45 | 22: 'FixtureIdentifier',
46 | 25: 'Keywords',
47 | 26: 'ContentLocationCode',
48 | 27: 'ContentLocationName',
49 | 30: 'ReleaseDate',
50 | 35: 'ReleaseTime',
51 | 37: 'ExpirationDate',
52 | 38: 'ExpirationTime',
53 | 40: 'SpecialInstructions',
54 | 42: 'ActionAdvised',
55 | 45: 'ReferenceService',
56 | 47: 'ReferenceDate',
57 | 50: 'ReferenceNumber',
58 | 55: 'DateCreated',
59 | 60: 'TimeCreated',
60 | 62: 'DigitalCreationDate',
61 | 63: 'DigitalCreationTime',
62 | 65: 'OriginatingProgram',
63 | 70: 'ProgramVersion',
64 | 75: 'ObjectCycle',
65 | 80: 'Byline',
66 | 85: 'BylineTitle',
67 | 90: 'City',
68 | 92: 'Sublocation',
69 | 95: 'State',
70 | 100: 'CountryCode',
71 | 101: 'Country',
72 | 103: 'OriginalTransmissionReference',
73 | 105: 'Headline',
74 | 110: 'Credit',
75 | 115: 'Source',
76 | 116: 'CopyrightNotice',
77 | 118: 'Contact',
78 | 120: 'Caption',
79 | 121: 'LocalCaption',
80 | 122: 'Writer',
81 | 125: 'RasterizedCaption',
82 | 130: 'ImageType',
83 | 131: 'ImageOrientation',
84 | 135: 'LanguageIdentifier',
85 | 150: 'AudioType',
86 | 151: 'AudioSamplingRate',
87 | 152: 'AudioSamplingResolution',
88 | 153: 'AudioDuration',
89 | 154: 'AudioOutcue',
90 | 184: 'JobID',
91 | 185: 'MasterDocumentID',
92 | 186: 'ShortDocumentID',
93 | 187: 'UniqueDocumentID',
94 | 188: 'OwnerID',
95 | 200: 'ObjectPreviewFileFormat',
96 | 201: 'ObjectPreviewFileVersion',
97 | 202: 'ObjectPreviewData',
98 | 221: 'Prefs',
99 | 225: 'ClassifyState',
100 | 228: 'SimilarityIndex',
101 | 230: 'DocumentNotes',
102 | 231: 'DocumentHistory',
103 | 232: 'ExifCameraInfo',
104 | 255: 'CatalogSets'
105 | }
106 |
107 | IptcMapProto.stringValues = {
108 | 10: {
109 | 0: '0 (reserved)',
110 | 1: '1 (most urgent)',
111 | 2: '2',
112 | 3: '3',
113 | 4: '4',
114 | 5: '5 (normal urgency)',
115 | 6: '6',
116 | 7: '7',
117 | 8: '8 (least urgent)',
118 | 9: '9 (user-defined priority)'
119 | },
120 | 75: {
121 | a: 'Morning',
122 | b: 'Both Morning and Evening',
123 | p: 'Evening'
124 | },
125 | 131: {
126 | L: 'Landscape',
127 | P: 'Portrait',
128 | S: 'Square'
129 | }
130 | }
131 |
132 | IptcMapProto.getText = function (id) {
133 | var value = this.get(id)
134 | var tagCode = this.map[id]
135 | var stringValue = this.stringValues[tagCode]
136 | if (stringValue) return stringValue[value]
137 | return String(value)
138 | }
139 |
140 | IptcMapProto.getAll = function () {
141 | var map = {}
142 | var prop
143 | var name
144 | for (prop in this) {
145 | if (Object.prototype.hasOwnProperty.call(this, prop)) {
146 | name = this.tags[prop]
147 | if (name) map[name] = this.getText(name)
148 | }
149 | }
150 | return map
151 | }
152 |
153 | IptcMapProto.getName = function (tagCode) {
154 | return this.tags[tagCode]
155 | }
156 |
157 | // Extend the map of tag names to tag codes:
158 | ;(function () {
159 | var tags = IptcMapProto.tags
160 | var map = IptcMapProto.map || {}
161 | var prop
162 | // Map the tag names to tags:
163 | for (prop in tags) {
164 | if (Object.prototype.hasOwnProperty.call(tags, prop)) {
165 | map[tags[prop]] = Number(prop)
166 | }
167 | }
168 | })()
169 | })
170 |
--------------------------------------------------------------------------------
/js/load-image-iptc.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript Load Image IPTC Parser
3 | * https://github.com/blueimp/JavaScript-Load-Image
4 | *
5 | * Copyright 2013, Sebastian Tschan
6 | * Copyright 2018, Dave Bevan
7 | * https://blueimp.net
8 | *
9 | * Licensed under the MIT license:
10 | * https://opensource.org/licenses/MIT
11 | */
12 |
13 | /* global define, module, require, DataView */
14 |
15 | ;(function (factory) {
16 | 'use strict'
17 | if (typeof define === 'function' && define.amd) {
18 | // Register as an anonymous AMD module:
19 | define(['./load-image', './load-image-meta'], factory)
20 | } else if (typeof module === 'object' && module.exports) {
21 | factory(require('./load-image'), require('./load-image-meta'))
22 | } else {
23 | // Browser globals:
24 | factory(window.loadImage)
25 | }
26 | })(function (loadImage) {
27 | 'use strict'
28 |
29 | /**
30 | * IPTC tag map
31 | *
32 | * @name IptcMap
33 | * @class
34 | */
35 | function IptcMap() {}
36 |
37 | IptcMap.prototype.map = {
38 | ObjectName: 5
39 | }
40 |
41 | IptcMap.prototype.types = {
42 | 0: 'Uint16', // ApplicationRecordVersion
43 | 200: 'Uint16', // ObjectPreviewFileFormat
44 | 201: 'Uint16', // ObjectPreviewFileVersion
45 | 202: 'binary' // ObjectPreviewData
46 | }
47 |
48 | /**
49 | * Retrieves IPTC tag value
50 | *
51 | * @param {number|string} id IPTC tag code or name
52 | * @returns {object} IPTC tag value
53 | */
54 | IptcMap.prototype.get = function (id) {
55 | return this[id] || this[this.map[id]]
56 | }
57 |
58 | /**
59 | * Retrieves string for the given DataView and range
60 | *
61 | * @param {DataView} dataView Data view interface
62 | * @param {number} offset Offset start
63 | * @param {number} length Offset length
64 | * @returns {string} String value
65 | */
66 | function getStringValue(dataView, offset, length) {
67 | var outstr = ''
68 | var end = offset + length
69 | for (var n = offset; n < end; n += 1) {
70 | outstr += String.fromCharCode(dataView.getUint8(n))
71 | }
72 | return outstr
73 | }
74 |
75 | /**
76 | * Retrieves tag value for the given DataView and range
77 | *
78 | * @param {number} tagCode tag code
79 | * @param {IptcMap} map IPTC tag map
80 | * @param {DataView} dataView Data view interface
81 | * @param {number} offset Range start
82 | * @param {number} length Range length
83 | * @returns {object} Tag value
84 | */
85 | function getTagValue(tagCode, map, dataView, offset, length) {
86 | if (map.types[tagCode] === 'binary') {
87 | return new Blob([dataView.buffer.slice(offset, offset + length)])
88 | }
89 | if (map.types[tagCode] === 'Uint16') {
90 | return dataView.getUint16(offset)
91 | }
92 | return getStringValue(dataView, offset, length)
93 | }
94 |
95 | /**
96 | * Combines IPTC value with existing ones.
97 | *
98 | * @param {object} value Existing IPTC field value
99 | * @param {object} newValue New IPTC field value
100 | * @returns {object} Resulting IPTC field value
101 | */
102 | function combineTagValues(value, newValue) {
103 | if (value === undefined) return newValue
104 | if (value instanceof Array) {
105 | value.push(newValue)
106 | return value
107 | }
108 | return [value, newValue]
109 | }
110 |
111 | /**
112 | * Parses IPTC tags.
113 | *
114 | * @param {DataView} dataView Data view interface
115 | * @param {number} segmentOffset Segment offset
116 | * @param {number} segmentLength Segment length
117 | * @param {object} data Data export object
118 | * @param {object} includeTags Map of tags to include
119 | * @param {object} excludeTags Map of tags to exclude
120 | */
121 | function parseIptcTags(
122 | dataView,
123 | segmentOffset,
124 | segmentLength,
125 | data,
126 | includeTags,
127 | excludeTags
128 | ) {
129 | var value, tagSize, tagCode
130 | var segmentEnd = segmentOffset + segmentLength
131 | var offset = segmentOffset
132 | while (offset < segmentEnd) {
133 | if (
134 | dataView.getUint8(offset) === 0x1c && // tag marker
135 | dataView.getUint8(offset + 1) === 0x02 // record number, only handles v2
136 | ) {
137 | tagCode = dataView.getUint8(offset + 2)
138 | if (
139 | (!includeTags || includeTags[tagCode]) &&
140 | (!excludeTags || !excludeTags[tagCode])
141 | ) {
142 | tagSize = dataView.getInt16(offset + 3)
143 | value = getTagValue(tagCode, data.iptc, dataView, offset + 5, tagSize)
144 | data.iptc[tagCode] = combineTagValues(data.iptc[tagCode], value)
145 | if (data.iptcOffsets) {
146 | data.iptcOffsets[tagCode] = offset
147 | }
148 | }
149 | }
150 | offset += 1
151 | }
152 | }
153 |
154 | /**
155 | * Tests if field segment starts at offset.
156 | *
157 | * @param {DataView} dataView Data view interface
158 | * @param {number} offset Segment offset
159 | * @returns {boolean} True if '8BIM' exists at offset
160 | */
161 | function isSegmentStart(dataView, offset) {
162 | return (
163 | dataView.getUint32(offset) === 0x3842494d && // Photoshop segment start
164 | dataView.getUint16(offset + 4) === 0x0404 // IPTC segment start
165 | )
166 | }
167 |
168 | /**
169 | * Returns header length.
170 | *
171 | * @param {DataView} dataView Data view interface
172 | * @param {number} offset Segment offset
173 | * @returns {number} Header length
174 | */
175 | function getHeaderLength(dataView, offset) {
176 | var length = dataView.getUint8(offset + 7)
177 | if (length % 2 !== 0) length += 1
178 | // Check for pre photoshop 6 format
179 | if (length === 0) {
180 | // Always 4
181 | length = 4
182 | }
183 | return length
184 | }
185 |
186 | loadImage.parseIptcData = function (dataView, offset, length, data, options) {
187 | if (options.disableIptc) {
188 | return
189 | }
190 | var markerLength = offset + length
191 | while (offset + 8 < markerLength) {
192 | if (isSegmentStart(dataView, offset)) {
193 | var headerLength = getHeaderLength(dataView, offset)
194 | var segmentOffset = offset + 8 + headerLength
195 | if (segmentOffset > markerLength) {
196 | // eslint-disable-next-line no-console
197 | console.log('Invalid IPTC data: Invalid segment offset.')
198 | break
199 | }
200 | var segmentLength = dataView.getUint16(offset + 6 + headerLength)
201 | if (offset + segmentLength > markerLength) {
202 | // eslint-disable-next-line no-console
203 | console.log('Invalid IPTC data: Invalid segment size.')
204 | break
205 | }
206 | // Create the iptc object to store the tags:
207 | data.iptc = new IptcMap()
208 | if (!options.disableIptcOffsets) {
209 | data.iptcOffsets = new IptcMap()
210 | }
211 | parseIptcTags(
212 | dataView,
213 | segmentOffset,
214 | segmentLength,
215 | data,
216 | options.includeIptcTags,
217 | options.excludeIptcTags || { 202: true } // ObjectPreviewData
218 | )
219 | return
220 | }
221 | // eslint-disable-next-line no-param-reassign
222 | offset += 1
223 | }
224 | }
225 |
226 | // Registers this IPTC parser for the APP13 JPEG metadata segment:
227 | loadImage.metaDataParsers.jpeg[0xffed].push(loadImage.parseIptcData)
228 |
229 | loadImage.IptcMap = IptcMap
230 |
231 | // Adds the following properties to the parseMetaData callback data:
232 | // - iptc: The iptc tags, parsed by the parseIptcData method
233 |
234 | // Adds the following options to the parseMetaData method:
235 | // - disableIptc: Disables IPTC parsing when true.
236 | // - disableIptcOffsets: Disables storing IPTC tag offsets when true.
237 | // - includeIptcTags: A map of IPTC tags to include for parsing.
238 | // - excludeIptcTags: A map of IPTC tags to exclude from parsing.
239 | })
240 |
--------------------------------------------------------------------------------
/js/load-image-meta.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript Load Image Meta
3 | * https://github.com/blueimp/JavaScript-Load-Image
4 | *
5 | * Copyright 2013, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Image metadata handling implementation
9 | * based on the help and contribution of
10 | * Achim Stöhr.
11 | *
12 | * Licensed under the MIT license:
13 | * https://opensource.org/licenses/MIT
14 | */
15 |
16 | /* global define, module, require, Promise, DataView, Uint8Array, ArrayBuffer */
17 |
18 | ;(function (factory) {
19 | 'use strict'
20 | if (typeof define === 'function' && define.amd) {
21 | // Register as an anonymous AMD module:
22 | define(['./load-image'], factory)
23 | } else if (typeof module === 'object' && module.exports) {
24 | factory(require('./load-image'))
25 | } else {
26 | // Browser globals:
27 | factory(window.loadImage)
28 | }
29 | })(function (loadImage) {
30 | 'use strict'
31 |
32 | var global = loadImage.global
33 | var originalTransform = loadImage.transform
34 |
35 | var blobSlice =
36 | global.Blob &&
37 | (Blob.prototype.slice ||
38 | Blob.prototype.webkitSlice ||
39 | Blob.prototype.mozSlice)
40 |
41 | var bufferSlice =
42 | (global.ArrayBuffer && ArrayBuffer.prototype.slice) ||
43 | function (begin, end) {
44 | // Polyfill for IE10, which does not support ArrayBuffer.slice
45 | // eslint-disable-next-line no-param-reassign
46 | end = end || this.byteLength - begin
47 | var arr1 = new Uint8Array(this, begin, end)
48 | var arr2 = new Uint8Array(end)
49 | arr2.set(arr1)
50 | return arr2.buffer
51 | }
52 |
53 | var metaDataParsers = {
54 | jpeg: {
55 | 0xffe1: [], // APP1 marker
56 | 0xffed: [] // APP13 marker
57 | }
58 | }
59 |
60 | /**
61 | * Parses image metadata and calls the callback with an object argument
62 | * with the following property:
63 | * - imageHead: The complete image head as ArrayBuffer
64 | * The options argument accepts an object and supports the following
65 | * properties:
66 | * - maxMetaDataSize: Defines the maximum number of bytes to parse.
67 | * - disableImageHead: Disables creating the imageHead property.
68 | *
69 | * @param {Blob} file Blob object
70 | * @param {Function} [callback] Callback function
71 | * @param {object} [options] Parsing options
72 | * @param {object} [data] Result data object
73 | * @returns {Promise|undefined} Returns Promise if no callback given.
74 | */
75 | function parseMetaData(file, callback, options, data) {
76 | var that = this
77 | /**
78 | * Promise executor
79 | *
80 | * @param {Function} resolve Resolution function
81 | * @param {Function} reject Rejection function
82 | * @returns {undefined} Undefined
83 | */
84 | function executor(resolve, reject) {
85 | if (
86 | !(
87 | global.DataView &&
88 | blobSlice &&
89 | file &&
90 | file.size >= 12 &&
91 | file.type === 'image/jpeg'
92 | )
93 | ) {
94 | // Nothing to parse
95 | return resolve(data)
96 | }
97 | // 256 KiB should contain all EXIF/ICC/IPTC segments:
98 | var maxMetaDataSize = options.maxMetaDataSize || 262144
99 | if (
100 | !loadImage.readFile(
101 | blobSlice.call(file, 0, maxMetaDataSize),
102 | function (buffer) {
103 | // Note on endianness:
104 | // Since the marker and length bytes in JPEG files are always
105 | // stored in big endian order, we can leave the endian parameter
106 | // of the DataView methods undefined, defaulting to big endian.
107 | var dataView = new DataView(buffer)
108 | // Check for the JPEG marker (0xffd8):
109 | if (dataView.getUint16(0) !== 0xffd8) {
110 | return reject(
111 | new Error('Invalid JPEG file: Missing JPEG marker.')
112 | )
113 | }
114 | var offset = 2
115 | var maxOffset = dataView.byteLength - 4
116 | var headLength = offset
117 | var markerBytes
118 | var markerLength
119 | var parsers
120 | var i
121 | while (offset < maxOffset) {
122 | markerBytes = dataView.getUint16(offset)
123 | // Search for APPn (0xffeN) and COM (0xfffe) markers,
124 | // which contain application-specific metadata like
125 | // Exif, ICC and IPTC data and text comments:
126 | if (
127 | (markerBytes >= 0xffe0 && markerBytes <= 0xffef) ||
128 | markerBytes === 0xfffe
129 | ) {
130 | // The marker bytes (2) are always followed by
131 | // the length bytes (2), indicating the length of the
132 | // marker segment, which includes the length bytes,
133 | // but not the marker bytes, so we add 2:
134 | markerLength = dataView.getUint16(offset + 2) + 2
135 | if (offset + markerLength > dataView.byteLength) {
136 | // eslint-disable-next-line no-console
137 | console.log('Invalid JPEG metadata: Invalid segment size.')
138 | break
139 | }
140 | parsers = metaDataParsers.jpeg[markerBytes]
141 | if (parsers && !options.disableMetaDataParsers) {
142 | for (i = 0; i < parsers.length; i += 1) {
143 | parsers[i].call(
144 | that,
145 | dataView,
146 | offset,
147 | markerLength,
148 | data,
149 | options
150 | )
151 | }
152 | }
153 | offset += markerLength
154 | headLength = offset
155 | } else {
156 | // Not an APPn or COM marker, probably safe to
157 | // assume that this is the end of the metadata
158 | break
159 | }
160 | }
161 | // Meta length must be longer than JPEG marker (2)
162 | // plus APPn marker (2), followed by length bytes (2):
163 | if (!options.disableImageHead && headLength > 6) {
164 | data.imageHead = bufferSlice.call(buffer, 0, headLength)
165 | }
166 | resolve(data)
167 | },
168 | reject,
169 | 'readAsArrayBuffer'
170 | )
171 | ) {
172 | // No support for the FileReader interface, nothing to parse
173 | resolve(data)
174 | }
175 | }
176 | options = options || {} // eslint-disable-line no-param-reassign
177 | if (global.Promise && typeof callback !== 'function') {
178 | options = callback || {} // eslint-disable-line no-param-reassign
179 | data = options // eslint-disable-line no-param-reassign
180 | return new Promise(executor)
181 | }
182 | data = data || {} // eslint-disable-line no-param-reassign
183 | return executor(callback, callback)
184 | }
185 |
186 | /**
187 | * Replaces the head of a JPEG Blob
188 | *
189 | * @param {Blob} blob Blob object
190 | * @param {ArrayBuffer} oldHead Old JPEG head
191 | * @param {ArrayBuffer} newHead New JPEG head
192 | * @returns {Blob} Combined Blob
193 | */
194 | function replaceJPEGHead(blob, oldHead, newHead) {
195 | if (!blob || !oldHead || !newHead) return null
196 | return new Blob([newHead, blobSlice.call(blob, oldHead.byteLength)], {
197 | type: 'image/jpeg'
198 | })
199 | }
200 |
201 | /**
202 | * Replaces the image head of a JPEG blob with the given one.
203 | * Returns a Promise or calls the callback with the new Blob.
204 | *
205 | * @param {Blob} blob Blob object
206 | * @param {ArrayBuffer} head New JPEG head
207 | * @param {Function} [callback] Callback function
208 | * @returns {Promise|undefined} Combined Blob
209 | */
210 | function replaceHead(blob, head, callback) {
211 | var options = { maxMetaDataSize: 1024, disableMetaDataParsers: true }
212 | if (!callback && global.Promise) {
213 | return parseMetaData(blob, options).then(function (data) {
214 | return replaceJPEGHead(blob, data.imageHead, head)
215 | })
216 | }
217 | parseMetaData(
218 | blob,
219 | function (data) {
220 | callback(replaceJPEGHead(blob, data.imageHead, head))
221 | },
222 | options
223 | )
224 | }
225 |
226 | loadImage.transform = function (img, options, callback, file, data) {
227 | if (loadImage.requiresMetaData(options)) {
228 | data = data || {} // eslint-disable-line no-param-reassign
229 | parseMetaData(
230 | file,
231 | function (result) {
232 | if (result !== data) {
233 | // eslint-disable-next-line no-console
234 | if (global.console) console.log(result)
235 | result = data // eslint-disable-line no-param-reassign
236 | }
237 | originalTransform.call(
238 | loadImage,
239 | img,
240 | options,
241 | callback,
242 | file,
243 | result
244 | )
245 | },
246 | options,
247 | data
248 | )
249 | } else {
250 | originalTransform.apply(loadImage, arguments)
251 | }
252 | }
253 |
254 | loadImage.blobSlice = blobSlice
255 | loadImage.bufferSlice = bufferSlice
256 | loadImage.replaceHead = replaceHead
257 | loadImage.parseMetaData = parseMetaData
258 | loadImage.metaDataParsers = metaDataParsers
259 | })
260 |
--------------------------------------------------------------------------------
/js/load-image-orientation.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript Load Image Orientation
3 | * https://github.com/blueimp/JavaScript-Load-Image
4 | *
5 | * Copyright 2013, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Licensed under the MIT license:
9 | * https://opensource.org/licenses/MIT
10 | */
11 |
12 | /*
13 | Exif orientation values to correctly display the letter F:
14 |
15 | 1 2
16 | ██████ ██████
17 | ██ ██
18 | ████ ████
19 | ██ ██
20 | ██ ██
21 |
22 | 3 4
23 | ██ ██
24 | ██ ██
25 | ████ ████
26 | ██ ██
27 | ██████ ██████
28 |
29 | 5 6
30 | ██████████ ██
31 | ██ ██ ██ ██
32 | ██ ██████████
33 |
34 | 7 8
35 | ██ ██████████
36 | ██ ██ ██ ██
37 | ██████████ ██
38 |
39 | */
40 |
41 | /* global define, module, require */
42 |
43 | ;(function (factory) {
44 | 'use strict'
45 | if (typeof define === 'function' && define.amd) {
46 | // Register as an anonymous AMD module:
47 | define(['./load-image', './load-image-scale', './load-image-meta'], factory)
48 | } else if (typeof module === 'object' && module.exports) {
49 | factory(
50 | require('./load-image'),
51 | require('./load-image-scale'),
52 | require('./load-image-meta')
53 | )
54 | } else {
55 | // Browser globals:
56 | factory(window.loadImage)
57 | }
58 | })(function (loadImage) {
59 | 'use strict'
60 |
61 | var originalTransform = loadImage.transform
62 | var originalRequiresCanvas = loadImage.requiresCanvas
63 | var originalRequiresMetaData = loadImage.requiresMetaData
64 | var originalTransformCoordinates = loadImage.transformCoordinates
65 | var originalGetTransformedOptions = loadImage.getTransformedOptions
66 |
67 | ;(function ($) {
68 | // Guard for non-browser environments (e.g. server-side rendering):
69 | if (!$.global.document) return
70 | // black+white 3x2 JPEG, with the following meta information set:
71 | // - EXIF Orientation: 6 (Rotated 90° CCW)
72 | // Image data layout (B=black, F=white):
73 | // BFF
74 | // BBB
75 | var testImageURL =
76 | 'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' +
77 | 'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' +
78 | 'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' +
79 | 'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAIAAwMBEQACEQEDEQH/x' +
80 | 'ABRAAEAAAAAAAAAAAAAAAAAAAAKEAEBAQADAQEAAAAAAAAAAAAGBQQDCAkCBwEBAAAAAAA' +
81 | 'AAAAAAAAAAAAAABEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AG8T9NfSMEVMhQ' +
82 | 'voP3fFiRZ+MTHDifa/95OFSZU5OzRzxkyejv8ciEfhSceSXGjS8eSdLnZc2HDm4M3BxcXw' +
83 | 'H/9k='
84 | var img = document.createElement('img')
85 | img.onload = function () {
86 | // Check if the browser supports automatic image orientation:
87 | $.orientation = img.width === 2 && img.height === 3
88 | if ($.orientation) {
89 | var canvas = $.createCanvas(1, 1, true)
90 | var ctx = canvas.getContext('2d')
91 | ctx.drawImage(img, 1, 1, 1, 1, 0, 0, 1, 1)
92 | // Check if the source image coordinates (sX, sY, sWidth, sHeight) are
93 | // correctly applied to the auto-orientated image, which should result
94 | // in a white opaque pixel (e.g. in Safari).
95 | // Browsers that show a transparent pixel (e.g. Chromium) fail to crop
96 | // auto-oriented images correctly and require a workaround, e.g.
97 | // drawing the complete source image to an intermediate canvas first.
98 | // See https://bugs.chromium.org/p/chromium/issues/detail?id=1074354
99 | $.orientationCropBug =
100 | ctx.getImageData(0, 0, 1, 1).data.toString() !== '255,255,255,255'
101 | }
102 | }
103 | img.src = testImageURL
104 | })(loadImage)
105 |
106 | /**
107 | * Determines if the orientation requires a canvas element.
108 | *
109 | * @param {object} [options] Options object
110 | * @param {boolean} [withMetaData] Is metadata required for orientation
111 | * @returns {boolean} Returns true if orientation requires canvas/meta
112 | */
113 | function requiresCanvasOrientation(options, withMetaData) {
114 | var orientation = options && options.orientation
115 | return (
116 | // Exif orientation for browsers without automatic image orientation:
117 | (orientation === true && !loadImage.orientation) ||
118 | // Orientation reset for browsers with automatic image orientation:
119 | (orientation === 1 && loadImage.orientation) ||
120 | // Orientation to defined value, requires meta for orientation reset only:
121 | ((!withMetaData || loadImage.orientation) &&
122 | orientation > 1 &&
123 | orientation < 9)
124 | )
125 | }
126 |
127 | /**
128 | * Determines if the image requires an orientation change.
129 | *
130 | * @param {number} [orientation] Defined orientation value
131 | * @param {number} [autoOrientation] Auto-orientation based on Exif data
132 | * @returns {boolean} Returns true if an orientation change is required
133 | */
134 | function requiresOrientationChange(orientation, autoOrientation) {
135 | return (
136 | orientation !== autoOrientation &&
137 | ((orientation === 1 && autoOrientation > 1 && autoOrientation < 9) ||
138 | (orientation > 1 && orientation < 9))
139 | )
140 | }
141 |
142 | /**
143 | * Determines orientation combinations that require a rotation by 180°.
144 | *
145 | * The following is a list of combinations that return true:
146 | *
147 | * 2 (flip) => 5 (rot90,flip), 7 (rot90,flip), 6 (rot90), 8 (rot90)
148 | * 4 (flip) => 5 (rot90,flip), 7 (rot90,flip), 6 (rot90), 8 (rot90)
149 | *
150 | * 5 (rot90,flip) => 2 (flip), 4 (flip), 6 (rot90), 8 (rot90)
151 | * 7 (rot90,flip) => 2 (flip), 4 (flip), 6 (rot90), 8 (rot90)
152 | *
153 | * 6 (rot90) => 2 (flip), 4 (flip), 5 (rot90,flip), 7 (rot90,flip)
154 | * 8 (rot90) => 2 (flip), 4 (flip), 5 (rot90,flip), 7 (rot90,flip)
155 | *
156 | * @param {number} [orientation] Defined orientation value
157 | * @param {number} [autoOrientation] Auto-orientation based on Exif data
158 | * @returns {boolean} Returns true if rotation by 180° is required
159 | */
160 | function requiresRot180(orientation, autoOrientation) {
161 | if (autoOrientation > 1 && autoOrientation < 9) {
162 | switch (orientation) {
163 | case 2:
164 | case 4:
165 | return autoOrientation > 4
166 | case 5:
167 | case 7:
168 | return autoOrientation % 2 === 0
169 | case 6:
170 | case 8:
171 | return (
172 | autoOrientation === 2 ||
173 | autoOrientation === 4 ||
174 | autoOrientation === 5 ||
175 | autoOrientation === 7
176 | )
177 | }
178 | }
179 | return false
180 | }
181 |
182 | // Determines if the target image should be a canvas element:
183 | loadImage.requiresCanvas = function (options) {
184 | return (
185 | requiresCanvasOrientation(options) ||
186 | originalRequiresCanvas.call(loadImage, options)
187 | )
188 | }
189 |
190 | // Determines if metadata should be loaded automatically:
191 | loadImage.requiresMetaData = function (options) {
192 | return (
193 | requiresCanvasOrientation(options, true) ||
194 | originalRequiresMetaData.call(loadImage, options)
195 | )
196 | }
197 |
198 | loadImage.transform = function (img, options, callback, file, data) {
199 | originalTransform.call(
200 | loadImage,
201 | img,
202 | options,
203 | function (img, data) {
204 | if (data) {
205 | var autoOrientation =
206 | loadImage.orientation && data.exif && data.exif.get('Orientation')
207 | if (autoOrientation > 4 && autoOrientation < 9) {
208 | // Automatic image orientation switched image dimensions
209 | var originalWidth = data.originalWidth
210 | var originalHeight = data.originalHeight
211 | data.originalWidth = originalHeight
212 | data.originalHeight = originalWidth
213 | }
214 | }
215 | callback(img, data)
216 | },
217 | file,
218 | data
219 | )
220 | }
221 |
222 | // Transforms coordinate and dimension options
223 | // based on the given orientation option:
224 | loadImage.getTransformedOptions = function (img, opts, data) {
225 | var options = originalGetTransformedOptions.call(loadImage, img, opts)
226 | var exifOrientation = data.exif && data.exif.get('Orientation')
227 | var orientation = options.orientation
228 | var autoOrientation = loadImage.orientation && exifOrientation
229 | if (orientation === true) orientation = exifOrientation
230 | if (!requiresOrientationChange(orientation, autoOrientation)) {
231 | return options
232 | }
233 | var top = options.top
234 | var right = options.right
235 | var bottom = options.bottom
236 | var left = options.left
237 | var newOptions = {}
238 | for (var i in options) {
239 | if (Object.prototype.hasOwnProperty.call(options, i)) {
240 | newOptions[i] = options[i]
241 | }
242 | }
243 | newOptions.orientation = orientation
244 | if (
245 | (orientation > 4 && !(autoOrientation > 4)) ||
246 | (orientation < 5 && autoOrientation > 4)
247 | ) {
248 | // Image dimensions and target dimensions are switched
249 | newOptions.maxWidth = options.maxHeight
250 | newOptions.maxHeight = options.maxWidth
251 | newOptions.minWidth = options.minHeight
252 | newOptions.minHeight = options.minWidth
253 | newOptions.sourceWidth = options.sourceHeight
254 | newOptions.sourceHeight = options.sourceWidth
255 | }
256 | if (autoOrientation > 1) {
257 | // Browsers which correctly apply source image coordinates to
258 | // auto-oriented images
259 | switch (autoOrientation) {
260 | case 2:
261 | // Horizontal flip
262 | right = options.left
263 | left = options.right
264 | break
265 | case 3:
266 | // 180° Rotate CCW
267 | top = options.bottom
268 | right = options.left
269 | bottom = options.top
270 | left = options.right
271 | break
272 | case 4:
273 | // Vertical flip
274 | top = options.bottom
275 | bottom = options.top
276 | break
277 | case 5:
278 | // Horizontal flip + 90° Rotate CCW
279 | top = options.left
280 | right = options.bottom
281 | bottom = options.right
282 | left = options.top
283 | break
284 | case 6:
285 | // 90° Rotate CCW
286 | top = options.left
287 | right = options.top
288 | bottom = options.right
289 | left = options.bottom
290 | break
291 | case 7:
292 | // Vertical flip + 90° Rotate CCW
293 | top = options.right
294 | right = options.top
295 | bottom = options.left
296 | left = options.bottom
297 | break
298 | case 8:
299 | // 90° Rotate CW
300 | top = options.right
301 | right = options.bottom
302 | bottom = options.left
303 | left = options.top
304 | break
305 | }
306 | // Some orientation combinations require additional rotation by 180°:
307 | if (requiresRot180(orientation, autoOrientation)) {
308 | var tmpTop = top
309 | var tmpRight = right
310 | top = bottom
311 | right = left
312 | bottom = tmpTop
313 | left = tmpRight
314 | }
315 | }
316 | newOptions.top = top
317 | newOptions.right = right
318 | newOptions.bottom = bottom
319 | newOptions.left = left
320 | // Account for defined browser orientation:
321 | switch (orientation) {
322 | case 2:
323 | // Horizontal flip
324 | newOptions.right = left
325 | newOptions.left = right
326 | break
327 | case 3:
328 | // 180° Rotate CCW
329 | newOptions.top = bottom
330 | newOptions.right = left
331 | newOptions.bottom = top
332 | newOptions.left = right
333 | break
334 | case 4:
335 | // Vertical flip
336 | newOptions.top = bottom
337 | newOptions.bottom = top
338 | break
339 | case 5:
340 | // Vertical flip + 90° Rotate CW
341 | newOptions.top = left
342 | newOptions.right = bottom
343 | newOptions.bottom = right
344 | newOptions.left = top
345 | break
346 | case 6:
347 | // 90° Rotate CW
348 | newOptions.top = right
349 | newOptions.right = bottom
350 | newOptions.bottom = left
351 | newOptions.left = top
352 | break
353 | case 7:
354 | // Horizontal flip + 90° Rotate CW
355 | newOptions.top = right
356 | newOptions.right = top
357 | newOptions.bottom = left
358 | newOptions.left = bottom
359 | break
360 | case 8:
361 | // 90° Rotate CCW
362 | newOptions.top = left
363 | newOptions.right = top
364 | newOptions.bottom = right
365 | newOptions.left = bottom
366 | break
367 | }
368 | return newOptions
369 | }
370 |
371 | // Transform image orientation based on the given EXIF orientation option:
372 | loadImage.transformCoordinates = function (canvas, options, data) {
373 | originalTransformCoordinates.call(loadImage, canvas, options, data)
374 | var orientation = options.orientation
375 | var autoOrientation =
376 | loadImage.orientation && data.exif && data.exif.get('Orientation')
377 | if (!requiresOrientationChange(orientation, autoOrientation)) {
378 | return
379 | }
380 | var ctx = canvas.getContext('2d')
381 | var width = canvas.width
382 | var height = canvas.height
383 | var sourceWidth = width
384 | var sourceHeight = height
385 | if (
386 | (orientation > 4 && !(autoOrientation > 4)) ||
387 | (orientation < 5 && autoOrientation > 4)
388 | ) {
389 | // Image dimensions and target dimensions are switched
390 | canvas.width = height
391 | canvas.height = width
392 | }
393 | if (orientation > 4) {
394 | // Destination and source dimensions are switched
395 | sourceWidth = height
396 | sourceHeight = width
397 | }
398 | // Reset automatic browser orientation:
399 | switch (autoOrientation) {
400 | case 2:
401 | // Horizontal flip
402 | ctx.translate(sourceWidth, 0)
403 | ctx.scale(-1, 1)
404 | break
405 | case 3:
406 | // 180° Rotate CCW
407 | ctx.translate(sourceWidth, sourceHeight)
408 | ctx.rotate(Math.PI)
409 | break
410 | case 4:
411 | // Vertical flip
412 | ctx.translate(0, sourceHeight)
413 | ctx.scale(1, -1)
414 | break
415 | case 5:
416 | // Horizontal flip + 90° Rotate CCW
417 | ctx.rotate(-0.5 * Math.PI)
418 | ctx.scale(-1, 1)
419 | break
420 | case 6:
421 | // 90° Rotate CCW
422 | ctx.rotate(-0.5 * Math.PI)
423 | ctx.translate(-sourceWidth, 0)
424 | break
425 | case 7:
426 | // Vertical flip + 90° Rotate CCW
427 | ctx.rotate(-0.5 * Math.PI)
428 | ctx.translate(-sourceWidth, sourceHeight)
429 | ctx.scale(1, -1)
430 | break
431 | case 8:
432 | // 90° Rotate CW
433 | ctx.rotate(0.5 * Math.PI)
434 | ctx.translate(0, -sourceHeight)
435 | break
436 | }
437 | // Some orientation combinations require additional rotation by 180°:
438 | if (requiresRot180(orientation, autoOrientation)) {
439 | ctx.translate(sourceWidth, sourceHeight)
440 | ctx.rotate(Math.PI)
441 | }
442 | switch (orientation) {
443 | case 2:
444 | // Horizontal flip
445 | ctx.translate(width, 0)
446 | ctx.scale(-1, 1)
447 | break
448 | case 3:
449 | // 180° Rotate CCW
450 | ctx.translate(width, height)
451 | ctx.rotate(Math.PI)
452 | break
453 | case 4:
454 | // Vertical flip
455 | ctx.translate(0, height)
456 | ctx.scale(1, -1)
457 | break
458 | case 5:
459 | // Vertical flip + 90° Rotate CW
460 | ctx.rotate(0.5 * Math.PI)
461 | ctx.scale(1, -1)
462 | break
463 | case 6:
464 | // 90° Rotate CW
465 | ctx.rotate(0.5 * Math.PI)
466 | ctx.translate(0, -height)
467 | break
468 | case 7:
469 | // Horizontal flip + 90° Rotate CW
470 | ctx.rotate(0.5 * Math.PI)
471 | ctx.translate(width, -height)
472 | ctx.scale(-1, 1)
473 | break
474 | case 8:
475 | // 90° Rotate CCW
476 | ctx.rotate(-0.5 * Math.PI)
477 | ctx.translate(-width, 0)
478 | break
479 | }
480 | }
481 | })
482 |
--------------------------------------------------------------------------------
/js/load-image-scale.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript Load Image Scaling
3 | * https://github.com/blueimp/JavaScript-Load-Image
4 | *
5 | * Copyright 2011, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Licensed under the MIT license:
9 | * https://opensource.org/licenses/MIT
10 | */
11 |
12 | /* global define, module, require */
13 |
14 | ;(function (factory) {
15 | 'use strict'
16 | if (typeof define === 'function' && define.amd) {
17 | // Register as an anonymous AMD module:
18 | define(['./load-image'], factory)
19 | } else if (typeof module === 'object' && module.exports) {
20 | factory(require('./load-image'))
21 | } else {
22 | // Browser globals:
23 | factory(window.loadImage)
24 | }
25 | })(function (loadImage) {
26 | 'use strict'
27 |
28 | var originalTransform = loadImage.transform
29 |
30 | loadImage.createCanvas = function (width, height, offscreen) {
31 | if (offscreen && loadImage.global.OffscreenCanvas) {
32 | return new OffscreenCanvas(width, height)
33 | }
34 | var canvas = document.createElement('canvas')
35 | canvas.width = width
36 | canvas.height = height
37 | return canvas
38 | }
39 |
40 | loadImage.transform = function (img, options, callback, file, data) {
41 | originalTransform.call(
42 | loadImage,
43 | loadImage.scale(img, options, data),
44 | options,
45 | callback,
46 | file,
47 | data
48 | )
49 | }
50 |
51 | // Transform image coordinates, allows to override e.g.
52 | // the canvas orientation based on the orientation option,
53 | // gets canvas, options and data passed as arguments:
54 | loadImage.transformCoordinates = function () {}
55 |
56 | // Returns transformed options, allows to override e.g.
57 | // maxWidth, maxHeight and crop options based on the aspectRatio.
58 | // gets img, options, data passed as arguments:
59 | loadImage.getTransformedOptions = function (img, options) {
60 | var aspectRatio = options.aspectRatio
61 | var newOptions
62 | var i
63 | var width
64 | var height
65 | if (!aspectRatio) {
66 | return options
67 | }
68 | newOptions = {}
69 | for (i in options) {
70 | if (Object.prototype.hasOwnProperty.call(options, i)) {
71 | newOptions[i] = options[i]
72 | }
73 | }
74 | newOptions.crop = true
75 | width = img.naturalWidth || img.width
76 | height = img.naturalHeight || img.height
77 | if (width / height > aspectRatio) {
78 | newOptions.maxWidth = height * aspectRatio
79 | newOptions.maxHeight = height
80 | } else {
81 | newOptions.maxWidth = width
82 | newOptions.maxHeight = width / aspectRatio
83 | }
84 | return newOptions
85 | }
86 |
87 | // Canvas render method, allows to implement a different rendering algorithm:
88 | loadImage.drawImage = function (
89 | img,
90 | canvas,
91 | sourceX,
92 | sourceY,
93 | sourceWidth,
94 | sourceHeight,
95 | destWidth,
96 | destHeight,
97 | options
98 | ) {
99 | var ctx = canvas.getContext('2d')
100 | if (options.imageSmoothingEnabled === false) {
101 | ctx.msImageSmoothingEnabled = false
102 | ctx.imageSmoothingEnabled = false
103 | } else if (options.imageSmoothingQuality) {
104 | ctx.imageSmoothingQuality = options.imageSmoothingQuality
105 | }
106 | ctx.drawImage(
107 | img,
108 | sourceX,
109 | sourceY,
110 | sourceWidth,
111 | sourceHeight,
112 | 0,
113 | 0,
114 | destWidth,
115 | destHeight
116 | )
117 | return ctx
118 | }
119 |
120 | // Determines if the target image should be a canvas element:
121 | loadImage.requiresCanvas = function (options) {
122 | return options.canvas || options.crop || !!options.aspectRatio
123 | }
124 |
125 | // Scales and/or crops the given image (img or canvas HTML element)
126 | // using the given options:
127 | loadImage.scale = function (img, options, data) {
128 | // eslint-disable-next-line no-param-reassign
129 | options = options || {}
130 | // eslint-disable-next-line no-param-reassign
131 | data = data || {}
132 | var useCanvas =
133 | img.getContext ||
134 | (loadImage.requiresCanvas(options) &&
135 | !!loadImage.global.HTMLCanvasElement)
136 | var width = img.naturalWidth || img.width
137 | var height = img.naturalHeight || img.height
138 | var destWidth = width
139 | var destHeight = height
140 | var maxWidth
141 | var maxHeight
142 | var minWidth
143 | var minHeight
144 | var sourceWidth
145 | var sourceHeight
146 | var sourceX
147 | var sourceY
148 | var pixelRatio
149 | var downsamplingRatio
150 | var tmp
151 | var canvas
152 | /**
153 | * Scales up image dimensions
154 | */
155 | function scaleUp() {
156 | var scale = Math.max(
157 | (minWidth || destWidth) / destWidth,
158 | (minHeight || destHeight) / destHeight
159 | )
160 | if (scale > 1) {
161 | destWidth *= scale
162 | destHeight *= scale
163 | }
164 | }
165 | /**
166 | * Scales down image dimensions
167 | */
168 | function scaleDown() {
169 | var scale = Math.min(
170 | (maxWidth || destWidth) / destWidth,
171 | (maxHeight || destHeight) / destHeight
172 | )
173 | if (scale < 1) {
174 | destWidth *= scale
175 | destHeight *= scale
176 | }
177 | }
178 | if (useCanvas) {
179 | // eslint-disable-next-line no-param-reassign
180 | options = loadImage.getTransformedOptions(img, options, data)
181 | sourceX = options.left || 0
182 | sourceY = options.top || 0
183 | if (options.sourceWidth) {
184 | sourceWidth = options.sourceWidth
185 | if (options.right !== undefined && options.left === undefined) {
186 | sourceX = width - sourceWidth - options.right
187 | }
188 | } else {
189 | sourceWidth = width - sourceX - (options.right || 0)
190 | }
191 | if (options.sourceHeight) {
192 | sourceHeight = options.sourceHeight
193 | if (options.bottom !== undefined && options.top === undefined) {
194 | sourceY = height - sourceHeight - options.bottom
195 | }
196 | } else {
197 | sourceHeight = height - sourceY - (options.bottom || 0)
198 | }
199 | destWidth = sourceWidth
200 | destHeight = sourceHeight
201 | }
202 | maxWidth = options.maxWidth
203 | maxHeight = options.maxHeight
204 | minWidth = options.minWidth
205 | minHeight = options.minHeight
206 | if (useCanvas && maxWidth && maxHeight && options.crop) {
207 | destWidth = maxWidth
208 | destHeight = maxHeight
209 | tmp = sourceWidth / sourceHeight - maxWidth / maxHeight
210 | if (tmp < 0) {
211 | sourceHeight = (maxHeight * sourceWidth) / maxWidth
212 | if (options.top === undefined && options.bottom === undefined) {
213 | sourceY = (height - sourceHeight) / 2
214 | }
215 | } else if (tmp > 0) {
216 | sourceWidth = (maxWidth * sourceHeight) / maxHeight
217 | if (options.left === undefined && options.right === undefined) {
218 | sourceX = (width - sourceWidth) / 2
219 | }
220 | }
221 | } else {
222 | if (options.contain || options.cover) {
223 | minWidth = maxWidth = maxWidth || minWidth
224 | minHeight = maxHeight = maxHeight || minHeight
225 | }
226 | if (options.cover) {
227 | scaleDown()
228 | scaleUp()
229 | } else {
230 | scaleUp()
231 | scaleDown()
232 | }
233 | }
234 | if (useCanvas) {
235 | pixelRatio = options.pixelRatio
236 | if (
237 | pixelRatio > 1 &&
238 | // Check if the image has not yet had the device pixel ratio applied:
239 | !(
240 | img.style.width &&
241 | Math.floor(parseFloat(img.style.width, 10)) ===
242 | Math.floor(width / pixelRatio)
243 | )
244 | ) {
245 | destWidth *= pixelRatio
246 | destHeight *= pixelRatio
247 | }
248 | // Check if workaround for Chromium orientation crop bug is required:
249 | // https://bugs.chromium.org/p/chromium/issues/detail?id=1074354
250 | if (
251 | loadImage.orientationCropBug &&
252 | !img.getContext &&
253 | (sourceX || sourceY || sourceWidth !== width || sourceHeight !== height)
254 | ) {
255 | // Write the complete source image to an intermediate canvas first:
256 | tmp = img
257 | // eslint-disable-next-line no-param-reassign
258 | img = loadImage.createCanvas(width, height, true)
259 | loadImage.drawImage(
260 | tmp,
261 | img,
262 | 0,
263 | 0,
264 | width,
265 | height,
266 | width,
267 | height,
268 | options
269 | )
270 | }
271 | downsamplingRatio = options.downsamplingRatio
272 | if (
273 | downsamplingRatio > 0 &&
274 | downsamplingRatio < 1 &&
275 | destWidth < sourceWidth &&
276 | destHeight < sourceHeight
277 | ) {
278 | while (sourceWidth * downsamplingRatio > destWidth) {
279 | canvas = loadImage.createCanvas(
280 | sourceWidth * downsamplingRatio,
281 | sourceHeight * downsamplingRatio,
282 | true
283 | )
284 | loadImage.drawImage(
285 | img,
286 | canvas,
287 | sourceX,
288 | sourceY,
289 | sourceWidth,
290 | sourceHeight,
291 | canvas.width,
292 | canvas.height,
293 | options
294 | )
295 | sourceX = 0
296 | sourceY = 0
297 | sourceWidth = canvas.width
298 | sourceHeight = canvas.height
299 | // eslint-disable-next-line no-param-reassign
300 | img = canvas
301 | }
302 | }
303 | canvas = loadImage.createCanvas(destWidth, destHeight)
304 | loadImage.transformCoordinates(canvas, options, data)
305 | if (pixelRatio > 1) {
306 | canvas.style.width = canvas.width / pixelRatio + 'px'
307 | }
308 | loadImage
309 | .drawImage(
310 | img,
311 | canvas,
312 | sourceX,
313 | sourceY,
314 | sourceWidth,
315 | sourceHeight,
316 | destWidth,
317 | destHeight,
318 | options
319 | )
320 | .setTransform(1, 0, 0, 1, 0, 0) // reset to the identity matrix
321 | return canvas
322 | }
323 | img.width = destWidth
324 | img.height = destHeight
325 | return img
326 | }
327 | })
328 |
--------------------------------------------------------------------------------
/js/load-image.all.min.js:
--------------------------------------------------------------------------------
1 | !function(c){"use strict";var t=c.URL||c.webkitURL;function f(e){return!!t&&t.createObjectURL(e)}function i(e){return!!t&&t.revokeObjectURL(e)}function u(e,t){!e||"blob:"!==e.slice(0,5)||t&&t.noRevoke||i(e)}function d(e,t,i,a){if(!c.FileReader)return!1;var n=new FileReader;n.onload=function(){t.call(n,this.result)},i&&(n.onabort=n.onerror=function(){i.call(n,this.error)});a=n[a||"readAsDataURL"];return a?(a.call(n,e),n):void 0}function g(e,t){return Object.prototype.toString.call(t)==="[object "+e+"]"}function m(s,e,l){function t(i,a){var n,r=document.createElement("img");function o(e,t){i!==a?e instanceof Error?a(e):((t=t||{}).image=e,i(t)):i&&i(e,t)}function e(e,t){t&&c.console&&console.log(t),e&&g("Blob",e)?n=f(s=e):(n=s,l&&l.crossOrigin&&(r.crossOrigin=l.crossOrigin)),r.src=n}return r.onerror=function(e){u(n,l),a&&a.call(r,e)},r.onload=function(){u(n,l);var e={originalWidth:r.naturalWidth||r.width,originalHeight:r.naturalHeight||r.height};try{m.transform(r,l,o,s,e)}catch(t){a&&a(t)}},"string"==typeof s?(m.requiresMetaData(l)?m.fetchBlob(s,e,l):e(),r):g("Blob",s)||g("File",s)?(n=f(s))?(r.src=n,r):d(s,function(e){r.src=e},a):void 0}return c.Promise&&"function"!=typeof e?(l=e,new Promise(t)):t(e,e)}m.requiresMetaData=function(e){return e&&e.meta},m.fetchBlob=function(e,t){t()},m.transform=function(e,t,i,a,n){i(e,n)},m.global=c,m.readFile=d,m.isInstanceOf=g,m.createObjectURL=f,m.revokeObjectURL=i,"function"==typeof define&&define.amd?define(function(){return m}):"object"==typeof module&&module.exports?module.exports=m:c.loadImage=m}("undefined"!=typeof window&&window||this),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image"],e):"object"==typeof module&&module.exports?e(require("./load-image")):e(window.loadImage)}(function(E){"use strict";var r=E.transform;E.createCanvas=function(e,t,i){if(i&&E.global.OffscreenCanvas)return new OffscreenCanvas(e,t);i=document.createElement("canvas");return i.width=e,i.height=t,i},E.transform=function(e,t,i,a,n){r.call(E,E.scale(e,t,n),t,i,a,n)},E.transformCoordinates=function(){},E.getTransformedOptions=function(e,t){var i,a,n,r=t.aspectRatio;if(!r)return t;for(a in i={},t)Object.prototype.hasOwnProperty.call(t,a)&&(i[a]=t[a]);return i.crop=!0,r<(n=e.naturalWidth||e.width)/(e=e.naturalHeight||e.height)?(i.maxWidth=e*r,i.maxHeight=e):(i.maxWidth=n,i.maxHeight=n/r),i},E.drawImage=function(e,t,i,a,n,r,o,s,l){t=t.getContext("2d");return!1===l.imageSmoothingEnabled?(t.msImageSmoothingEnabled=!1,t.imageSmoothingEnabled=!1):l.imageSmoothingQuality&&(t.imageSmoothingQuality=l.imageSmoothingQuality),t.drawImage(e,i,a,n,r,0,0,o,s),t},E.requiresCanvas=function(e){return e.canvas||e.crop||!!e.aspectRatio},E.scale=function(e,t,i){t=t||{},i=i||{};var a,n,r,o,s,l,c,f,u,d,g,m=e.getContext||E.requiresCanvas(t)&&!!E.global.HTMLCanvasElement,h=e.naturalWidth||e.width,p=e.naturalHeight||e.height,A=h,b=p;function y(){var e=Math.max((r||A)/A,(o||b)/b);1t.byteLength){console.log("Invalid JPEG metadata: Invalid segment size.");break}if((n=h.jpeg[i])&&!u.disableMetaDataParsers)for(r=0;re.byteLength)console.log("Invalid Exif data: Invalid directory offset.");else{if(!((c=i+2+12*(l=e.getUint16(i,a)))+4>e.byteLength)){for(f=0;fe.byteLength)){if(1===n)return u.getValue(e,o,r);for(s=[],l=0;ll.byteLength)console.log("Invalid Exif data: Invalid segment size.");else if(0===l.getUint16(e+8)){switch(l.getUint16(g)){case 18761:f=!0;break;case 19789:f=!1;break;default:return void console.log("Invalid Exif data: Invalid byte alignment marker.")}42===l.getUint16(g+2,f)?(e=l.getUint32(g+4,f),c.exif=new m,i.disableExifOffsets||(c.exifOffsets=new m,c.exifTiffOffset=g,c.exifLittleEndian=f),(e=A(l,g,g+e,f,c.exif,c.exifOffsets,u,d))&&p(u,d,"ifd1")&&(c.exif.ifd1=e,c.exifOffsets&&(c.exifOffsets.ifd1=g+e)),Object.keys(c.exif.ifds).forEach(function(e){var t,i,a,n,r,o,s;i=e,a=l,n=g,r=f,o=u,s=d,(e=(t=c).exif[i])&&(t.exif[i]=new m(i),t.exifOffsets&&(t.exifOffsets[i]=new m(i)),A(a,n,n+e,r,t.exif[i],t.exifOffsets&&t.exifOffsets[i],o&&o[i],s&&s[i]))}),(e=c.exif.ifd1)&&e[513]&&(e[513]=function(e,t,i){if(i){if(!(t+i>e.byteLength))return new Blob([n.bufferSlice.call(e.buffer,t,t+i)],{type:"image/jpeg"});console.log("Invalid Exif data: Invalid thumbnail data.")}}(l,g+e[513],e[514]))):console.log("Invalid Exif data: Missing TIFF marker.")}else console.log("Invalid Exif data: Missing byte alignment offset.")}},n.metaDataParsers.jpeg[65505].push(n.parseExifData),n.exifWriters={274:function(e,t,i){var a=t.exifOffsets[274];return a&&new DataView(e,a+8,2).setUint16(0,i,t.exifLittleEndian),e}},n.writeExifData=function(e,t,i,a){return n.exifWriters[t.exif.map[i]](e,t,a)},n.ExifMap=m}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-exif"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-exif")):e(window.loadImage)}(function(e){"use strict";var n=e.ExifMap.prototype;n.tags={256:"ImageWidth",257:"ImageHeight",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",274:"Orientation",277:"SamplesPerPixel",284:"PlanarConfiguration",530:"YCbCrSubSampling",531:"YCbCrPositioning",282:"XResolution",283:"YResolution",296:"ResolutionUnit",273:"StripOffsets",278:"RowsPerStrip",279:"StripByteCounts",513:"JPEGInterchangeFormat",514:"JPEGInterchangeFormatLength",301:"TransferFunction",318:"WhitePoint",319:"PrimaryChromaticities",529:"YCbCrCoefficients",532:"ReferenceBlackWhite",306:"DateTime",270:"ImageDescription",271:"Make",272:"Model",305:"Software",315:"Artist",33432:"Copyright",34665:{36864:"ExifVersion",40960:"FlashpixVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",42240:"Gamma",37121:"ComponentsConfiguration",37122:"CompressedBitsPerPixel",37500:"MakerNote",37510:"UserComment",40964:"RelatedSoundFile",36867:"DateTimeOriginal",36868:"DateTimeDigitized",36880:"OffsetTime",36881:"OffsetTimeOriginal",36882:"OffsetTimeDigitized",37520:"SubSecTime",37521:"SubSecTimeOriginal",37522:"SubSecTimeDigitized",33434:"ExposureTime",33437:"FNumber",34850:"ExposureProgram",34852:"SpectralSensitivity",34855:"PhotographicSensitivity",34856:"OECF",34864:"SensitivityType",34865:"StandardOutputSensitivity",34866:"RecommendedExposureIndex",34867:"ISOSpeed",34868:"ISOSpeedLatitudeyyy",34869:"ISOSpeedLatitudezzz",37377:"ShutterSpeedValue",37378:"ApertureValue",37379:"BrightnessValue",37380:"ExposureBias",37381:"MaxApertureValue",37382:"SubjectDistance",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37396:"SubjectArea",37386:"FocalLength",41483:"FlashEnergy",41484:"SpatialFrequencyResponse",41486:"FocalPlaneXResolution",41487:"FocalPlaneYResolution",41488:"FocalPlaneResolutionUnit",41492:"SubjectLocation",41493:"ExposureIndex",41495:"SensingMethod",41728:"FileSource",41729:"SceneType",41730:"CFAPattern",41985:"CustomRendered",41986:"ExposureMode",41987:"WhiteBalance",41988:"DigitalZoomRatio",41989:"FocalLengthIn35mmFilm",41990:"SceneCaptureType",41991:"GainControl",41992:"Contrast",41993:"Saturation",41994:"Sharpness",41995:"DeviceSettingDescription",41996:"SubjectDistanceRange",42016:"ImageUniqueID",42032:"CameraOwnerName",42033:"BodySerialNumber",42034:"LensSpecification",42035:"LensMake",42036:"LensModel",42037:"LensSerialNumber"},34853:{0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude",5:"GPSAltitudeRef",6:"GPSAltitude",7:"GPSTimeStamp",8:"GPSSatellites",9:"GPSStatus",10:"GPSMeasureMode",11:"GPSDOP",12:"GPSSpeedRef",13:"GPSSpeed",14:"GPSTrackRef",15:"GPSTrack",16:"GPSImgDirectionRef",17:"GPSImgDirection",18:"GPSMapDatum",19:"GPSDestLatitudeRef",20:"GPSDestLatitude",21:"GPSDestLongitudeRef",22:"GPSDestLongitude",23:"GPSDestBearingRef",24:"GPSDestBearing",25:"GPSDestDistanceRef",26:"GPSDestDistance",27:"GPSProcessingMethod",28:"GPSAreaInformation",29:"GPSDateStamp",30:"GPSDifferential",31:"GPSHPositioningError"},40965:{1:"InteroperabilityIndex"}},n.tags.ifd1=n.tags,n.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:{0:"Flash did not fire",1:"Flash fired",5:"Strobe return light not detected",7:"Strobe return light detected",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"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"}},n.getText=function(e){var t=this.get(e);switch(e){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[e][t];case"ExifVersion":case"FlashpixVersion":return t?String.fromCharCode(t[0],t[1],t[2],t[3]):void 0;case"ComponentsConfiguration":return t?this.stringValues[e][t[0]]+this.stringValues[e][t[1]]+this.stringValues[e][t[2]]+this.stringValues[e][t[3]]:void 0;case"GPSVersionID":return t?t[0]+"."+t[1]+"."+t[2]+"."+t[3]:void 0}return String(t)},n.getAll=function(){var e,t,i={};for(e in this)Object.prototype.hasOwnProperty.call(this,e)&&((t=this[e])&&t.getAll?i[this.ifds[e].name]=t.getAll():(t=this.tags[e])&&(i[t]=this.getText(t)));return i},n.getName=function(e){var t=this.tags[e];return"object"==typeof t?this.ifds[e].name:t},function(){var e,t,i,a=n.tags;for(e in a)if(Object.prototype.hasOwnProperty.call(a,e))if(t=n.ifds[e])for(e in i=a[e])Object.prototype.hasOwnProperty.call(i,e)&&(t.map[i[e]]=Number(e));else n.map[a[e]]=Number(e)}()}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-meta"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-meta")):e(window.loadImage)}(function(e){"use strict";function l(){}function u(e,t,i,a,n){return"binary"===t.types[e]?new Blob([i.buffer.slice(a,a+n)]):"Uint16"===t.types[e]?i.getUint16(a):function(e,t,i){for(var a="",n=t+i,r=t;r} Object
101 | */
102 | function loadImage(file, callback, options) {
103 | /**
104 | * Promise executor
105 | *
106 | * @param {Function} resolve Resolution function
107 | * @param {Function} reject Rejection function
108 | * @returns {HTMLImageElement|FileReader} Object
109 | */
110 | function executor(resolve, reject) {
111 | var img = document.createElement('img')
112 | var url
113 | /**
114 | * Callback for the fetchBlob call.
115 | *
116 | * @param {HTMLImageElement|HTMLCanvasElement} img Error object
117 | * @param {object} data Data object
118 | * @returns {undefined} Undefined
119 | */
120 | function resolveWrapper(img, data) {
121 | if (resolve === reject) {
122 | // Not using Promises
123 | if (resolve) resolve(img, data)
124 | return
125 | } else if (img instanceof Error) {
126 | reject(img)
127 | return
128 | }
129 | data = data || {} // eslint-disable-line no-param-reassign
130 | data.image = img
131 | resolve(data)
132 | }
133 | /**
134 | * Callback for the fetchBlob call.
135 | *
136 | * @param {Blob} blob Blob object
137 | * @param {Error} err Error object
138 | */
139 | function fetchBlobCallback(blob, err) {
140 | if (err && $.console) console.log(err) // eslint-disable-line no-console
141 | if (blob && isInstanceOf('Blob', blob)) {
142 | file = blob // eslint-disable-line no-param-reassign
143 | url = createObjectURL(file)
144 | } else {
145 | url = file
146 | if (options && options.crossOrigin) {
147 | img.crossOrigin = options.crossOrigin
148 | }
149 | }
150 | img.src = url
151 | }
152 | img.onerror = function (event) {
153 | revokeHelper(url, options)
154 | if (reject) reject.call(img, event)
155 | }
156 | img.onload = function () {
157 | revokeHelper(url, options)
158 | var data = {
159 | originalWidth: img.naturalWidth || img.width,
160 | originalHeight: img.naturalHeight || img.height
161 | }
162 | try {
163 | loadImage.transform(img, options, resolveWrapper, file, data)
164 | } catch (error) {
165 | if (reject) reject(error)
166 | }
167 | }
168 | if (typeof file === 'string') {
169 | if (loadImage.requiresMetaData(options)) {
170 | loadImage.fetchBlob(file, fetchBlobCallback, options)
171 | } else {
172 | fetchBlobCallback()
173 | }
174 | return img
175 | } else if (isInstanceOf('Blob', file) || isInstanceOf('File', file)) {
176 | url = createObjectURL(file)
177 | if (url) {
178 | img.src = url
179 | return img
180 | }
181 | return readFile(
182 | file,
183 | function (url) {
184 | img.src = url
185 | },
186 | reject
187 | )
188 | }
189 | }
190 | if ($.Promise && typeof callback !== 'function') {
191 | options = callback // eslint-disable-line no-param-reassign
192 | return new Promise(executor)
193 | }
194 | return executor(callback, callback)
195 | }
196 |
197 | // Determines if metadata should be loaded automatically.
198 | // Requires the load image meta extension to load metadata.
199 | loadImage.requiresMetaData = function (options) {
200 | return options && options.meta
201 | }
202 |
203 | // If the callback given to this function returns a blob, it is used as image
204 | // source instead of the original url and overrides the file argument used in
205 | // the onload and onerror event callbacks:
206 | loadImage.fetchBlob = function (url, callback) {
207 | callback()
208 | }
209 |
210 | loadImage.transform = function (img, options, callback, file, data) {
211 | callback(img, data)
212 | }
213 |
214 | loadImage.global = $
215 | loadImage.readFile = readFile
216 | loadImage.isInstanceOf = isInstanceOf
217 | loadImage.createObjectURL = createObjectURL
218 | loadImage.revokeObjectURL = revokeObjectURL
219 |
220 | if (typeof define === 'function' && define.amd) {
221 | define(function () {
222 | return loadImage
223 | })
224 | } else if (typeof module === 'object' && module.exports) {
225 | module.exports = loadImage
226 | } else {
227 | $.loadImage = loadImage
228 | }
229 | })((typeof window !== 'undefined' && window) || this)
230 |
--------------------------------------------------------------------------------
/js/vendor/canvas-to-blob.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript Canvas to Blob
3 | * https://github.com/blueimp/JavaScript-Canvas-to-Blob
4 | *
5 | * Copyright 2012, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Licensed under the MIT license:
9 | * https://opensource.org/licenses/MIT
10 | *
11 | * Based on stackoverflow user Stoive's code snippet:
12 | * http://stackoverflow.com/q/4998908
13 | */
14 |
15 | /* global define, Uint8Array, ArrayBuffer, module */
16 |
17 | ;(function (window) {
18 | 'use strict'
19 |
20 | var CanvasPrototype =
21 | window.HTMLCanvasElement && window.HTMLCanvasElement.prototype
22 | var hasBlobConstructor =
23 | window.Blob &&
24 | (function () {
25 | try {
26 | return Boolean(new Blob())
27 | } catch (e) {
28 | return false
29 | }
30 | })()
31 | var hasArrayBufferViewSupport =
32 | hasBlobConstructor &&
33 | window.Uint8Array &&
34 | (function () {
35 | try {
36 | return new Blob([new Uint8Array(100)]).size === 100
37 | } catch (e) {
38 | return false
39 | }
40 | })()
41 | var BlobBuilder =
42 | window.BlobBuilder ||
43 | window.WebKitBlobBuilder ||
44 | window.MozBlobBuilder ||
45 | window.MSBlobBuilder
46 | var dataURIPattern = /^data:((.*?)(;charset=.*?)?)(;base64)?,/
47 | var dataURLtoBlob =
48 | (hasBlobConstructor || BlobBuilder) &&
49 | window.atob &&
50 | window.ArrayBuffer &&
51 | window.Uint8Array &&
52 | function (dataURI) {
53 | var matches,
54 | mediaType,
55 | isBase64,
56 | dataString,
57 | byteString,
58 | arrayBuffer,
59 | intArray,
60 | i,
61 | bb
62 | // Parse the dataURI components as per RFC 2397
63 | matches = dataURI.match(dataURIPattern)
64 | if (!matches) {
65 | throw new Error('invalid data URI')
66 | }
67 | // Default to text/plain;charset=US-ASCII
68 | mediaType = matches[2]
69 | ? matches[1]
70 | : 'text/plain' + (matches[3] || ';charset=US-ASCII')
71 | isBase64 = !!matches[4]
72 | dataString = dataURI.slice(matches[0].length)
73 | if (isBase64) {
74 | // Convert base64 to raw binary data held in a string:
75 | byteString = atob(dataString)
76 | } else {
77 | // Convert base64/URLEncoded data component to raw binary:
78 | byteString = decodeURIComponent(dataString)
79 | }
80 | // Write the bytes of the string to an ArrayBuffer:
81 | arrayBuffer = new ArrayBuffer(byteString.length)
82 | intArray = new Uint8Array(arrayBuffer)
83 | for (i = 0; i < byteString.length; i += 1) {
84 | intArray[i] = byteString.charCodeAt(i)
85 | }
86 | // Write the ArrayBuffer (or ArrayBufferView) to a blob:
87 | if (hasBlobConstructor) {
88 | return new Blob([hasArrayBufferViewSupport ? intArray : arrayBuffer], {
89 | type: mediaType
90 | })
91 | }
92 | bb = new BlobBuilder()
93 | bb.append(arrayBuffer)
94 | return bb.getBlob(mediaType)
95 | }
96 | if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
97 | if (CanvasPrototype.mozGetAsFile) {
98 | CanvasPrototype.toBlob = function (callback, type, quality) {
99 | var self = this
100 | setTimeout(function () {
101 | if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
102 | callback(dataURLtoBlob(self.toDataURL(type, quality)))
103 | } else {
104 | callback(self.mozGetAsFile('blob', type))
105 | }
106 | })
107 | }
108 | } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
109 | if (CanvasPrototype.msToBlob) {
110 | CanvasPrototype.toBlob = function (callback, type, quality) {
111 | var self = this
112 | setTimeout(function () {
113 | if (
114 | ((type && type !== 'image/png') || quality) &&
115 | CanvasPrototype.toDataURL &&
116 | dataURLtoBlob
117 | ) {
118 | callback(dataURLtoBlob(self.toDataURL(type, quality)))
119 | } else {
120 | callback(self.msToBlob(type))
121 | }
122 | })
123 | }
124 | } else {
125 | CanvasPrototype.toBlob = function (callback, type, quality) {
126 | var self = this
127 | setTimeout(function () {
128 | callback(dataURLtoBlob(self.toDataURL(type, quality)))
129 | })
130 | }
131 | }
132 | }
133 | }
134 | if (typeof define === 'function' && define.amd) {
135 | define(function () {
136 | return dataURLtoBlob
137 | })
138 | } else if (typeof module === 'object' && module.exports) {
139 | module.exports = dataURLtoBlob
140 | } else {
141 | window.dataURLtoBlob = dataURLtoBlob
142 | }
143 | })(window)
144 |
--------------------------------------------------------------------------------
/js/vendor/promise-polyfill.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
3 | typeof define === 'function' && define.amd ? define(factory) :
4 | (factory());
5 | }(this, (function () { 'use strict';
6 |
7 | /**
8 | * @this {Promise}
9 | */
10 | function finallyConstructor(callback) {
11 | var constructor = this.constructor;
12 | return this.then(
13 | function(value) {
14 | // @ts-ignore
15 | return constructor.resolve(callback()).then(function() {
16 | return value;
17 | });
18 | },
19 | function(reason) {
20 | // @ts-ignore
21 | return constructor.resolve(callback()).then(function() {
22 | // @ts-ignore
23 | return constructor.reject(reason);
24 | });
25 | }
26 | );
27 | }
28 |
29 | function allSettled(arr) {
30 | var P = this;
31 | return new P(function(resolve, reject) {
32 | if (!(arr && typeof arr.length !== 'undefined')) {
33 | return reject(
34 | new TypeError(
35 | typeof arr +
36 | ' ' +
37 | arr +
38 | ' is not iterable(cannot read property Symbol(Symbol.iterator))'
39 | )
40 | );
41 | }
42 | var args = Array.prototype.slice.call(arr);
43 | if (args.length === 0) return resolve([]);
44 | var remaining = args.length;
45 |
46 | function res(i, val) {
47 | if (val && (typeof val === 'object' || typeof val === 'function')) {
48 | var then = val.then;
49 | if (typeof then === 'function') {
50 | then.call(
51 | val,
52 | function(val) {
53 | res(i, val);
54 | },
55 | function(e) {
56 | args[i] = { status: 'rejected', reason: e };
57 | if (--remaining === 0) {
58 | resolve(args);
59 | }
60 | }
61 | );
62 | return;
63 | }
64 | }
65 | args[i] = { status: 'fulfilled', value: val };
66 | if (--remaining === 0) {
67 | resolve(args);
68 | }
69 | }
70 |
71 | for (var i = 0; i < args.length; i++) {
72 | res(i, args[i]);
73 | }
74 | });
75 | }
76 |
77 | // Store setTimeout reference so promise-polyfill will be unaffected by
78 | // other code modifying setTimeout (like sinon.useFakeTimers())
79 | var setTimeoutFunc = setTimeout;
80 |
81 | function isArray(x) {
82 | return Boolean(x && typeof x.length !== 'undefined');
83 | }
84 |
85 | function noop() {}
86 |
87 | // Polyfill for Function.prototype.bind
88 | function bind(fn, thisArg) {
89 | return function() {
90 | fn.apply(thisArg, arguments);
91 | };
92 | }
93 |
94 | /**
95 | * @constructor
96 | * @param {Function} fn
97 | */
98 | function Promise(fn) {
99 | if (!(this instanceof Promise))
100 | throw new TypeError('Promises must be constructed via new');
101 | if (typeof fn !== 'function') throw new TypeError('not a function');
102 | /** @type {!number} */
103 | this._state = 0;
104 | /** @type {!boolean} */
105 | this._handled = false;
106 | /** @type {Promise|undefined} */
107 | this._value = undefined;
108 | /** @type {!Array} */
109 | this._deferreds = [];
110 |
111 | doResolve(fn, this);
112 | }
113 |
114 | function handle(self, deferred) {
115 | while (self._state === 3) {
116 | self = self._value;
117 | }
118 | if (self._state === 0) {
119 | self._deferreds.push(deferred);
120 | return;
121 | }
122 | self._handled = true;
123 | Promise._immediateFn(function() {
124 | var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
125 | if (cb === null) {
126 | (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
127 | return;
128 | }
129 | var ret;
130 | try {
131 | ret = cb(self._value);
132 | } catch (e) {
133 | reject(deferred.promise, e);
134 | return;
135 | }
136 | resolve(deferred.promise, ret);
137 | });
138 | }
139 |
140 | function resolve(self, newValue) {
141 | try {
142 | // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
143 | if (newValue === self)
144 | throw new TypeError('A promise cannot be resolved with itself.');
145 | if (
146 | newValue &&
147 | (typeof newValue === 'object' || typeof newValue === 'function')
148 | ) {
149 | var then = newValue.then;
150 | if (newValue instanceof Promise) {
151 | self._state = 3;
152 | self._value = newValue;
153 | finale(self);
154 | return;
155 | } else if (typeof then === 'function') {
156 | doResolve(bind(then, newValue), self);
157 | return;
158 | }
159 | }
160 | self._state = 1;
161 | self._value = newValue;
162 | finale(self);
163 | } catch (e) {
164 | reject(self, e);
165 | }
166 | }
167 |
168 | function reject(self, newValue) {
169 | self._state = 2;
170 | self._value = newValue;
171 | finale(self);
172 | }
173 |
174 | function finale(self) {
175 | if (self._state === 2 && self._deferreds.length === 0) {
176 | Promise._immediateFn(function() {
177 | if (!self._handled) {
178 | Promise._unhandledRejectionFn(self._value);
179 | }
180 | });
181 | }
182 |
183 | for (var i = 0, len = self._deferreds.length; i < len; i++) {
184 | handle(self, self._deferreds[i]);
185 | }
186 | self._deferreds = null;
187 | }
188 |
189 | /**
190 | * @constructor
191 | */
192 | function Handler(onFulfilled, onRejected, promise) {
193 | this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
194 | this.onRejected = typeof onRejected === 'function' ? onRejected : null;
195 | this.promise = promise;
196 | }
197 |
198 | /**
199 | * Take a potentially misbehaving resolver function and make sure
200 | * onFulfilled and onRejected are only called once.
201 | *
202 | * Makes no guarantees about asynchrony.
203 | */
204 | function doResolve(fn, self) {
205 | var done = false;
206 | try {
207 | fn(
208 | function(value) {
209 | if (done) return;
210 | done = true;
211 | resolve(self, value);
212 | },
213 | function(reason) {
214 | if (done) return;
215 | done = true;
216 | reject(self, reason);
217 | }
218 | );
219 | } catch (ex) {
220 | if (done) return;
221 | done = true;
222 | reject(self, ex);
223 | }
224 | }
225 |
226 | Promise.prototype['catch'] = function(onRejected) {
227 | return this.then(null, onRejected);
228 | };
229 |
230 | Promise.prototype.then = function(onFulfilled, onRejected) {
231 | // @ts-ignore
232 | var prom = new this.constructor(noop);
233 |
234 | handle(this, new Handler(onFulfilled, onRejected, prom));
235 | return prom;
236 | };
237 |
238 | Promise.prototype['finally'] = finallyConstructor;
239 |
240 | Promise.all = function(arr) {
241 | return new Promise(function(resolve, reject) {
242 | if (!isArray(arr)) {
243 | return reject(new TypeError('Promise.all accepts an array'));
244 | }
245 |
246 | var args = Array.prototype.slice.call(arr);
247 | if (args.length === 0) return resolve([]);
248 | var remaining = args.length;
249 |
250 | function res(i, val) {
251 | try {
252 | if (val && (typeof val === 'object' || typeof val === 'function')) {
253 | var then = val.then;
254 | if (typeof then === 'function') {
255 | then.call(
256 | val,
257 | function(val) {
258 | res(i, val);
259 | },
260 | reject
261 | );
262 | return;
263 | }
264 | }
265 | args[i] = val;
266 | if (--remaining === 0) {
267 | resolve(args);
268 | }
269 | } catch (ex) {
270 | reject(ex);
271 | }
272 | }
273 |
274 | for (var i = 0; i < args.length; i++) {
275 | res(i, args[i]);
276 | }
277 | });
278 | };
279 |
280 | Promise.allSettled = allSettled;
281 |
282 | Promise.resolve = function(value) {
283 | if (value && typeof value === 'object' && value.constructor === Promise) {
284 | return value;
285 | }
286 |
287 | return new Promise(function(resolve) {
288 | resolve(value);
289 | });
290 | };
291 |
292 | Promise.reject = function(value) {
293 | return new Promise(function(resolve, reject) {
294 | reject(value);
295 | });
296 | };
297 |
298 | Promise.race = function(arr) {
299 | return new Promise(function(resolve, reject) {
300 | if (!isArray(arr)) {
301 | return reject(new TypeError('Promise.race accepts an array'));
302 | }
303 |
304 | for (var i = 0, len = arr.length; i < len; i++) {
305 | Promise.resolve(arr[i]).then(resolve, reject);
306 | }
307 | });
308 | };
309 |
310 | // Use polyfill for setImmediate for performance gains
311 | Promise._immediateFn =
312 | // @ts-ignore
313 | (typeof setImmediate === 'function' &&
314 | function(fn) {
315 | // @ts-ignore
316 | setImmediate(fn);
317 | }) ||
318 | function(fn) {
319 | setTimeoutFunc(fn, 0);
320 | };
321 |
322 | Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
323 | if (typeof console !== 'undefined' && console) {
324 | console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
325 | }
326 | };
327 |
328 | /** @suppress {undefinedVars} */
329 | var globalNS = (function() {
330 | // the only reliable means to get the global object is
331 | // `Function('return this')()`
332 | // However, this causes CSP violations in Chrome apps.
333 | if (typeof self !== 'undefined') {
334 | return self;
335 | }
336 | if (typeof window !== 'undefined') {
337 | return window;
338 | }
339 | if (typeof global !== 'undefined') {
340 | return global;
341 | }
342 | throw new Error('unable to locate global object');
343 | })();
344 |
345 | // Expose the polyfill if Promise is undefined or set to a
346 | // non-function value. The latter can be due to a named HTMLElement
347 | // being exposed by browsers for legacy reasons.
348 | // https://github.com/taylorhakes/promise-polyfill/issues/114
349 | if (typeof globalNS['Promise'] !== 'function') {
350 | globalNS['Promise'] = Promise;
351 | } else if (!globalNS.Promise.prototype['finally']) {
352 | globalNS.Promise.prototype['finally'] = finallyConstructor;
353 | } else if (!globalNS.Promise.allSettled) {
354 | globalNS.Promise.allSettled = allSettled;
355 | }
356 |
357 | })));
358 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blueimp-load-image",
3 | "version": "5.16.0",
4 | "title": "JavaScript Load Image",
5 | "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 and Exif tags as well as embedded thumbnail images, to overwrite the Exif Orientation value and to restore the complete image header after resizing.",
6 | "keywords": [
7 | "javascript",
8 | "load",
9 | "loading",
10 | "image",
11 | "file",
12 | "blob",
13 | "url",
14 | "scale",
15 | "crop",
16 | "rotate",
17 | "img",
18 | "canvas",
19 | "meta",
20 | "exif",
21 | "orientation",
22 | "thumbnail",
23 | "iptc"
24 | ],
25 | "homepage": "https://github.com/blueimp/JavaScript-Load-Image",
26 | "author": {
27 | "name": "Sebastian Tschan",
28 | "url": "https://blueimp.net"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "git://github.com/blueimp/JavaScript-Load-Image.git"
33 | },
34 | "license": "MIT",
35 | "devDependencies": {
36 | "blueimp-canvas-to-blob": "3",
37 | "chai": "4",
38 | "eslint": "7",
39 | "eslint-config-blueimp": "2",
40 | "eslint-config-prettier": "8",
41 | "eslint-plugin-jsdoc": "36",
42 | "eslint-plugin-prettier": "4",
43 | "jquery": "1",
44 | "mocha": "9",
45 | "prettier": "2",
46 | "promise-polyfill": "8",
47 | "uglify-js": "3"
48 | },
49 | "eslintConfig": {
50 | "extends": [
51 | "blueimp",
52 | "plugin:jsdoc/recommended",
53 | "plugin:prettier/recommended"
54 | ],
55 | "env": {
56 | "browser": true
57 | }
58 | },
59 | "eslintIgnore": [
60 | "js/*.min.js",
61 | "js/vendor",
62 | "test/vendor"
63 | ],
64 | "prettier": {
65 | "arrowParens": "avoid",
66 | "proseWrap": "always",
67 | "semi": false,
68 | "singleQuote": true,
69 | "trailingComma": "none"
70 | },
71 | "scripts": {
72 | "lint": "eslint .",
73 | "preunit": "bin/sync-vendor-libs.sh",
74 | "unit": "docker-compose run --rm mocha",
75 | "test": "npm run lint && npm run unit",
76 | "posttest": "docker-compose down -v",
77 | "build": "cd js && uglifyjs load-image.js load-image-scale.js load-image-meta.js load-image-fetch.js load-image-orientation.js load-image-exif.js load-image-exif-map.js load-image-iptc.js load-image-iptc-map.js --ie8 -c -m -o load-image.all.min.js --source-map url=load-image.all.min.js.map",
78 | "preversion": "npm test",
79 | "version": "npm run build && git add -A js",
80 | "postversion": "git push --tags origin master master:gh-pages && npm publish"
81 | },
82 | "files": [
83 | "js/*.js",
84 | "js/*.js.map"
85 | ],
86 | "main": "js/index.js"
87 | }
88 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
19 |
20 | JavaScript Load Image Test
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/test/vendor/mocha.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | body {
4 | margin:0;
5 | }
6 |
7 | #mocha {
8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
9 | margin: 60px 50px;
10 | }
11 |
12 | #mocha ul,
13 | #mocha li {
14 | margin: 0;
15 | padding: 0;
16 | }
17 |
18 | #mocha ul {
19 | list-style: none;
20 | }
21 |
22 | #mocha h1,
23 | #mocha h2 {
24 | margin: 0;
25 | }
26 |
27 | #mocha h1 {
28 | margin-top: 15px;
29 | font-size: 1em;
30 | font-weight: 200;
31 | }
32 |
33 | #mocha h1 a {
34 | text-decoration: none;
35 | color: inherit;
36 | }
37 |
38 | #mocha h1 a:hover {
39 | text-decoration: underline;
40 | }
41 |
42 | #mocha .suite .suite h1 {
43 | margin-top: 0;
44 | font-size: .8em;
45 | }
46 |
47 | #mocha .hidden {
48 | display: none;
49 | }
50 |
51 | #mocha h2 {
52 | font-size: 12px;
53 | font-weight: normal;
54 | cursor: pointer;
55 | }
56 |
57 | #mocha .suite {
58 | margin-left: 15px;
59 | }
60 |
61 | #mocha .test {
62 | margin-left: 15px;
63 | overflow: hidden;
64 | }
65 |
66 | #mocha .test.pending:hover h2::after {
67 | content: '(pending)';
68 | font-family: arial, sans-serif;
69 | }
70 |
71 | #mocha .test.pass.medium .duration {
72 | background: #c09853;
73 | }
74 |
75 | #mocha .test.pass.slow .duration {
76 | background: #b94a48;
77 | }
78 |
79 | #mocha .test.pass::before {
80 | content: '✓';
81 | font-size: 12px;
82 | display: block;
83 | float: left;
84 | margin-right: 5px;
85 | color: #00d6b2;
86 | }
87 |
88 | #mocha .test.pass .duration {
89 | font-size: 9px;
90 | margin-left: 5px;
91 | padding: 2px 5px;
92 | color: #fff;
93 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
94 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
95 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
96 | -webkit-border-radius: 5px;
97 | -moz-border-radius: 5px;
98 | -ms-border-radius: 5px;
99 | -o-border-radius: 5px;
100 | border-radius: 5px;
101 | }
102 |
103 | #mocha .test.pass.fast .duration {
104 | display: none;
105 | }
106 |
107 | #mocha .test.pending {
108 | color: #0b97c4;
109 | }
110 |
111 | #mocha .test.pending::before {
112 | content: '◦';
113 | color: #0b97c4;
114 | }
115 |
116 | #mocha .test.fail {
117 | color: #c00;
118 | }
119 |
120 | #mocha .test.fail pre {
121 | color: black;
122 | }
123 |
124 | #mocha .test.fail::before {
125 | content: '✖';
126 | font-size: 12px;
127 | display: block;
128 | float: left;
129 | margin-right: 5px;
130 | color: #c00;
131 | }
132 |
133 | #mocha .test pre.error {
134 | color: #c00;
135 | max-height: 300px;
136 | overflow: auto;
137 | }
138 |
139 | #mocha .test .html-error {
140 | overflow: auto;
141 | color: black;
142 | display: block;
143 | float: left;
144 | clear: left;
145 | font: 12px/1.5 monaco, monospace;
146 | margin: 5px;
147 | padding: 15px;
148 | border: 1px solid #eee;
149 | max-width: 85%; /*(1)*/
150 | max-width: -webkit-calc(100% - 42px);
151 | max-width: -moz-calc(100% - 42px);
152 | max-width: calc(100% - 42px); /*(2)*/
153 | max-height: 300px;
154 | word-wrap: break-word;
155 | border-bottom-color: #ddd;
156 | -webkit-box-shadow: 0 1px 3px #eee;
157 | -moz-box-shadow: 0 1px 3px #eee;
158 | box-shadow: 0 1px 3px #eee;
159 | -webkit-border-radius: 3px;
160 | -moz-border-radius: 3px;
161 | border-radius: 3px;
162 | }
163 |
164 | #mocha .test .html-error pre.error {
165 | border: none;
166 | -webkit-border-radius: 0;
167 | -moz-border-radius: 0;
168 | border-radius: 0;
169 | -webkit-box-shadow: 0;
170 | -moz-box-shadow: 0;
171 | box-shadow: 0;
172 | padding: 0;
173 | margin: 0;
174 | margin-top: 18px;
175 | max-height: none;
176 | }
177 |
178 | /**
179 | * (1): approximate for browsers not supporting calc
180 | * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
181 | * ^^ seriously
182 | */
183 | #mocha .test pre {
184 | display: block;
185 | float: left;
186 | clear: left;
187 | font: 12px/1.5 monaco, monospace;
188 | margin: 5px;
189 | padding: 15px;
190 | border: 1px solid #eee;
191 | max-width: 85%; /*(1)*/
192 | max-width: -webkit-calc(100% - 42px);
193 | max-width: -moz-calc(100% - 42px);
194 | max-width: calc(100% - 42px); /*(2)*/
195 | word-wrap: break-word;
196 | border-bottom-color: #ddd;
197 | -webkit-box-shadow: 0 1px 3px #eee;
198 | -moz-box-shadow: 0 1px 3px #eee;
199 | box-shadow: 0 1px 3px #eee;
200 | -webkit-border-radius: 3px;
201 | -moz-border-radius: 3px;
202 | border-radius: 3px;
203 | }
204 |
205 | #mocha .test h2 {
206 | position: relative;
207 | }
208 |
209 | #mocha .test a.replay {
210 | position: absolute;
211 | top: 3px;
212 | right: 0;
213 | text-decoration: none;
214 | vertical-align: middle;
215 | display: block;
216 | width: 15px;
217 | height: 15px;
218 | line-height: 15px;
219 | text-align: center;
220 | background: #eee;
221 | font-size: 15px;
222 | -webkit-border-radius: 15px;
223 | -moz-border-radius: 15px;
224 | border-radius: 15px;
225 | -webkit-transition:opacity 200ms;
226 | -moz-transition:opacity 200ms;
227 | -o-transition:opacity 200ms;
228 | transition: opacity 200ms;
229 | opacity: 0.3;
230 | color: #888;
231 | }
232 |
233 | #mocha .test:hover a.replay {
234 | opacity: 1;
235 | }
236 |
237 | #mocha-report.pass .test.fail {
238 | display: none;
239 | }
240 |
241 | #mocha-report.fail .test.pass {
242 | display: none;
243 | }
244 |
245 | #mocha-report.pending .test.pass,
246 | #mocha-report.pending .test.fail {
247 | display: none;
248 | }
249 | #mocha-report.pending .test.pass.pending {
250 | display: block;
251 | }
252 |
253 | #mocha-error {
254 | color: #c00;
255 | font-size: 1.5em;
256 | font-weight: 100;
257 | letter-spacing: 1px;
258 | }
259 |
260 | #mocha-stats {
261 | position: fixed;
262 | top: 15px;
263 | right: 10px;
264 | font-size: 12px;
265 | margin: 0;
266 | color: #888;
267 | z-index: 1;
268 | }
269 |
270 | #mocha-stats .progress {
271 | float: right;
272 | padding-top: 0;
273 |
274 | /**
275 | * Set safe initial values, so mochas .progress does not inherit these
276 | * properties from Bootstrap .progress (which causes .progress height to
277 | * equal line height set in Bootstrap).
278 | */
279 | height: auto;
280 | -webkit-box-shadow: none;
281 | -moz-box-shadow: none;
282 | box-shadow: none;
283 | background-color: initial;
284 | }
285 |
286 | #mocha-stats em {
287 | color: black;
288 | }
289 |
290 | #mocha-stats a {
291 | text-decoration: none;
292 | color: inherit;
293 | }
294 |
295 | #mocha-stats a:hover {
296 | border-bottom: 1px solid #eee;
297 | }
298 |
299 | #mocha-stats li {
300 | display: inline-block;
301 | margin: 0 5px;
302 | list-style: none;
303 | padding-top: 11px;
304 | }
305 |
306 | #mocha-stats canvas {
307 | width: 40px;
308 | height: 40px;
309 | }
310 |
311 | #mocha code .comment { color: #ddd; }
312 | #mocha code .init { color: #2f6fad; }
313 | #mocha code .string { color: #5890ad; }
314 | #mocha code .keyword { color: #8a6343; }
315 | #mocha code .number { color: #2f6fad; }
316 |
317 | @media screen and (max-device-width: 480px) {
318 | #mocha {
319 | margin: 60px 0px;
320 | }
321 |
322 | #mocha #stats {
323 | position: absolute;
324 | }
325 | }
326 |
--------------------------------------------------------------------------------