/* * 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 } } })