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

  1. /**
  2. * Copyright 2013 Matthieu Moquet
  3. * Copyright 2016-2017 LasLabs Inc.
  4. * License MIT (https://opensource.org/licenses/MIT)
  5. **/
  6. (function() {
  7. 'use strict';
  8. window.Darkroom = Darkroom;
  9. // Core object of DarkroomJS.
  10. // Basically it's a single object, instanciable via an element
  11. // (it could be a CSS selector or a DOM element), some custom options,
  12. // and a list of plugin objects (or none to use default ones).
  13. function Darkroom(element, options, plugins) {
  14. return this.constructor(element, options, plugins);
  15. }
  16. // Create an empty list of plugin objects, which will be filled by
  17. // other plugin scripts. This is the default plugin list if none is
  18. // specified in Darkroom's constructor.
  19. Darkroom.plugins = [];
  20. Darkroom.prototype = {
  21. // Reference to the main container element
  22. containerElement: null,
  23. // Reference to the Fabric canvas object
  24. canvas: null,
  25. // Reference to the Fabric image object
  26. image: null,
  27. // Reference to the Fabric source canvas object
  28. sourceCanvas: null,
  29. // Reference to the Fabric source image object
  30. sourceImage: null,
  31. // Track of the original image element
  32. originalImageElement: null,
  33. // Stack of transformations to apply to the image source
  34. transformations: [],
  35. // Default options
  36. defaults: {
  37. // Canvas properties (dimension, ratio, color)
  38. minWidth: null,
  39. minHeight: null,
  40. maxWidth: null,
  41. maxHeight: null,
  42. ratio: null,
  43. backgroundColor: '#fff',
  44. // Plugins options
  45. plugins: {},
  46. // Post-initialisation callback
  47. initialize: function() { /* noop */ }
  48. },
  49. // List of the instancied plugins
  50. plugins: {},
  51. // This options are a merge between `defaults` and the options passed
  52. // through the constructor
  53. options: {},
  54. constructor: function(element, options) {
  55. this.options = Darkroom.Utils.extend(options, this.defaults);
  56. if (typeof element === 'string')
  57. element = document.querySelector(element);
  58. if (null === element)
  59. return;
  60. var image = new Image();
  61. var parent = element.parentElement;
  62. image.onload = function() {
  63. // Initialize the DOM/Fabric elements
  64. this._initializeDOM(element, parent);
  65. this._initializeImage();
  66. // Then initialize the plugins
  67. this._initializePlugins(Darkroom.plugins);
  68. // Public method to adjust image according to the canvas
  69. this.refresh(function() {
  70. // Execute a custom callback after initialization
  71. this.options.initialize.bind(this).call();
  72. }.bind(this));
  73. }.bind(this);
  74. image.src = element.src;
  75. },
  76. selfDestroy: function() {
  77. var container = this.containerElement;
  78. var image = new Image();
  79. image.onload = function() {
  80. container.parentNode.replaceChild(image, container);
  81. };
  82. image.src = this.sourceImage.toDataURL();
  83. },
  84. // Add ability to attach event listener on the core object.
  85. // It uses the canvas element to process events.
  86. addEventListener: function(eventName, callback) {
  87. var el = this.canvas.getElement();
  88. if (el.addEventListener) {
  89. el.addEventListener(eventName, callback);
  90. } else if (el.attachEvent) {
  91. el.attachEvent('on' + eventName, callback);
  92. }
  93. },
  94. dispatchEvent: function(eventName) {
  95. // Use the old way of creating event to be IE compatible
  96. // See https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
  97. var event = document.createEvent('Event');
  98. event.initEvent(eventName, true, true);
  99. this.canvas.getElement().dispatchEvent(event);
  100. },
  101. // Adjust image & canvas dimension according to min/max width/height
  102. // and ratio specified in the options.
  103. // This method should be called after each image transformation.
  104. refresh: function(next) {
  105. var clone = new Image();
  106. clone.onload = function() {
  107. this._replaceCurrentImage(new fabric.Image(clone));
  108. if (next) next();
  109. }.bind(this);
  110. clone.src = this.sourceImage.toDataURL();
  111. },
  112. _replaceCurrentImage: function(newImage) {
  113. if (this.image) {
  114. this.image.remove();
  115. }
  116. this.image = newImage;
  117. this.image.selectable = false;
  118. // Adjust width or height according to specified ratio
  119. var viewport = Darkroom.Utils.computeImageViewPort(this.image);
  120. var canvasWidth = viewport.width;
  121. var canvasHeight = viewport.height;
  122. if (null !== this.options.ratio) {
  123. var canvasRatio = +this.options.ratio;
  124. var currentRatio = canvasWidth / canvasHeight;
  125. if (currentRatio > canvasRatio) {
  126. canvasHeight = canvasWidth / canvasRatio;
  127. } else if (currentRatio < canvasRatio) {
  128. canvasWidth = canvasHeight * canvasRatio;
  129. }
  130. }
  131. // Then scale the image to fit into dimension limits
  132. var scaleMin = 1;
  133. var scaleMax = 1;
  134. var scaleX = 1;
  135. var scaleY = 1;
  136. if (null !== this.options.maxWidth && this.options.maxWidth < canvasWidth) {
  137. scaleX = this.options.maxWidth / canvasWidth;
  138. }
  139. if (null !== this.options.maxHeight && this.options.maxHeight < canvasHeight) {
  140. scaleY = this.options.maxHeight / canvasHeight;
  141. }
  142. scaleMin = Math.min(scaleX, scaleY);
  143. scaleX = 1;
  144. scaleY = 1;
  145. if (null !== this.options.minWidth && this.options.minWidth > canvasWidth) {
  146. scaleX = this.options.minWidth / canvasWidth;
  147. }
  148. if (null !== this.options.minHeight && this.options.minHeight > canvasHeight) {
  149. scaleY = this.options.minHeight / canvasHeight;
  150. }
  151. scaleMax = Math.max(scaleX, scaleY);
  152. var scale = scaleMax * scaleMin; // one should be equals to 1
  153. canvasWidth *= scale;
  154. canvasHeight *= scale;
  155. // Finally place the image in the center of the canvas
  156. this.image.setScaleX(1 * scale);
  157. this.image.setScaleY(1 * scale);
  158. this.canvas.add(this.image);
  159. this.canvas.setWidth(canvasWidth);
  160. this.canvas.setHeight(canvasHeight);
  161. this.canvas.centerObject(this.image);
  162. this.image.setCoords();
  163. },
  164. // Apply the transformation on the current image and save it in the
  165. // transformations stack (in order to reconstitute the previous states
  166. // of the image).
  167. applyTransformation: function(transformation) {
  168. this.transformations.push(transformation);
  169. transformation.applyTransformation(
  170. this.sourceCanvas,
  171. this.sourceImage,
  172. this._postTransformation.bind(this)
  173. );
  174. },
  175. _postTransformation: function(newImage) {
  176. if (newImage)
  177. this.sourceImage = newImage;
  178. this.refresh(function() {
  179. this.dispatchEvent('core:transformation');
  180. }.bind(this));
  181. },
  182. // Initialize image from original element plus re-apply every
  183. // transformations.
  184. reinitializeImage: function() {
  185. this.sourceImage.remove();
  186. this._initializeImage();
  187. this._popTransformation(this.transformations.slice());
  188. },
  189. _popTransformation: function(transformations) {
  190. if (0 === transformations.length) {
  191. this.dispatchEvent('core:reinitialized');
  192. this.refresh();
  193. return;
  194. }
  195. var transformation = transformations.shift();
  196. var next = function(newImage) {
  197. if (newImage) this.sourceImage = newImage;
  198. this._popTransformation(transformations);
  199. };
  200. transformation.applyTransformation(
  201. this.sourceCanvas,
  202. this.sourceImage,
  203. next.bind(this)
  204. );
  205. },
  206. // Create the DOM elements and instanciate the Fabric canvas.
  207. // The image element is replaced by a new `div` element.
  208. // However the original image is re-injected in order to keep a trace of it.
  209. _initializeDOM: function(imageElement) {
  210. // Container
  211. var mainContainerElement = document.createElement('div');
  212. mainContainerElement.className = 'darkroom-container';
  213. // Toolbar
  214. var toolbarElement = document.createElement('div');
  215. toolbarElement.className = 'darkroom-toolbar';
  216. mainContainerElement.appendChild(toolbarElement);
  217. // Viewport canvas
  218. var canvasContainerElement = document.createElement('div');
  219. canvasContainerElement.className = 'darkroom-image-container';
  220. var canvasElement = document.createElement('canvas');
  221. canvasContainerElement.appendChild(canvasElement);
  222. mainContainerElement.appendChild(canvasContainerElement);
  223. // Source canvas
  224. var sourceCanvasContainerElement = document.createElement('div');
  225. sourceCanvasContainerElement.className = 'darkroom-source-container';
  226. sourceCanvasContainerElement.style.display = 'none';
  227. var sourceCanvasElement = document.createElement('canvas');
  228. sourceCanvasContainerElement.appendChild(sourceCanvasElement);
  229. mainContainerElement.appendChild(sourceCanvasContainerElement);
  230. // Original image
  231. imageElement.parentNode.replaceChild(mainContainerElement, imageElement);
  232. imageElement.style.display = 'none';
  233. mainContainerElement.appendChild(imageElement);
  234. // Instanciate object from elements
  235. this.containerElement = mainContainerElement;
  236. this.originalImageElement = imageElement;
  237. this.toolbar = new Darkroom.UI.Toolbar(toolbarElement);
  238. this.canvas = new fabric.Canvas(canvasElement, {
  239. selection: false,
  240. backgroundColor: this.options.backgroundColor,
  241. });
  242. this.sourceCanvas = new fabric.Canvas(sourceCanvasElement, {
  243. selection: false,
  244. backgroundColor: this.options.backgroundColor,
  245. });
  246. },
  247. // Instanciate the Fabric image object.
  248. // The image is created as a static element with no control,
  249. // then it is add in the Fabric canvas object.
  250. _initializeImage: function() {
  251. this.sourceImage = new fabric.Image(this.originalImageElement, {
  252. // Some options to make the image static
  253. selectable: false,
  254. evented: false,
  255. lockMovementX: true,
  256. lockMovementY: true,
  257. lockRotation: true,
  258. lockScalingX: true,
  259. lockScalingY: true,
  260. lockUniScaling: true,
  261. hasControls: false,
  262. hasBorders: false,
  263. });
  264. this.sourceCanvas.add(this.sourceImage);
  265. // Adjust width or height according to specified ratio
  266. var viewport = Darkroom.Utils.computeImageViewPort(this.sourceImage);
  267. var canvasWidth = viewport.width;
  268. var canvasHeight = viewport.height;
  269. this.sourceCanvas.setWidth(canvasWidth);
  270. this.sourceCanvas.setHeight(canvasHeight);
  271. this.sourceCanvas.centerObject(this.sourceImage);
  272. this.sourceImage.setCoords();
  273. },
  274. // Initialize every plugins.
  275. // Note that plugins are instanciated in the same order than they
  276. // are declared in the parameter object.
  277. _initializePlugins: function(plugins) {
  278. for (var name in plugins) {
  279. var plugin = plugins[name];
  280. var options = this.options.plugins[name];
  281. // Setting false into the plugin options will disable the plugin
  282. if (options === false)
  283. continue;
  284. // Avoid any issues with _proto_
  285. if (!plugins.hasOwnProperty(name))
  286. continue;
  287. this.plugins[name] = new plugin(this, options);
  288. }
  289. },
  290. };
  291. })();