/** * Copyright 2013 Matthieu Moquet * Copyright 2016-2017 LasLabs Inc. * License MIT (https://opensource.org/licenses/MIT) **/ (function() { 'use strict'; window.Darkroom = Darkroom; // Core object of DarkroomJS. // Basically it's a single object, instanciable via an element // (it could be a CSS selector or a DOM element), some custom options, // and a list of plugin objects (or none to use default ones). function Darkroom(element, options, plugins) { return this.constructor(element, options, plugins); } // Create an empty list of plugin objects, which will be filled by // other plugin scripts. This is the default plugin list if none is // specified in Darkroom's constructor. Darkroom.plugins = []; Darkroom.prototype = { // Reference to the main container element containerElement: null, // Reference to the Fabric canvas object canvas: null, // Reference to the Fabric image object image: null, // Reference to the Fabric source canvas object sourceCanvas: null, // Reference to the Fabric source image object sourceImage: null, // Track of the original image element originalImageElement: null, // Stack of transformations to apply to the image source transformations: [], // Default options defaults: { // Canvas properties (dimension, ratio, color) minWidth: null, minHeight: null, maxWidth: null, maxHeight: null, ratio: null, backgroundColor: '#fff', // Plugins options plugins: {}, // Post-initialisation callback initialize: function() { /* noop */ } }, // List of the instancied plugins plugins: {}, // This options are a merge between `defaults` and the options passed // through the constructor options: {}, constructor: function(element, options) { this.options = Darkroom.Utils.extend(options, this.defaults); if (typeof element === 'string') element = document.querySelector(element); if (null === element) return; var image = new Image(); var parent = element.parentElement; image.onload = function() { // Initialize the DOM/Fabric elements this._initializeDOM(element, parent); this._initializeImage(); // Then initialize the plugins this._initializePlugins(Darkroom.plugins); // Public method to adjust image according to the canvas this.refresh(function() { // Execute a custom callback after initialization this.options.initialize.bind(this).call(); }.bind(this)); }.bind(this); image.src = element.src; }, selfDestroy: function() { var container = this.containerElement; var image = new Image(); image.onload = function() { container.parentNode.replaceChild(image, container); }; image.src = this.sourceImage.toDataURL(); }, // Add ability to attach event listener on the core object. // It uses the canvas element to process events. addEventListener: function(eventName, callback) { var el = this.canvas.getElement(); if (el.addEventListener) { el.addEventListener(eventName, callback); } else if (el.attachEvent) { el.attachEvent('on' + eventName, callback); } }, dispatchEvent: function(eventName) { // Use the old way of creating event to be IE compatible // See https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events var event = document.createEvent('Event'); event.initEvent(eventName, true, true); this.canvas.getElement().dispatchEvent(event); }, // Adjust image & canvas dimension according to min/max width/height // and ratio specified in the options. // This method should be called after each image transformation. refresh: function(next) { var clone = new Image(); clone.onload = function() { this._replaceCurrentImage(new fabric.Image(clone)); if (next) next(); }.bind(this); clone.src = this.sourceImage.toDataURL(); }, _replaceCurrentImage: function(newImage) { if (this.image) { this.image.remove(); } this.image = newImage; this.image.selectable = false; // Adjust width or height according to specified ratio var viewport = Darkroom.Utils.computeImageViewPort(this.image); var canvasWidth = viewport.width; var canvasHeight = viewport.height; if (null !== this.options.ratio) { var canvasRatio = +this.options.ratio; var currentRatio = canvasWidth / canvasHeight; if (currentRatio > canvasRatio) { canvasHeight = canvasWidth / canvasRatio; } else if (currentRatio < canvasRatio) { canvasWidth = canvasHeight * canvasRatio; } } // Then scale the image to fit into dimension limits var scaleMin = 1; var scaleMax = 1; var scaleX = 1; var scaleY = 1; if (null !== this.options.maxWidth && this.options.maxWidth < canvasWidth) { scaleX = this.options.maxWidth / canvasWidth; } if (null !== this.options.maxHeight && this.options.maxHeight < canvasHeight) { scaleY = this.options.maxHeight / canvasHeight; } scaleMin = Math.min(scaleX, scaleY); scaleX = 1; scaleY = 1; if (null !== this.options.minWidth && this.options.minWidth > canvasWidth) { scaleX = this.options.minWidth / canvasWidth; } if (null !== this.options.minHeight && this.options.minHeight > canvasHeight) { scaleY = this.options.minHeight / canvasHeight; } scaleMax = Math.max(scaleX, scaleY); var scale = scaleMax * scaleMin; // one should be equals to 1 canvasWidth *= scale; canvasHeight *= scale; // Finally place the image in the center of the canvas this.image.setScaleX(1 * scale); this.image.setScaleY(1 * scale); this.canvas.add(this.image); this.canvas.setWidth(canvasWidth); this.canvas.setHeight(canvasHeight); this.canvas.centerObject(this.image); this.image.setCoords(); }, // Apply the transformation on the current image and save it in the // transformations stack (in order to reconstitute the previous states // of the image). applyTransformation: function(transformation) { this.transformations.push(transformation); transformation.applyTransformation( this.sourceCanvas, this.sourceImage, this._postTransformation.bind(this) ); }, _postTransformation: function(newImage) { if (newImage) this.sourceImage = newImage; this.refresh(function() { this.dispatchEvent('core:transformation'); }.bind(this)); }, // Initialize image from original element plus re-apply every // transformations. reinitializeImage: function() { this.sourceImage.remove(); this._initializeImage(); this._popTransformation(this.transformations.slice()); }, _popTransformation: function(transformations) { if (0 === transformations.length) { this.dispatchEvent('core:reinitialized'); this.refresh(); return; } var transformation = transformations.shift(); var next = function(newImage) { if (newImage) this.sourceImage = newImage; this._popTransformation(transformations); }; transformation.applyTransformation( this.sourceCanvas, this.sourceImage, next.bind(this) ); }, // Create the DOM elements and instanciate the Fabric canvas. // The image element is replaced by a new `div` element. // However the original image is re-injected in order to keep a trace of it. _initializeDOM: function(imageElement) { // Container var mainContainerElement = document.createElement('div'); mainContainerElement.className = 'darkroom-container'; // Toolbar var toolbarElement = document.createElement('div'); toolbarElement.className = 'darkroom-toolbar'; mainContainerElement.appendChild(toolbarElement); // Viewport canvas var canvasContainerElement = document.createElement('div'); canvasContainerElement.className = 'darkroom-image-container'; var canvasElement = document.createElement('canvas'); canvasContainerElement.appendChild(canvasElement); mainContainerElement.appendChild(canvasContainerElement); // Source canvas var sourceCanvasContainerElement = document.createElement('div'); sourceCanvasContainerElement.className = 'darkroom-source-container'; sourceCanvasContainerElement.style.display = 'none'; var sourceCanvasElement = document.createElement('canvas'); sourceCanvasContainerElement.appendChild(sourceCanvasElement); mainContainerElement.appendChild(sourceCanvasContainerElement); // Original image imageElement.parentNode.replaceChild(mainContainerElement, imageElement); imageElement.style.display = 'none'; mainContainerElement.appendChild(imageElement); // Instanciate object from elements this.containerElement = mainContainerElement; this.originalImageElement = imageElement; this.toolbar = new Darkroom.UI.Toolbar(toolbarElement); this.canvas = new fabric.Canvas(canvasElement, { selection: false, backgroundColor: this.options.backgroundColor, }); this.sourceCanvas = new fabric.Canvas(sourceCanvasElement, { selection: false, backgroundColor: this.options.backgroundColor, }); }, // Instanciate the Fabric image object. // The image is created as a static element with no control, // then it is add in the Fabric canvas object. _initializeImage: function() { this.sourceImage = new fabric.Image(this.originalImageElement, { // Some options to make the image static selectable: false, evented: false, lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingX: true, lockScalingY: true, lockUniScaling: true, hasControls: false, hasBorders: false, }); this.sourceCanvas.add(this.sourceImage); // Adjust width or height according to specified ratio var viewport = Darkroom.Utils.computeImageViewPort(this.sourceImage); var canvasWidth = viewport.width; var canvasHeight = viewport.height; this.sourceCanvas.setWidth(canvasWidth); this.sourceCanvas.setHeight(canvasHeight); this.sourceCanvas.centerObject(this.sourceImage); this.sourceImage.setCoords(); }, // Initialize every plugins. // Note that plugins are instanciated in the same order than they // are declared in the parameter object. _initializePlugins: function(plugins) { for (var name in plugins) { var plugin = plugins[name]; var options = this.options.plugins[name]; // Setting false into the plugin options will disable the plugin if (options === false) continue; // Avoid any issues with _proto_ if (!plugins.hasOwnProperty(name)) continue; this.plugins[name] = new plugin(this, options); } }, }; })();