UNPKG

15.3 kBJavaScriptView Raw
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/*
13Exif 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})