UNPKG

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