How to step back in HTML5 Canvas history - javascript

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.

The 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 command 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 command object with all the information needed to execute the command plus the information needed to undo it.
Commands are applied to a data model (the image and the crop marks). A command "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 commands, you need to store the old and new crop rectangles - if you keep a copy of the original image around. When the command 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 commands then manipulate this state (so the next command can save it for undo) and update the UI.

Related

Trying to determine whether canvas is blank or not with javascript

I'm using the solution from: How to check if a canvas is blank? but it's not working. My blank canvas URL does not match the newly created canvas URL and I cannot figure out why. They are the same dimensions and everything but their .toDataURL return different things so there is no way for me to tell if it has been drawn in. Below is an example of what happens when I do .toDataURL on my blank canvas and .toDataURL on the newly created canvas, as you can see they differ as you continue along.
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA0sAAACUCAYAAABCxLIRAAAKfUlEQVR4Xu3ZIRKAQAwEQfj/ow9NgRi/jY5J58wU9znnXD4CBAgQIECAAAECBAgQeAncYsmLIECAAAECBAgQIECAwFdALHkVBAgQIECAAAECBAgQ+BEQS54FAQIECBAgQIAAAQIExJI3QIAAAQIECBAgQIAAgSbgz1JzMkWAAAECBAgQIECAwJiAWBo7uHUJECBAgAABAgQIEGgCYqk5mSJAgAABAgQIECBAYExALI0d3LoECBAgQIAAAQIECDQBsdScTBEgQIAAAQIECBAgMCYglsYObl0CBAgQIECAAAECBJqAWGpOpggQIECAAAECBAgQGBMQS2MHty4BAgQIECBAgAABAk1ALDUnUwQIECBAgAABAgQIjAmIpbGDW5cAAQIECBAgQIAAgSYglpqTKQIECBAgQIAAAQIExgTE0tjBrUuAAAECBAgQIECAQBMQS83JFAECBAgQIECAAAECYwJiaezg1iVAgAABAgQIECBAoAmIpeZkigABAgQIECBAgACBMQGxNHZw6xIgQIAAAQIECBAg0ATEUnMyRYAAAQIECBAgQIDAmIBYGju4dQkQIECAAAECBAgQaAJiqTmZIkCAAAECBAgQIEBgTEAsjR3cugQIECBAgAABAgQINAGx1JxMESBAgAABAgQIECAwJiCWxg5uXQIECBAgQIAAAQIEmoBYak6mCBAgQIAAAQIECBAYExBLYwe3LgECBAgQIECAAAECTUAsNSdTBAgQIECAAAECBAiMCYilsYNblwABAgQIECBAgACBJiCWmpMpAgQIECBAgAABAgTGBMTS2MGtS4AAAQIECBAgQIBAExBLzckUAQIECBAgQIAAAQJjAmJp7ODWJUCAAAECBAgQIECgCYil5mSKAAECBAgQIECAAIExAbE0dnDrEiBAgAABAgQIECDQBMRSczJFgAABAgQIECBAgMCYgFgaO7h1CRAgQIAAAQIECBBoAmKpOZkiQIAAAQIECBAgQGBMQCyNHdy6BAgQIECAAAECBAg0AbHUnEwRIECAAAECBAgQIDAmIJbGDm5dAgQIECBAgAABAgSagFhqTqYIECBAgAABAgQIEBgTEEtjB7cuAQIECBAgQIAAAQJNQCw1J1MECBAgQIAAAQIECIwJiKWxg1uXAAECBAgQIECAAIEmIJaakykCBAgQIECAAAECBMYExNLYwa1LgAABAgQIECBAgEATEEvNyRQBAgQIECBAgAABAmMCYmns4NYlQIAAAQIECBAgQKAJiKXmZIoAAQIECBAgQIAAgTEBsTR2cOsSIECAAAECBAgQINAExFJzMkWAAAECBAgQIECAwJiAWBo7uHUJECBAgAABAgQIEGgCYqk5mSJAgAABAgQIECBAYExALI0d3LoECBAgQIAAAQIECDQBsdScTBEgQIAAAQIECBAgMCYglsYObl0CBAgQIECAAAECBJqAWGpOpggQIECAAAECBAgQGBMQS2MHty4BAgQIECBAgAABAk1ALDUnUwQIECBAgAABAgQIjAmIpbGDW5cAAQIECBAgQIAAgSYglpqTKQIECBAgQIAAAQIExgTE0tjBrUuAAAECBAgQIECAQBMQS83JFAECBAgQIECAAAECYwJiaezg1iVAgAABAgQIECBAoAmIpeZkigABAgQIECBAgACBMQGxNHZw6xIgQIAAAQIECBAg0ATEUnMyRYAAAQIECBAgQIDAmIBYGju4dQkQIECAAAECBAgQaAJiqTmZIkCAAAECBAgQIEBgTEAsjR3cugQIECBAgAABAgQINAGx1JxMESBAgAABAgQIECAwJiCWxg5uXQIECBAgQIAAAQIEmoBYak6mCBAgQIAAAQIECBAYExBLYwe3LgECBAgQIECAAAECTUAsNSdTBAgQIECAAAECBAiMCYilsYNblwABAgQIECBAgACBJiCWmpMpAgQIECBAgAABAgTGBMTS2MGtS4AAAQIECBAgQIBAExBLzckUAQIECBAgQIAAAQJjAmJp7ODWJUCAAAECBAgQIECgCYil5mSKAAECBAgQIECAAIExAbE0dnDrEiBAgAABAgQIECDQBMRSczJFgAABAgQIECBAgMCYgFgaO7h1CRAgQIAAAQIECBBoAmKpOZkiQIAAAQIECBAgQGBMQCyNHdy6BAgQIECAAAECBAg0AbHUnEwRIECAAAECBAgQIDAmIJbGDm5dAgQIECBAgAABAgSagFhqTqYIECBAgAABAgQIEBgTEEtjB7cuAQIECBAgQIAAAQJNQCw1J1MECBAgQIAAAQIECIwJiKWxg1uXAAECBAgQIECAAIEmIJaakykCBAgQIECAAAECBMYExNLYwa1LgAABAgQIECBAgEATEEvNyRQBAgQIECBAgAABAmMCYmns4NYlQIAAAQIECBAgQKAJiKXmZIoAAQIECBAgQIAAgTEBsTR2cOsSIECAAAECBAgQINAExFJzMkWAAAECBAgQIECAwJiAWBo7uHUJECBAgAABAgQIEGgCYqk5mSJAgAABAgQIECBAYExALI0d3LoECBAgQIAAAQIECDQBsdScTBEgQIAAAQIECBAgMCYglsYObl0CBAgQIECAAAECBJqAWGpOpggQIECAAAECBAgQGBMQS2MHty4BAgQIECBAgAABAk1ALDUnUwQIECBAgAABAgQIjAmIpbGDW5cAAQIECBAgQIAAgSYglpqTKQIECBAgQIAAAQIExgTE0tjBrUuAAAECBAgQIECAQBMQS83JFAECBAgQIECAAAECYwJiaezg1iVAgAABAgQIECBAoAmIpeZkigABAgQIECBAgACBMQGxNHZw6xIgQIAAAQIECBAg0ATEUnMyRYAAAQIECBAgQIDAmIBYGju4dQkQIECAAAECBAgQaAJiqTmZIkCAAAECBAgQIEBgTEAsjR3cugQIECBAgAABAgQINAGx1JxMESBAgAABAgQIECAwJiCWxg5uXQIECBAgQIAAAQIEmoBYak6mCBAgQIAAAQIECBAYExBLYwe3LgECBAgQIECAAAECTUAsNSdTBAgQIECAAAECBAiMCYilsYNblwABAgQIECBAgACBJiCWmpMpAgQIECBAgAABAgTGBMTS2MGtS4AAAQIECBAgQIBAExBLzckUAQIECBAgQIAAAQJjAmJp7ODWJUCAAAECBAgQIECgCYil5mSKAAECBAgQIECAAIExAbE0dnDrEiBAgAABAgQIECDQBMRSczJFgAABAgQIECBAgMCYgFgaO7h1CRAgQIAAAQIECBBoAmKpOZkiQIAAAQIECBAgQGBMQCyNHdy6BAgQIECAAAECBAg0AbHUnEwRIECAAAECBAgQIDAmIJbGDm5dAgQIECBAgAABAgSagFhqTqYIECBAgAABAgQIEBgTEEtjB7cuAQIECBAgQIAAAQJNQCw1J1MECBAgQIAAAQIECIwJiKWxg1uXAAECBAgQIECAAIEmIJaakykCBAgQIECAAAECBMYExNLYwa1LgAABAgQIECBAgEATEEvNyRQBAgQIECBAgAABAmMCYmns4NYlQIAAAQIECBAgQKAJiKXmZIoAAQIECBAgQIAAgTGBB7R+TmNlhJuwAAAAAElFTkSuQmCC
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA0sAAACUCAYAAABCxLIRAAAKeUlEQVR4Xu3ZoRHAMAwEwbj/plOADY7/BotoZXKT8/kIECBAgAABAgQIECBA4BI4TAgQIECAAAECBAgQIEDgFhBLXgUBAgQIECBAgAABAgQeAmLJsyBAgAABAgQIECBAgIBY8gYIECBAgAABAgQIECDQBPxZak6mCBAgQIAAAQIECBAYExBLYwe3LgECBAgQIECAAAECTUAsNSdTBAgQIECAAAECBAiMCYilsYNblwABAgQIECBAgACBJiCWmpMpAgQIECBAgAABAgTGBMTS2MGtS4AAAQIECBAgQIBAExBLzckUAQIECBAgQIAAAQJjAmJp7ODWJUCAAAECBAgQIECgCYil5mSKAAECBAgQIECAAIExAbE0dnDrEiBAgAABAgQIECDQBMRSczJFgAABAgQIECBAgMCYgFgaO7h1CRAgQIAAAQIECBBoAmKpOZkiQIAAAQIECBAgQGBMQCyNHdy6BAgQIECAAAECBAg0AbHUnEwRIECAAAECBAgQIDAmIJbGDm5dAgQIECBAgAABAgSagFhqTqYIECBAgAABAgQIEBgTEEtjB7cuAQIECBAgQIAAAQJNQCw1J1MECBAgQIAAAQIECIwJiKWxg1uXAAECBAgQIECAAIEmIJaakykCBAgQIECAAAECBMYExNLYwa1LgAABAgQIECBAgEATEEvNyRQBAgQIECBAgAABAmMCYmns4NYlQIAAAQIECBAgQKAJiKXmZIoAAQIECBAgQIAAgTEBsTR2cOsSIECAAAECBAgQINAExFJzMkWAAAECBAgQIECAwJiAWBo7uHUJECBAgAABAgQIEGgCYqk5mSJAgAABAgQIECBAYExALI0d3LoECBAgQIAAAQIECDQBsdScTBEgQIAAAQIECBAgMCYglsYObl0CBAgQIECAAAECBJqAWGpOpggQIECAAAECBAgQGBMQS2MHty4BAgQIECBAgAABAk1ALDUnUwQIECBAgAABAgQIjAmIpbGDW5cAAQIECBAgQIAAgSYglpqTKQIECBAgQIAAAQIExgTE0tjBrUuAAAECBAgQIECAQBMQS83JFAECBAgQIECAAAECYwJiaezg1iVAgAABAgQIECBAoAmIpeZkigABAgQIECBAgACBMQGxNHZw6xIgQIAAAQIECBAg0ATEUnMyRYAAAQIECBAgQIDAmIBYGju4dQkQIECAAAECBAgQaAJiqTmZIkCAAAECBAgQIEBgTEAsjR3cugQIECBAgAABAgQINAGx1JxMESBAgAABAgQIECAwJiCWxg5uXQIECBAgQIAAAQIEmoBYak6mCBAgQIAAAQIECBAYExBLYwe3LgECBAgQIECAAAECTUAsNSdTBAgQIECAAAECBAiMCYilsYNblwABAgQIECBAgACBJiCWmpMpAgQIECBAgAABAgTGBMTS2MGtS4AAAQIECBAgQIBAExBLzckUAQIECBAgQIAAAQJjAmJp7ODWJUCAAAECBAgQIECgCYil5mSKAAECBAgQIECAAIExAbE0dnDrEiBAgAABAgQIECDQBMRSczJFgAABAgQIECBAgMCYgFgaO7h1CRAgQIAAAQIECBBoAmKpOZkiQIAAAQIECBAgQGBMQCyNHdy6BAgQIECAAAECBAg0AbHUnEwRIECAAAECBAgQIDAmIJbGDm5dAgQIECBAgAABAgSagFhqTqYIECBAgAABAgQIEBgTEEtjB7cuAQIECBAgQIAAAQJNQCw1J1MECBAgQIAAAQIECIwJiKWxg1uXAAECBAgQIECAAIEmIJaakykCBAgQIECAAAECBMYExNLYwa1LgAABAgQIECBAgEATEEvNyRQBAgQIECBAgAABAmMCYmns4NYlQIAAAQIECBAgQKAJiKXmZIoAAQIECBAgQIAAgTEBsTR2cOsSIECAAAECBAgQINAExFJzMkWAAAECBAgQIECAwJiAWBo7uHUJECBAgAABAgQIEGgCYqk5mSJAgAABAgQIECBAYExALI0d3LoECBAgQIAAAQIECDQBsdScTBEgQIAAAQIECBAgMCYglsYObl0CBAgQIECAAAECBJqAWGpOpggQIECAAAECBAgQGBMQS2MHty4BAgQIECBAgAABAk1ALDUnUwQIECBAgAABAgQIjAmIpbGDW5cAAQIECBAgQIAAgSYglpqTKQIECBAgQIAAAQIExgTE0tjBrUuAAAECBAgQIECAQBMQS83JFAECBAgQIECAAAECYwJiaezg1iVAgAABAgQIECBAoAmIpeZkigABAgQIECBAgACBMQGxNHZw6xIgQIAAAQIECBAg0ATEUnMyRYAAAQIECBAgQIDAmIBYGju4dQkQIECAAAECBAgQaAJiqTmZIkCAAAECBAgQIEBgTEAsjR3cugQIECBAgAABAgQINAGx1JxMESBAgAABAgQIECAwJiCWxg5uXQIECBAgQIAAAQIEmoBYak6mCBAgQIAAAQIECBAYExBLYwe3LgECBAgQIECAAAECTUAsNSdTBAgQIECAAAECBAiMCYilsYNblwABAgQIECBAgACBJiCWmpMpAgQIECBAgAABAgTGBMTS2MGtS4AAAQIECBAgQIBAExBLzckUAQIECBAgQIAAAQJjAmJp7ODWJUCAAAECBAgQIECgCYil5mSKAAECBAgQIECAAIExAbE0dnDrEiBAgAABAgQIECDQBMRSczJFgAABAgQIECBAgMCYgFgaO7h1CRAgQIAAAQIECBBoAmKpOZkiQIAAAQIECBAgQGBMQCyNHdy6BAgQIECAAAECBAg0AbHUnEwRIECAAAECBAgQIDAmIJbGDm5dAgQIECBAgAABAgSagFhqTqYIECBAgAABAgQIEBgTEEtjB7cuAQIECBAgQIAAAQJNQCw1J1MECBAgQIAAAQIECIwJiKWxg1uXAAECBAgQIECAAIEmIJaakykCBAgQIECAAAECBMYExNLYwa1LgAABAgQIECBAgEATEEvNyRQBAgQIECBAgAABAmMCYmns4NYlQIAAAQIECBAgQKAJiKXmZIoAAQIECBAgQIAAgTEBsTR2cOsSIECAAAECBAgQINAExFJzMkWAAAECBAgQIECAwJiAWBo7uHUJECBAgAABAgQIEGgCYqk5mSJAgAABAgQIECBAYExALI0d3LoECBAgQIAAAQIECDQBsdScTBEgQIAAAQIECBAgMCYglsYObl0CBAgQIECAAAECBJqAWGpOpggQIECAAAECBAgQGBMQS2MHty4BAgQIECBAgAABAk1ALDUnUwQIECBAgAABAgQIjAmIpbGDW5cAAQIECBAgQIAAgSYglpqTKQIECBAgQIAAAQIExgTE0tjBrUuAAAECBAgQIECAQBMQS83JFAECBAgQIECAAAECYwJiaezg1iVAgAABAgQIECBAoAmIpeZkigABAgQIECBAgACBMQGxNHZw6xIgQIAAAQIECBAg0ATEUnMyRYAAAQIECBAgQIDAmIBYGju4dQkQIECAAAECBAgQaAJiqTmZIkCAAAECBAgQIEBgTEAsjR3cugQIECBAgAABAgQINAGx1JxMESBAgAABAgQIECAwJvADO98Ald0HVn8AAAAASUVORK5CYII=
For reference here is the function:
function isCanvasBlank(canvas) {
var blank = document.createElement('canvas');
blank.width = canvas.width;
blank.height = canvas.height;
return canvas.toDataURL() == blank.toDataURL();
}
the resulting images you have are different. The first one has a white background and the other is transparent.
This is causing the difference.
this is a quick way to fill the new canvas.
function isCanvasBlank(canvas) {
var blank = document.createElement('canvas');
blank.width = canvas.width;
blank.height = canvas.height;
var ctx = blank.getContext("2d");
ctx.fillStyle = "#FFFFFF";
ctx.fillRect(0, 0, blank .width, blank .height);
return canvas.toDataURL() == blank.toDataURL();
}
or use same function as the fiddle. blank.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
function isCanvasBlank(canvas) {
var blank = document.createElement('canvas');
blank.width = canvas.width;
blank.height = canvas.height;
blank.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
return canvas.toDataURL() == blank.toDataURL();
}

