trying to debug undo functionality in canvas paint program - javascript

here is my page
http://www.dev.1over0.com/canvas/canvas_web.html
I’m having issues with the undo functionality – I think I’m getting close but here’s the issue
First I’ll tell you the basics of the drawing application – I’m layering four different canvases
http://www.dev.1over0.com/canvas/js/canvas_web.js
if you look at the js file you’ll see at the top all my comments on what I’m doing with the canvases
when a user uses a tool to draw – it actually draws it first temporarily on the imageTemp canvas –
each tool has it’s own object (pencil, rectangle, line)
with functions within for mousedown, mousemove and mouseup events
at the end of the mouseup event it calls this function
function img_update() {
contexto.drawImage(canvas, 0, 0);
saveActions();
context.clearRect(0, 0, canvas.width, canvas.height);
}
Here the image that’s on the temp canvas ends up being drawn on the imageView canvas so essentially the imageTemp canvas is being erased each time
So for the undo functionality I first declare a an empty array
var undoHistory = [];
I also declare a new image
var undoImg = new Image();
when img_update is invoked – it calls another function saveActions
function saveActions() {
var imgData = canvaso.toDataURL("image/png");
undoHistory.push(imgData);
$('#undo_canvas button').removeAttr('disabled');
}
In this function – I’m essentially saving the newly drawed image canvas into the undoHistory array
This seems to be working fine (I believe)
So after each time the permanent imageView canvas is updated is saves that state into an array
Now if you notice once you draw an image and it’s save to the imageView canvas the undo button becomes enabled
So if you click the undo button it calls this function
function undoDraw() {
if(undoHistory.length > 0){
$(undoImg).load(function(){
contexto.drawImage(undoImg, 0,0);
});
undoImg.src = undoHistory.pop();
if(undoHistory.length == 0) {
$('#undo_canvas button').attr('disabled','disabled');
}
}
}
Here what I’m trying to do is take the previous image state and draw it on the canvas
It’s working in a way that it affects the z-index of the images but not getting rid of the images
And you have to click it twice as well which confuses me
So if you can try drawing out three rectangles on top of each other
You can click the undo button and it will effect the z-index – click it three times you’ll see what I mean so it's sort of working, maybe ;-{

You have to clear the canvas before you draw the last image, otherwise it will draw an image onto the previous canvas;
Also, you pop the last image which is the one you just've pushed in (this is why you have to click twice). So try something like this ( you might have to adapt the code I'm not sure it works out of the box)
function undoDraw(){
if(undoHistory.length > 0){
$(undoImg).load(function(){
contexto.clearRect(0,0, canvas.width, canvas.height);
contexto.drawImage(undoImg, 0,0);
});
undoHistory.pop();
undoImg.src = undoHistory.pop();
if(undoHistory.length == 0) {
$('#undo_canvas button').attr('disabled','disabled');
}
}
}

Related

Canvas Emptied but Reappears

I have seen several solutions on this issue but have not found one yet that works for my situation.
I have a chart being made with chart.js which uses canvas to display. I have a function that is supposed to clear the canvas so that I can then redraw the chart with different x variables. The function works initially but as soon as I start to hover on my cleared canvas the old contents reappear. More specifically it seems to reappear if I am hovering over on of the previous data points.
Here is the function:
function empty(){
canvas = document.getElementById("loansChart");
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
}
EDIT
If a canvas contained items that had hover initiated function items, do those items still exist (just invisible) in the canvas?
You should use the chart.js functions for updating and clearing charts rather than the ones for the html canvas.
The docs are quite straightforward. https://www.chartjs.org/docs/latest/developers/updates.html

HTML5 Canvas Context.drawImage not showing up

