482 lines
15 KiB
JavaScript
482 lines
15 KiB
JavaScript
/*
|
|
* JavaScript Load Image Orientation
|
|
* https://github.com/blueimp/JavaScript-Load-Image
|
|
*
|
|
* Copyright 2013, Sebastian Tschan
|
|
* https://blueimp.net
|
|
*
|
|
* Licensed under the MIT license:
|
|
* https://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
/*
|
|
Exif orientation values to correctly display the letter F:
|
|
|
|
1 2
|
|
██████ ██████
|
|
██ ██
|
|
████ ████
|
|
██ ██
|
|
██ ██
|
|
|
|
3 4
|
|
██ ██
|
|
██ ██
|
|
████ ████
|
|
██ ██
|
|
██████ ██████
|
|
|
|
5 6
|
|
██████████ ██
|
|
██ ██ ██ ██
|
|
██ ██████████
|
|
|
|
7 8
|
|
██ ██████████
|
|
██ ██ ██ ██
|
|
██████████ ██
|
|
|
|
*/
|
|
|
|
/* global define, module, require */
|
|
|
|
;(function (factory) {
|
|
'use strict'
|
|
if (typeof define === 'function' && define.amd) {
|
|
// Register as an anonymous AMD module:
|
|
define(['./load-image', './load-image-scale', './load-image-meta'], factory)
|
|
} else if (typeof module === 'object' && module.exports) {
|
|
factory(
|
|
require('./load-image'),
|
|
require('./load-image-scale'),
|
|
require('./load-image-meta')
|
|
)
|
|
} else {
|
|
// Browser globals:
|
|
factory(window.loadImage)
|
|
}
|
|
})(function (loadImage) {
|
|
'use strict'
|
|
|
|
var originalTransform = loadImage.transform
|
|
var originalRequiresCanvas = loadImage.requiresCanvas
|
|
var originalRequiresMetaData = loadImage.requiresMetaData
|
|
var originalTransformCoordinates = loadImage.transformCoordinates
|
|
var originalGetTransformedOptions = loadImage.getTransformedOptions
|
|
|
|
;(function ($) {
|
|
// Guard for non-browser environments (e.g. server-side rendering):
|
|
if (!$.global.document) return
|
|
// black+white 3x2 JPEG, with the following meta information set:
|
|
// - EXIF Orientation: 6 (Rotated 90° CCW)
|
|
// Image data layout (B=black, F=white):
|
|
// BFF
|
|
// BBB
|
|
var testImageURL =
|
|
'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' +
|
|
'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' +
|
|
'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' +
|
|
'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAIAAwMBEQACEQEDEQH/x' +
|
|
'ABRAAEAAAAAAAAAAAAAAAAAAAAKEAEBAQADAQEAAAAAAAAAAAAGBQQDCAkCBwEBAAAAAAA' +
|
|
'AAAAAAAAAAAAAABEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AG8T9NfSMEVMhQ' +
|
|
'voP3fFiRZ+MTHDifa/95OFSZU5OzRzxkyejv8ciEfhSceSXGjS8eSdLnZc2HDm4M3BxcXw' +
|
|
'H/9k='
|
|
var img = document.createElement('img')
|
|
img.onload = function () {
|
|
// Check if the browser supports automatic image orientation:
|
|
$.orientation = img.width === 2 && img.height === 3
|
|
if ($.orientation) {
|
|
var canvas = $.createCanvas(1, 1, true)
|
|
var ctx = canvas.getContext('2d')
|
|
ctx.drawImage(img, 1, 1, 1, 1, 0, 0, 1, 1)
|
|
// Check if the source image coordinates (sX, sY, sWidth, sHeight) are
|
|
// correctly applied to the auto-orientated image, which should result
|
|
// in a white opaque pixel (e.g. in Safari).
|
|
// Browsers that show a transparent pixel (e.g. Chromium) fail to crop
|
|
// auto-oriented images correctly and require a workaround, e.g.
|
|
// drawing the complete source image to an intermediate canvas first.
|
|
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1074354
|
|
$.orientationCropBug =
|
|
ctx.getImageData(0, 0, 1, 1).data.toString() !== '255,255,255,255'
|
|
}
|
|
}
|
|
img.src = testImageURL
|
|
})(loadImage)
|
|
|
|
/**
|
|
* Determines if the orientation requires a canvas element.
|
|
*
|
|
* @param {object} [options] Options object
|
|
* @param {boolean} [withMetaData] Is metadata required for orientation
|
|
* @returns {boolean} Returns true if orientation requires canvas/meta
|
|
*/
|
|
function requiresCanvasOrientation(options, withMetaData) {
|
|
var orientation = options && options.orientation
|
|
return (
|
|
// Exif orientation for browsers without automatic image orientation:
|
|
(orientation === true && !loadImage.orientation) ||
|
|
// Orientation reset for browsers with automatic image orientation:
|
|
(orientation === 1 && loadImage.orientation) ||
|
|
// Orientation to defined value, requires meta for orientation reset only:
|
|
((!withMetaData || loadImage.orientation) &&
|
|
orientation > 1 &&
|
|
orientation < 9)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Determines if the image requires an orientation change.
|
|
*
|
|
* @param {number} [orientation] Defined orientation value
|
|
* @param {number} [autoOrientation] Auto-orientation based on Exif data
|
|
* @returns {boolean} Returns true if an orientation change is required
|
|
*/
|
|
function requiresOrientationChange(orientation, autoOrientation) {
|
|
return (
|
|
orientation !== autoOrientation &&
|
|
((orientation === 1 && autoOrientation > 1 && autoOrientation < 9) ||
|
|
(orientation > 1 && orientation < 9))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Determines orientation combinations that require a rotation by 180°.
|
|
*
|
|
* The following is a list of combinations that return true:
|
|
*
|
|
* 2 (flip) => 5 (rot90,flip), 7 (rot90,flip), 6 (rot90), 8 (rot90)
|
|
* 4 (flip) => 5 (rot90,flip), 7 (rot90,flip), 6 (rot90), 8 (rot90)
|
|
*
|
|
* 5 (rot90,flip) => 2 (flip), 4 (flip), 6 (rot90), 8 (rot90)
|
|
* 7 (rot90,flip) => 2 (flip), 4 (flip), 6 (rot90), 8 (rot90)
|
|
*
|
|
* 6 (rot90) => 2 (flip), 4 (flip), 5 (rot90,flip), 7 (rot90,flip)
|
|
* 8 (rot90) => 2 (flip), 4 (flip), 5 (rot90,flip), 7 (rot90,flip)
|
|
*
|
|
* @param {number} [orientation] Defined orientation value
|
|
* @param {number} [autoOrientation] Auto-orientation based on Exif data
|
|
* @returns {boolean} Returns true if rotation by 180° is required
|
|
*/
|
|
function requiresRot180(orientation, autoOrientation) {
|
|
if (autoOrientation > 1 && autoOrientation < 9) {
|
|
switch (orientation) {
|
|
case 2:
|
|
case 4:
|
|
return autoOrientation > 4
|
|
case 5:
|
|
case 7:
|
|
return autoOrientation % 2 === 0
|
|
case 6:
|
|
case 8:
|
|
return (
|
|
autoOrientation === 2 ||
|
|
autoOrientation === 4 ||
|
|
autoOrientation === 5 ||
|
|
autoOrientation === 7
|
|
)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Determines if the target image should be a canvas element:
|
|
loadImage.requiresCanvas = function (options) {
|
|
return (
|
|
requiresCanvasOrientation(options) ||
|
|
originalRequiresCanvas.call(loadImage, options)
|
|
)
|
|
}
|
|
|
|
// Determines if metadata should be loaded automatically:
|
|
loadImage.requiresMetaData = function (options) {
|
|
return (
|
|
requiresCanvasOrientation(options, true) ||
|
|
originalRequiresMetaData.call(loadImage, options)
|
|
)
|
|
}
|
|
|
|
loadImage.transform = function (img, options, callback, file, data) {
|
|
originalTransform.call(
|
|
loadImage,
|
|
img,
|
|
options,
|
|
function (img, data) {
|
|
if (data) {
|
|
var autoOrientation =
|
|
loadImage.orientation && data.exif && data.exif.get('Orientation')
|
|
if (autoOrientation > 4 && autoOrientation < 9) {
|
|
// Automatic image orientation switched image dimensions
|
|
var originalWidth = data.originalWidth
|
|
var originalHeight = data.originalHeight
|
|
data.originalWidth = originalHeight
|
|
data.originalHeight = originalWidth
|
|
}
|
|
}
|
|
callback(img, data)
|
|
},
|
|
file,
|
|
data
|
|
)
|
|
}
|
|
|
|
// Transforms coordinate and dimension options
|
|
// based on the given orientation option:
|
|
loadImage.getTransformedOptions = function (img, opts, data) {
|
|
var options = originalGetTransformedOptions.call(loadImage, img, opts)
|
|
var exifOrientation = data.exif && data.exif.get('Orientation')
|
|
var orientation = options.orientation
|
|
var autoOrientation = loadImage.orientation && exifOrientation
|
|
if (orientation === true) orientation = exifOrientation
|
|
if (!requiresOrientationChange(orientation, autoOrientation)) {
|
|
return options
|
|
}
|
|
var top = options.top
|
|
var right = options.right
|
|
var bottom = options.bottom
|
|
var left = options.left
|
|
var newOptions = {}
|
|
for (var i in options) {
|
|
if (Object.prototype.hasOwnProperty.call(options, i)) {
|
|
newOptions[i] = options[i]
|
|
}
|
|
}
|
|
newOptions.orientation = orientation
|
|
if (
|
|
(orientation > 4 && !(autoOrientation > 4)) ||
|
|
(orientation < 5 && autoOrientation > 4)
|
|
) {
|
|
// Image dimensions and target dimensions are switched
|
|
newOptions.maxWidth = options.maxHeight
|
|
newOptions.maxHeight = options.maxWidth
|
|
newOptions.minWidth = options.minHeight
|
|
newOptions.minHeight = options.minWidth
|
|
newOptions.sourceWidth = options.sourceHeight
|
|
newOptions.sourceHeight = options.sourceWidth
|
|
}
|
|
if (autoOrientation > 1) {
|
|
// Browsers which correctly apply source image coordinates to
|
|
// auto-oriented images
|
|
switch (autoOrientation) {
|
|
case 2:
|
|
// Horizontal flip
|
|
right = options.left
|
|
left = options.right
|
|
break
|
|
case 3:
|
|
// 180° Rotate CCW
|
|
top = options.bottom
|
|
right = options.left
|
|
bottom = options.top
|
|
left = options.right
|
|
break
|
|
case 4:
|
|
// Vertical flip
|
|
top = options.bottom
|
|
bottom = options.top
|
|
break
|
|
case 5:
|
|
// Horizontal flip + 90° Rotate CCW
|
|
top = options.left
|
|
right = options.bottom
|
|
bottom = options.right
|
|
left = options.top
|
|
break
|
|
case 6:
|
|
// 90° Rotate CCW
|
|
top = options.left
|
|
right = options.top
|
|
bottom = options.right
|
|
left = options.bottom
|
|
break
|
|
case 7:
|
|
// Vertical flip + 90° Rotate CCW
|
|
top = options.right
|
|
right = options.top
|
|
bottom = options.left
|
|
left = options.bottom
|
|
break
|
|
case 8:
|
|
// 90° Rotate CW
|
|
top = options.right
|
|
right = options.bottom
|
|
bottom = options.left
|
|
left = options.top
|
|
break
|
|
}
|
|
// Some orientation combinations require additional rotation by 180°:
|
|
if (requiresRot180(orientation, autoOrientation)) {
|
|
var tmpTop = top
|
|
var tmpRight = right
|
|
top = bottom
|
|
right = left
|
|
bottom = tmpTop
|
|
left = tmpRight
|
|
}
|
|
}
|
|
newOptions.top = top
|
|
newOptions.right = right
|
|
newOptions.bottom = bottom
|
|
newOptions.left = left
|
|
// Account for defined browser orientation:
|
|
switch (orientation) {
|
|
case 2:
|
|
// Horizontal flip
|
|
newOptions.right = left
|
|
newOptions.left = right
|
|
break
|
|
case 3:
|
|
// 180° Rotate CCW
|
|
newOptions.top = bottom
|
|
newOptions.right = left
|
|
newOptions.bottom = top
|
|
newOptions.left = right
|
|
break
|
|
case 4:
|
|
// Vertical flip
|
|
newOptions.top = bottom
|
|
newOptions.bottom = top
|
|
break
|
|
case 5:
|
|
// Vertical flip + 90° Rotate CW
|
|
newOptions.top = left
|
|
newOptions.right = bottom
|
|
newOptions.bottom = right
|
|
newOptions.left = top
|
|
break
|
|
case 6:
|
|
// 90° Rotate CW
|
|
newOptions.top = right
|
|
newOptions.right = bottom
|
|
newOptions.bottom = left
|
|
newOptions.left = top
|
|
break
|
|
case 7:
|
|
// Horizontal flip + 90° Rotate CW
|
|
newOptions.top = right
|
|
newOptions.right = top
|
|
newOptions.bottom = left
|
|
newOptions.left = bottom
|
|
break
|
|
case 8:
|
|
// 90° Rotate CCW
|
|
newOptions.top = left
|
|
newOptions.right = top
|
|
newOptions.bottom = right
|
|
newOptions.left = bottom
|
|
break
|
|
}
|
|
return newOptions
|
|
}
|
|
|
|
// Transform image orientation based on the given EXIF orientation option:
|
|
loadImage.transformCoordinates = function (canvas, options, data) {
|
|
originalTransformCoordinates.call(loadImage, canvas, options, data)
|
|
var orientation = options.orientation
|
|
var autoOrientation =
|
|
loadImage.orientation && data.exif && data.exif.get('Orientation')
|
|
if (!requiresOrientationChange(orientation, autoOrientation)) {
|
|
return
|
|
}
|
|
var ctx = canvas.getContext('2d')
|
|
var width = canvas.width
|
|
var height = canvas.height
|
|
var sourceWidth = width
|
|
var sourceHeight = height
|
|
if (
|
|
(orientation > 4 && !(autoOrientation > 4)) ||
|
|
(orientation < 5 && autoOrientation > 4)
|
|
) {
|
|
// Image dimensions and target dimensions are switched
|
|
canvas.width = height
|
|
canvas.height = width
|
|
}
|
|
if (orientation > 4) {
|
|
// Destination and source dimensions are switched
|
|
sourceWidth = height
|
|
sourceHeight = width
|
|
}
|
|
// Reset automatic browser orientation:
|
|
switch (autoOrientation) {
|
|
case 2:
|
|
// Horizontal flip
|
|
ctx.translate(sourceWidth, 0)
|
|
ctx.scale(-1, 1)
|
|
break
|
|
case 3:
|
|
// 180° Rotate CCW
|
|
ctx.translate(sourceWidth, sourceHeight)
|
|
ctx.rotate(Math.PI)
|
|
break
|
|
case 4:
|
|
// Vertical flip
|
|
ctx.translate(0, sourceHeight)
|
|
ctx.scale(1, -1)
|
|
break
|
|
case 5:
|
|
// Horizontal flip + 90° Rotate CCW
|
|
ctx.rotate(-0.5 * Math.PI)
|
|
ctx.scale(-1, 1)
|
|
break
|
|
case 6:
|
|
// 90° Rotate CCW
|
|
ctx.rotate(-0.5 * Math.PI)
|
|
ctx.translate(-sourceWidth, 0)
|
|
break
|
|
case 7:
|
|
// Vertical flip + 90° Rotate CCW
|
|
ctx.rotate(-0.5 * Math.PI)
|
|
ctx.translate(-sourceWidth, sourceHeight)
|
|
ctx.scale(1, -1)
|
|
break
|
|
case 8:
|
|
// 90° Rotate CW
|
|
ctx.rotate(0.5 * Math.PI)
|
|
ctx.translate(0, -sourceHeight)
|
|
break
|
|
}
|
|
// Some orientation combinations require additional rotation by 180°:
|
|
if (requiresRot180(orientation, autoOrientation)) {
|
|
ctx.translate(sourceWidth, sourceHeight)
|
|
ctx.rotate(Math.PI)
|
|
}
|
|
switch (orientation) {
|
|
case 2:
|
|
// Horizontal flip
|
|
ctx.translate(width, 0)
|
|
ctx.scale(-1, 1)
|
|
break
|
|
case 3:
|
|
// 180° Rotate CCW
|
|
ctx.translate(width, height)
|
|
ctx.rotate(Math.PI)
|
|
break
|
|
case 4:
|
|
// Vertical flip
|
|
ctx.translate(0, height)
|
|
ctx.scale(1, -1)
|
|
break
|
|
case 5:
|
|
// Vertical flip + 90° Rotate CW
|
|
ctx.rotate(0.5 * Math.PI)
|
|
ctx.scale(1, -1)
|
|
break
|
|
case 6:
|
|
// 90° Rotate CW
|
|
ctx.rotate(0.5 * Math.PI)
|
|
ctx.translate(0, -height)
|
|
break
|
|
case 7:
|
|
// Horizontal flip + 90° Rotate CW
|
|
ctx.rotate(0.5 * Math.PI)
|
|
ctx.translate(width, -height)
|
|
ctx.scale(-1, 1)
|
|
break
|
|
case 8:
|
|
// 90° Rotate CCW
|
|
ctx.rotate(-0.5 * Math.PI)
|
|
ctx.translate(-width, 0)
|
|
break
|
|
}
|
|
}
|
|
})
|