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