I am trying to make a drawing program with HTML canvas. You can see a version of it here: https://naclcaleb.github.io/Draw
My architecture works somewhat like this:
I have a master Canvas object, which contains a list of layers that it draws.
Each layer has a list of "Strokes". Each stroke contains a list of actions required to draw it.
I've had a few problems with lag. First, it was taking too long to draw all the strokes in the list.
I solved this by "rendering" the layer every time a stroke is added, so it's just an image.
That worked fine, except that I still had to draw every stroke when the user pressed Ctrl+Z, so I could reset the render.
So every time they tried to undo, it would take a while to compute - usable, but not ideal.
To fix this, I tried the same approach with each stroke - when it's done being created, "render" it by drawing it on a different hidden canvas, then turning it into an image with a DataURL.
(NOTE: I did try using getImageData and putImageData instead, but then it would replace the previous strokes with empty pixels)
And now I'm getting a pretty big problem:
Whenever I make a stroke, it immediately disappears. But once I press Ctrl+Z, all the previous strokes suddenly become visible.
I've been trying to figure this out for a while now.
Here's basically what happens:
The mouse is released, which triggers this event listener (found in the layer class):
this.el.addEventListener("mouseup", function(e){
//You can ignore this line...
if (active){
//...and this if statement. It just handles the erasers, and has been tested to work
if (that.erasing){
CURRENT_TOOL.currentStroke.actions.push({
func: ctx.setGlobalCompositeOperation,
params: ["source-over"]
});
}
//Have the current stroke create its render - I have used console.log to ensure that this does get run, and that the dataURL produced shows the correct image.
CURRENT_TOOL.currentStroke.createRender();
//The endStroke method returns the currentStroke, which I grab here
var newStroke = CURRENT_TOOL.endStroke(ctx);
//"that" is just a reference to the layer instance
//Add it to the strokes list
that.strokes.push(newStroke);
//Update the render
that.updateRender();
}
});
As you saw in the event listener, it calls an updateRender method. Here is that method (also in the layer class):
//Clear the canvas
this.ctx.clearRect(0, 0, this.el.width, this.el.height);
//Put the LAYER's render onto the screen
this.ctx.putImageData(this.render, 0, 0);
//Draw the stroke - I'll give you the draw function, but I've confirmed it does get run
this.strokes[this.strokes.length-1].draw();
//Now get the image data and update the render
this.render = this.ctx.getImageData(0,0, this.el.width, this.el.height);
As you saw in that function, it calls the stroke's draw function (found in the stroke class):
draw(){
//Ignore this if
if (this.render === undefined){
for (var i = 0;i<this.actions.length;i++){
this.actions[i].func.apply(this.ctx, this.actions[i].params);
}
}
else {
//I've used console.log to confirm that this runs.
//Draw the render
this.ctx.drawImage(this.render, 0, 0);
//I've also console.logged the dataurl of the layer before and after drawing the image, and neither have the render drawn on them.
}
}
This seems to say that the this.ctx.drawImage is not being run? Or that it's being run in the wrong way?
Anyways, that's that, and though I apologize for the longevity of this question, I'm hoping someone can help me. This is really my last resort.
One more thing: I got it to work in the link I gave earlier by rendering the layer using the same method I render the strokes - using an image with a dataURL instead of getImageData and putImageData. I'm not really sure what to think about that...

html5 canvas img goes behind other drawn objects

