blueimp-load-image
Version:
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 a
482 lines (463 loc) • 15.3 kB
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
}
}
})