Canvas - resize image and save to a variable

So, I have a browser game which is run by a canvas with dynamic dimensions(it scales to the user's window's dimensions) which takes images at various sizes, scales them according to the user's window dimensions and draws them:
(example of my resizing algo:)
https://jsfiddle.net/8o3sn9mh/55/
var
canvas = document.getElementById('c'),
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var flower = new Image();
//the src is actually local, but i put a url for example's sake
flower.src = "http://plainicon.com/dboard/userprod/2803_dd580/prod_thumb/plainicon.com-46129-128px-af2.png"
flower.onload = function(){
ctx.drawImage(flower,0,0,128,128, 0, 0, window.innerWidth / 2, window.innerHeight / 2);
}
as you can see, I am drawing a 128x128px image and I resize it to half's the user's window. So, I was thinking - my game may do this operation every single render execution, and it may take unnecessary resizing time to perform it every time. So:
1) Does performing this resizing take more time since I use it every requestAnimationFrame?
2) Is there a way to resize my image one time, save it into a var and use it in order to save that time?
Regardless I'd like a way to save the resized image into a var in order to fit it inside canvas' createPattern method:
pattern = ctx.createPattern(resizedFlower, 'repeat'); //resizedFlower should be the original flower resized into half's the user's window's width + height
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 50, 50);
You can save your image to a canvas and then use drawImage with that canvas. For instance:
var canvas = document.getElementById('c'),
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var flowerCanvas = document.createElement('canvas');
flowerCanvas.width = window.innerWidth;
flowerCanvas.height = window.innerHeight;
flowerCtx = flowerCanvas.getContext('2d');
var flower = new Image();
//the src is actually local, but i put a url for example's sake
flower.src = "http://plainicon.com/dboard/userprod/2803_dd580/prod_thumb/plainicon.com-46129-128px-af2.png"
flower.onload = function(){
flowerCtx.drawImage(flower,0,0,128,128, 0, 0, window.innerWidth / 2, window.innerHeight / 2);
ctx.drawImage(flowerCanvas,0,0);
}
Now you can reuse flowerCanvas and it will be whatever size you initially drew it at. You probably won't see much of a performance gain between resizing vs not resizing, although you should benchmark it to be sure.
Also you shouldn't resize everything everyframe, you can listen for a window resize event, and resize when that event fires.

