You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

356 lines
13 KiB

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