I have an image cropper using Imgly HTML5 Canvas plugin. I need to be able to setup a history stack for the cropper to be able to undo a crop operation. Currently, I can clear the canvas on button click, but I need to be able to retain the original image, and just move back through a history of changes of the image in the canvas, in the event that a cropping step is done incorrectly.
I have the following which simply clears the canvas:
$("#renderButton").click(function() {
var elem = $(".imgly-canvas");
var canvas = elem.get(0);
var context = canvas.getContext("2d");
$('#file').val('');
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
});
The plugin creates the canvas element on image load with:
Utils.getImageDataForImage = function(image) {
var canvas, context;
canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
context = canvas.getContext("2d");
context.drawImage(image, 0, 0);
return context.getImageData(0, 0, image.width, image.height);
};
And this is used on resize:
Utils.cloneImageData = function(imageData) {
var i, newImageData, _i, _ref;
newImageData = this.sharedContext.createImageData(imageData.width, imageData.height);
for (i = _i = 0, _ref = imageData.data.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
newImageData.data[i] = imageData.data[i];
}
return newImageData;
};
/*
@param {Object} dimensions
@param {Integer} dimensions.width
@param {Integer} dimensions.height
@returns {HTMLCanvasElement}
*/
Utils.newCanvasWithDimensions = function(dimensions) {
var canvas;
canvas = document.createElement("canvas");
canvas.width = dimensions.width;
canvas.height = dimensions.height;
return canvas;
};
/*
@param {imageData} imageData
@returns {HTMLCanvasElement}
*/
Utils.newCanvasFromImageData = function(imageData) {
var canvas, context;
canvas = document.createElement("canvas");
canvas.width = imageData.width;
canvas.height = imageData.height;
context = canvas.getContext("2d");
context.putImageData(imageData, 0, 0);
return canvas;
};
So I'm not sure how to build a call stack to reference each change and move back through a history of modifications to an image in the canvas.
I have an image cropper using Imgly HTML5 Canvas plugin. I need to be able to setup a history stack for the cropper to be able to undo a crop operation. Currently, I can clear the canvas on button click, but I need to be able to retain the original image, and just move back through a history of changes of the image in the canvas, in the event that a cropping step is done incorrectly.
I have the following which simply clears the canvas:
$("#renderButton").click(function() {
var elem = $(".imgly-canvas");
var canvas = elem.get(0);
var context = canvas.getContext("2d");
$('#file').val('');
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
});
The plugin creates the canvas element on image load with:
Utils.getImageDataForImage = function(image) {
var canvas, context;
canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
context = canvas.getContext("2d");
context.drawImage(image, 0, 0);
return context.getImageData(0, 0, image.width, image.height);
};
And this is used on resize:
Utils.cloneImageData = function(imageData) {
var i, newImageData, _i, _ref;
newImageData = this.sharedContext.createImageData(imageData.width, imageData.height);
for (i = _i = 0, _ref = imageData.data.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
newImageData.data[i] = imageData.data[i];
}
return newImageData;
};
/*
@param {Object} dimensions
@param {Integer} dimensions.width
@param {Integer} dimensions.height
@returns {HTMLCanvasElement}
*/
Utils.newCanvasWithDimensions = function(dimensions) {
var canvas;
canvas = document.createElement("canvas");
canvas.width = dimensions.width;
canvas.height = dimensions.height;
return canvas;
};
/*
@param {imageData} imageData
@returns {HTMLCanvasElement}
*/
Utils.newCanvasFromImageData = function(imageData) {
var canvas, context;
canvas = document.createElement("canvas");
canvas.width = imageData.width;
canvas.height = imageData.height;
context = canvas.getContext("2d");
context.putImageData(imageData, 0, 0);
return canvas;
};
So I'm not sure how to build a call stack to reference each change and move back through a history of modifications to an image in the canvas.
Share Improve this question asked Jul 8, 2015 at 15:38 MattMatt 1,2675 gold badges26 silver badges54 bronze badges2 Answers
Reset to default 8The HTML5 canvas nicely converts to JSON which can then be used to reload the canvas. You can store this in a global object.
var myObj = window.myObj || {};
myObj = {
history: [],
canvas: null
};
Get the canvas data:
myObj.canvas = document.getElementById('canvas-id');
var ctx = myObj.canvas.getContext('2d');
var data = JSON.stringify(ctx.getImageData(0, 0, myObj.canvas.width, myObj.canvas.height));
myObj.history.push(data);
Reload data:
var reloadData = JSON.parse(myObj.history[someIndex]);
var ctx = myObj.canvas.getContext('2d');
ctx.putImageData(reloadData, 0, 0);
Once you can store/load data the tricky part is managing the myObj.history
array.
You should look at the mand pattern. Basically, you need to write a function for every action that the user can do. When they click a button or load an image, don't call the function right away. Instead, create a mand object with all the information needed to execute the mand plus the information needed to undo it.
Commands are applied to a data model (the image and the crop marks). A mand "load image" needs to record the new and the previous image URL so you can load the correct image when you move through the history.
For crop mands, you need to store the old and new crop rectangles - if you keep a copy of the original image around. When the mand is executed, you apply the new crop rectangle on the original and draw that on the canvas.
For undo, you use the original image and the previous crop rectangle.
So the trick is to define a data model which contains all the information how the UI looks like (which is often hard to get from the UI directly - you can't get crop information after rendering the cropped image to the canvas). The mands then manipulate this state (so the next mand can save it for undo) and update the UI.