Change html canvas black background to white background when creating jpg image from png image

I have a canvas which is loaded with a png image. I get its jpg base64 string by .toDataURL() method like this:
$('#base64str').val(canvas.toDataURL("image/jpeg"));
But the transparent parts of the png image are shown black in the new jpg image.
Any solutions to change this color to white? Thanks in advance.
This blackening occurs because the 'image/jpeg' conversion involves setting the alpha of all canvas pixels to fully opaque (alpha=255). The problem is that transparent canvas pixels are colored fully-black-but-transparent. So when you turn these black pixels opaque, the result is a blackened jpeg.
The workaround is to manually change all non-opaque canvas pixels to your desired white color instead of black.
That way when they are made opaque they will appear as white instead of black pixels.
Here's how:
// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
if(data[i+3]<255){
data[i]=255;
data[i+1]=255;
data[i+2]=255;
data[i+3]=255;
}
}
ctx.putImageData(imgData,0,0);
After spending a lot of time on this and this post specifically, and these solutions kinda worked expect I just couldn't get the canvas to look right. Anyway I found this solution elsewhere and wanted to post it here incase it helps someone else from spending hours trying to get the black background to white and look like the original.
public getURI(): string {
let canvas = <HTMLCanvasElement>document.getElementById('chartcanvas');
var newCanvas = <HTMLCanvasElement>canvas.cloneNode(true);
var ctx = newCanvas.getContext('2d');
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, newCanvas.width, newCanvas.height);
ctx.drawImage(canvas, 0, 0);
return newCanvas.toDataURL("image/jpeg");
}
This answer is a bit longer, but I find it to be more 'correct' in that it handles these things without directly modifying raw canvas data. I find that to be a pretty messy and theoretically unsatisfying solution. There are built in functions to achieve that, and they ought to be used. Here is the solution I found/pilfered:
function canvasToImage(backgroundColor){
var context = document.getElementById('canvas').getContext('2d');
canvas = context.canvas;
//cache height and width
var w = canvas.width;
var h = canvas.height;
var data;
//get the current ImageData for the canvas.
data = context.getImageData(0, 0, w, h);
//store the current globalCompositeOperation
var compositeOperation = context.globalCompositeOperation;
//set to draw behind current content
context.globalCompositeOperation = "destination-over";
//set background color
context.fillStyle = backgroundColor;
//draw background / rect on entire canvas
context.fillRect(0,0,w,h);
//get the image data from the canvas
var imageData = this.canvas.toDataURL("image/jpeg");
//clear the canvas
context.clearRect (0,0,w,h);
//restore it with original / cached ImageData
context.putImageData(data, 0,0);
//reset the globalCompositeOperation to what it was
context.globalCompositeOperation = compositeOperation;
//return the Base64 encoded data url string
return imageData;
}
Basically, you create a white background image and underlay it under the canvas and then print that. This function is mostly plagiarized from someone's blog, but it required a bit of modification -- such as actually getting the context -- and copied directly from my (working) code, so as long as your canvas element has the id 'canvas', you should be able to copy/paste it and have it work.
This is the blog post I modified it from:
http://www.mikechambers.com/blog/2011/01/31/setting-the-background-color-when-generating-images-from-canvas-todataurl/
The big advantage of my function over this is that it outputs to jpeg instead of png, which is more likely to work well in chrome, which has a dataurl limit of 2MB, and it actually grabs the context, which was a glaring omission in the original function.
Marks answer is correct, but when a picture has some antialiasing applied, the exported image won't be as good as it should be (mainly text). I would like to enhance his solution:
// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
if(data[i+3]<255){
data[i] = 255 - data[i];
data[i+1] = 255 - data[i+1];
data[i+2] = 255 - data[i+2];
data[i+3] = 255 - data[i+3];
}
}
ctx.putImageData(imgData,0,0);
If you want to move to white only full transparent pixels just check for (data[i+3]==0) instead of (data[i+3]<255).
Why not to save it as PNG?
canvas.toDataURL("image/png");
// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
if(data[i+3]<255){
data[i] = 255 - data[i];
data[i+1] = 255 - data[i+1];
data[i+2] = 255 - data[i+2];
data[i+3] = 255 - data[i+3];
}
Here is my function that resizes a photo and handles the black transparent background problem:
resizeImage({ file, maxSize, backgroundColor }) {
const fr = new FileReader();
const img = new Image();
const dataURItoBlob = (dataURI) => {
const bytes = (dataURI.split(',')[0].indexOf('base64') >= 0)
? window.atob(dataURI.split(',')[1])
: window.unescape(dataURI.split(',')[1]);
const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
const max = bytes.length;
const ia = new Uint8Array(max);
for (let i = 0; i < max; i += 1) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ia], { type: mime });
};
const resize = () => {
// create a canvas element to manipulate
const canvas = document.createElement('canvas');
canvas.setAttribute('id', 'canvas');
const context = canvas.getContext('2d');
// setup some resizing definitions
let { width, height } = img;
const isTooWide = ((width > height) && (width > maxSize));
const isTooTall = (height > maxSize);
// resize according to `maxSize`
if (isTooWide) {
height *= maxSize / width;
width = maxSize;
} else if (isTooTall) {
width *= maxSize / height;
height = maxSize;
}
// resize the canvas
canvas.width = width;
canvas.height = height;
// place the image on the canvas
context.drawImage(img, 0, 0, width, height);
// get the current ImageData for the canvas
const data = context.getImageData(0, 0, width, height);
// store the current globalCompositeOperation
const compositeOperation = context.globalCompositeOperation;
// set to draw behind current content
context.globalCompositeOperation = 'destination-over';
// set background color
context.fillStyle = backgroundColor;
// draw background / rect on entire canvas
context.fillRect(0, 0, width, height);
// get the image data from the canvas
const imageData = canvas.toDataURL('image/jpeg');
// clear the canvas
context.clearRect(0, 0, width, height);
// restore it with original / cached ImageData
context.putImageData(data, 0, 0);
// reset the globalCompositeOperation to what it was
context.globalCompositeOperation = compositeOperation;
// return the base64-encoded data url string
return dataURItoBlob(imageData);
};
return new Promise((resolve, reject) => {
if (!file.type.match(/image.*/)) {
reject(new Error('VImageInput# Problem resizing image: file must be an image.'));
}
fr.onload = (readerEvent) => {
img.onload = () => resolve(resize());
img.src = readerEvent.target.result;
};
fr.readAsDataURL(file);
});
},
That is a Vue JS instance method that can be used like this:
// this would be the user-uploaded file from the input element
const image = file;
const settings = {
file: image,
maxSize: 192, // to make 192x192 image
backgroundColor: '#FFF',
};
// this will output a base64 string you can dump into your database
const resizedImage = await this.resizeImage(settings);
My solution here is a combination of about 74 different StackOverflow answers related to resizing images client-side, and the final boss was to handle transparent PNG files.
My answer would not be possible without Laereom's answer here.