I am creating a html5 canvas game and the main idea is that there is a loop at every animation that redraws everything on my canvas in every frame.
For some reason the objects drawn don't appear in the order i want them to. i want the background image first, then a rectangle and finally an other image to be shown on top of each other.However, the rectangle blocks the view of the second image not other way around.
my relevant code:
function playerdraw(p){
ctx.rect(p.x,p.y,100,150);
ctx.stroke();
//irrelevant stuff here...
ctx.drawImage(p.im,p.x,p.y+25,100,100);
}
I run the whole thing on window.onload so image loading shoudn't be a problem.
Why is this happening?
(as you haven't provided enough code, I assume this could be the issue.)
You need to redraw the background image each time you run the playerdraw() function (if you're clearing the entire canvas each time). So, the following code should work :
function playerdraw(p) {
// clear entire canvas
ctx.clearRect(...);
// draw background image
ctx.drawImage(...);
// draw rectangle
ctx.rect(p.x, p.y, 100, 150);
ctx.stroke();
// irrelevant stuff here...
// draw other image
ctx.drawImage(p.im, p.x, p.y + 25, 100, 100);
}
Note: You need to clear the entire canvas at the very beginning (if you're doing so). There's is also a possibility that you're doing some kind of canvas translating or scaling stuff which if done in wrong order, things can get pretty messed up.
I now know what my issue was. I forgot to add
ctx.beginPath();
in my code. I called my function again and again and it drew more rectangles then I wanted to. I feel so silly.

canvas Elements goes invisible after clicking on it

I am trying to to write undo/Redo functionality on canvas . i am using fabric-js to draw Objects . undo ?Redo is working fine but the problem is after calling undo/redo function if i click on canvas Elements goes Invisible. i tried canvas.renderAll() still i am getting problem.
Steps for Undo::
Before Element Drawn i am saving canvas.toDataURl() in an variable Array.
2.When Undo Button is clicked i am using clear method of fabric to clear canvas and then getting variable Array to Draw on canvas using Previous `toDataUrl() .
Code ::
function undoDrawOnCanvas() {
console.log('inside undodrawn');
// If we have some restore points
if (restorePoints.length > 0) {
// Create a new Image object
var oImg = new Image();
// When the image object is fully loaded in the memory...
oImg.onload = function() {
//Saving point for Redo
c_redo=document.getElementById("design_text").toDataURL("image/png");
canvas.clear();
// Get the canvas context
var canvasContext = document.getElementById("design_text").getContext("2d");
// and draw the image (restore point) on the canvas. That would overwrite anything
// already drawn on the canvas, which will basically restore it to a previous point.
canvasContext.drawImage(oImg, 0, 0);
}
// The source of the image, is the last restoration point
oImg.src = restorePoints.pop();
}
}

bezier curve creation dynamically?

I am trying to achieve grass like bend on a straight line created by using Cubic bezier curve.I have created a straight line using Bezier curve and now i want to bend it from top.But the problem I am facing is I am not able to do it dynamically.The moment I use mousemove for creating the bend,series of curves appear giving the canvas a paint like look which is somthing I dont want.I just want a single curved line.My question is how do I clear the previous lines that have been drawn and restore the latest bended curve??
My CODE:here is my code if you want to have a look at it
Create two canvases stacked on top of each other:
The first one containing the static content, the other only the dynamic content.
This way you will only need to be concerned about clearing the area the grass was drawn in (and this is much faster too as there is no need to redraw static all the time).
Place the two canvases inside a div which have position set to relative, then place the canvases using position set to absolute.
Now you can make a function to draw the static content (you will need to redraw it if the browser window gets resized etc.).
Record the area the grass is drawn within and clear only this (for each grass) in the next frame.
If this last is too complicated, just use a simple clear on the "dynamic" canvas:
ctxDyn.clear(0, 0, canvasDyn.width, canvasDyn.height);
This will also preserve transparency of this canvas, or "layer" if you like.
Use requestAnimationFrame to do the actual animation.
var isPlaying = true;
function animate() {
ctxDyn.clear(0, 0, canvasDyn.width, canvasDyn.height);
drawGrass(); //function to draw the actual grass
if (isPlaying) requestAnimationFrame(animate);
}
animate(); // start
The isPlaying is a flag you can toggle with a button on screen or something similar, to be able to stop the animation. See the link for requestAnimationFrame on how to make this cross-browser as this is still a "young" implementation.
dynCtx and dynCanvas can of course be called what you want.
You need to erase the current contents of the canvas before redrawing your updated "grass". This would involve redrawing everything else that is "static" on the canvas. For example:
function redraw() {
// Erase the current contents
ctx.fillStyle = 'white';
ctx.fill(0, 0, canvas.width, canvas.height);
// Draw the stuff that does not change from frame to frame
drawStaticContent();
// Draw the dynamic content
drawGrass();
}
In your mousemove event, update your grass curve information, then 'redraw'.

Categories

Resources