Manipulate an image on a canvas

I have to load an image on a canvas object and after clicking some buttons or sliding some sliders, I need to change some pixels on this canvas. Thats my code so far:
function CanvasImage(element, src) {
var width, height;
var canvas = document.getElementById(element);
var ctx = canvas.getContext("2d");
var img = new Image;
img.onload = function () {
width = canvas.width = img.width;
height = canvas.height = img.width;
ctx.drawImage(img, 0, 0, width, height)
};
img.src = src;
this.brightness = function (amount) {
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = imgData.data;
for (var i = 0; i < data.length; i += 4) {
data[i] += amount;
data[i + 1] += amount;
data[i + 2] += amount
}
imgData.data = data;
ctx.putImageData(imgData, 0, 0)
};
this.transform = function (data) {
ctx.save();
this.brightness(data.brightness);
ctx.restore()
}
}
Thats I've just added the function to manipulate the brightness of the image. At the beginning, the image is loaded to the canvas element. That works fine for me. The function brightness is just adding the giving amount to the pixels. That also works. I think my problem is the transform function. I save the state of the canvas context and call my transform function. After this manipulation I restore the state. I would say, the context should look like the earlier loaded image.
My problem now is, that after changing a slider and returning it to the original state my image is still brighter or darker and doesn't have the original state of the image. Do you have any suggestions for me, please?
I appreciate every answer. :D
save and restore only saves the state of the canvas not image data. State are things such as styles, translations, text alignment and so forth.
In order to get back original content you need to either redraw the source or store the data to be restored with another canvas element (ie. off-screen canvas), or store the data as an array (getImageData()).
Also worth to notice is that putImageData by-passes the canvas' state and changes pixels directly independently.
My recommendation would be to keep the source available one way or another and always redraw it to canvas before a new alteration takes place. For reset simply redraw source and skip alteration.
ONLINE DEMO HERE
this.brightness = function (amount) {
ctx.drawImage(img, 0, 0, width, height)
/// no change, just exit
if (amount === 0) return;
/// ... snipped
};

Canvas Image trouble

I am having issues with JavaScript and Canvas particularly in Firefox. I am trying to create an canvas image viewer that will run when canvas is enabled in a browser in place of standard img tag. Javascript changes the image from an array of src paths when an arrow is clicked. For some reason Firefox is always one img out of sync. When I test it in chrome the imgs are in sync but the first one is not displayed.
here is the image object
var image = new Image();
image.src = myImage.src[0];
here is my initial canvas intialization
if(canvas()){ //canvas stuff
var myc = document.createElement('canvas');
myc.id='mycanvasx';
mycanvas = document.getElementById('mycanvasx');
myc.width = image.width;
myc.height = image.height;
//viewer.replaceChild(myc, scroller);
viewer.appendChild(myc);
var context = myc.getContext("2d");
dw = image.width;
dh = image.height;
context.clearRect(0, 0, myc.width, myc.height);
context.drawImage(image, 0, 0, dw, dh);
}
and when the arrow is pressed a function like this is called
function update(){
if(canvas()){
context.clearRect(0,0,myc.width, myc.height) //maybe bloat
myc.width = image.width;
myc.height = image.height;
dw = image.width;
dh = image.height;
context.drawImage(image, 0, 0, dw, dh);
}
}
function left(){ //these move the image through the source array
if(myImage.num > 0){myImage.num--;}else{
myImage.num = (myImage.src.length-1)}
image.src = myImage.src[myImage.num];
scroller.src = image.src;
update();
x=0;
}
I know I must be missing something simple here. I'm using firefox 4.0.1 and chrome 11
You're setting the src of the image and then immediately painting it into the canvas. But image loads are async. So you're painting it before the new src you set has loaded; since you keep reusing the same image it's still showing the previous image at that point.
You want to run update() off an onload listener on image.

Categories

Resources