I've got a canvas where the user can draw, but I need to detect the percent of filled area. The user has to continue to draw during the checking.
I already have my canvas and the user can draw on it. I got here a little function to check if some pixels aren't blank, but it's too slow and the user can't draw anymore.
Have you any idea of how I can do this?
UPDATE :
for drawing on my canvas I'm using lineTo() :
$.fn.drawMouse = function() {
var clicked = 0;
var start = function(e) {
clicked = 1;
ctx.beginPath();
x = e.pageX;
y = e.pageY;
ctx.moveTo(x,y);
};
var move = function(e) {
if(clicked){
x = e.pageX;
y = e.pageY;
ctx.lineTo(x,y);
ctx.stroke();
}
};
var stop = function(e) {
clicked = 0;
};
$(this).on("mousedown", start);
$(this).on("mousemove", move);
$(window).on("mouseup", stop);
};
Wouldn't it be better, if you added a "x percent covered" value to a percentFilled variable each time the user draws something?
Example:
Suppose you draw by creating circles. You can calculate the area of the circle, then perhaps check, if some of the pixels aren't already filled. Add (areaOfCircle (in %, or maybe px^2) - areaAlreadyFilled (in %, or maybe px^2) ) to your percentFilled variable and you get the current filled percentage. Everytime you paint something, it adds the painted area. You can completely avoid calculating the covered part of the canvas as a whole entirely.
Related
I'm having a problem with moving an object on the canvas but only on x or y-axis once at a time.
Idea:
The user can drag an object with CTRL / Shift pressed and then he's able to move an object on the x-axis or y-axis only. I move an object on an axis that I'm further away from starting position. This feature is present in most vector software (Corel, Inkscape, etc.).
On this video you can see what I mean:
https://www.youtube.com/watch?v=9AheCfh13Aw
To be honest - I don't know where to start. I guess I have to track the origin position of the object while dragging so I can check which axis should be locked while mouse movement.
Forked jsFiddle I'm using for developing:
https://jsfiddle.net/sores/1emj47q9/38/
Mouse movement event listener:
canvas.addEventListener('mousemove', function(e) {
if (myState.dragging) {
console.warn('mouseMove and dragging');
console.warn('object position: ', myState.selection);
var mouse = myState.getMouse(e);
console.warn('mouse: ', mouse);
// We don't want to drag the object by its top-left corner, we want to drag it
// from where we clicked. Thats why we saved the offset and use it here
// get the very first position of the object?
myState.selection.x = mouse.x - myState.dragoffx;
// y locked
// myState.selection.y = mouse.y - myState.dragoffy;
console.warn('new position: ', myState.selection);
myState.valid = false; // Something's dragging so we must redraw
}
}, true);
If anyone is familiar with such a thing I will be very grateful for any tips.
Thanks!
This answer is posted as code untested. The theory should point you in the right direction, though. I've commented the additions I've made to hopefully make it clearer but the basic rundown is;
Get the current mouse position
Move the mouse
Get the new mouse position
Compare the new mouse position against the old one
The biggest difference in positions determines the axis
When you've determined the direction (such as y), reset the position of the other axis (in this case, x) as it'll probably move slightly. I haven't included code for this.
// A reference to the previous mouse position
let initialMousePosition = {
x: 0,
y: 0
}
// A reference to locked axis that can be set or unset later
let dragLock: {
x: false,
y: false
}
// A reference to the control key
let ctrlPressed = false;
// Moved previousMousePosition logic in to on mouse down for increased reliability
canvas.addEventListener('mousedown', function(e){
var mouse = myState.getMouse(e);
initialMousePosition.x = mouse.x;
initialMousePosition.y = mouse.y;
})
canvas.addEventListener('mousemove', function(e) {
if (myState.dragging) {
var mouse = myState.getMouse(e);
// check for 0 positions to skip the first tick
if (initialMousePosition.x > 0 || initialMousePosition.y > 0) {
// compare previous mouse x & y positions to the current mouse positions
// assume the biggest difference is the direction of dragging
if (mouse.x - initialMousePosition.x > mouse.y - initialMousePosition.y) {
dragLock.y = true;
} else {
dragLock.x = true;
}
}
if (!dragLock.x || !ctrlPressed) myState.selection.x = mouse.x - myState.dragoffx;
if (!dragLock.y || !ctrlPressed) myState.selection.y = mouse.y - myState.dragoffy;
myState.valid = false; // Something's dragging so we must redraw
}
}, true);
//
canvas.addEventListener('keydown', function(e) {
if (event.which == "17")
ctrlPressed = true;
});
canvas.addEventListener('keyup', function(e) {
ctrlPressed = false;
})
I'm having a few issues with canvas and changing a variable to assign a different mousestate. I have shown a section of my code below.
$("#shape").click(function() { //Runs the drawbackground function on click
mouse_state = "fill_shape";
console.log("shape: " + mouse_state);
});
$("#paint").click(function() { //Runs the drawbackground function on click
console.log('hi');
mouse_state = "paint";
console.log("paint: " + mouse_state);
});
var mouse_state = "paint";
if (myCanvas) { //Checks is canvas exists and/or is supported by the browser
var isDown = false; //Stores the current status of the mouseclick, default is not down
var ctx = myCanvas.getContext("2d"); //Stores the 2d context of the canvas
var canvasX, canvasY; //Initialises variables canvasX and canvasY
if (mouse_state == "paint"){
$(myCanvas).mousedown(function(e) { //When the user clicks on the canvas, this function runs
e.preventDefault(); //Prevents the cursor from changing when clicking on the canvas
isDown = true; //Sets the mouseclick variable to down
ctx.beginPath(); //Begins the path
canvasX = e.pageX - myCanvas.offsetLeft; //Stores the mouse position on the x axis by subtracting the distance from the left edge of the canvas from the position from the left edge of the document.
canvasY = e.pageY - myCanvas.offsetTop; //Stores the mouse position on the y axis by subtracting the distance from the top edge of the canvas from the position from the top edge of the document.
ctx.moveTo(canvasX, canvasY); //Sets the position which the drawing will begin
}).mousemove(function(e) {
if (isDown != false) { //On the mousemouse the line continues to be drawn as the mouseclick variable will still be set as false
canvasX = e.pageX - myCanvas.offsetLeft; //Similar to above
canvasY = e.pageY - myCanvas.offsetTop;
ctx.lineTo(canvasX, canvasY); //Stores the information which should be drawn
ctx.strokeStyle = current_colour; //Sets the colour to be drawn as the colour stored in the current colour variable
ctx.stroke(); //Draws the path given
}
}).mouseup(function(e) { //When the mouse click is released, do this function...
isDown = false; //Sets the mouseclick variable to false
ctx.closePath(); //Closes the path
});
}
else if(mouse_state == "fill_shape"){
//Checks is canvas exists and/or is supported by the browser
$(myCanvas).mousedown(function(ev) { //When the user clicks on the canvas, this function runs
console.log("1" + mouse_state);
ev.preventDefault(); //Prevents the cursor from changing when clicking on the canvas
isDown = true; //Sets the mouseclick variable to down
ctx.beginPath(); //Begins the path
canvasX = ev.pageX - myCanvas.offsetLeft; //Stores the mouse position on the x axis by subtracting the distance from the left edge of the canvas from the position from the left edge of the document.
canvasY = ev.pageY - myCanvas.offsetTop; //Stores the mouse position on the y axis by subtracting the distance from the top edge of the canvas from the position from the top edge of the document.
ctx.moveTo(canvasX, canvasY); //Sets the position which the drawing will begin
}).mousemove(function(ev) {
if (isDown != false) { //On the mousemouse the line continues to be drawn as the mouseclick variable will still be set as false
canvasX = ev.pageX - myCanvas.offsetLeft; //Similar to above
canvasY = ev.pageY - myCanvas.offsetTop;
ctx.lineTo(canvasX, canvasY); //Stores the information which should be drawn
ctx.strokeStyle = current_colour; //Sets the colour to be drawn as the colour stored in the current colour variable
ctx.stroke(); //Draws the path given
}
}).mouseup(function(ev) { //When the mouse click is released, do this function...
ctx.fillStyle = current_colour;
ctx.fill();
isDown = false; //Sets the mouseclick variable to false
ctx.closePath(); //Closes the path
});
}};
The drawing of the canvas works fine and the two different 'mouse_states' work fine with the first (paint) simply drawing the lines or shapes and the second (fill_shape) drawing shapes and then filling them in using ctx.fill.
The mouse_state variable is initialised as "paint" so the paint function runs and when I change it to "shape_fill" the shape fill function runs fine. The problem arises when changing between the two states using the buttons to change the variable name. The console log shows that the variable name changes as expected but it just doesn't seem to take any affect and sticks with the initial value of the mouse_state variable. I would appreciate any help or tips with this.
You're running the if statements at the wrong time - they're executing on page load, and subsequently only binding the first set of events.
Instead, bind only one set of events, and check the variable within them and run the corresponding code:
if (myCanvas) { //Checks is canvas exists and/or is supported by the browser
var isDown = false; //Stores the current status of the mouseclick, default is not down
var ctx = myCanvas.getContext("2d"); //Stores the 2d context of the canvas
var canvasX, canvasY; //Initialises variables canvasX and canvasY
$(myCanvas).mousedown(function(e) { //When the user clicks on the canvas, this function runs
if (mouse_state == "paint") {
//code for paint
} else if (mouse_state == "fill_shape") {
//code for fill_shape
}
}); //etc, for other events
}
I've gotten a lot of help from this site, but I seem to be having a problem putting all of it together. Specifically, in JS, I know how to
a) draw an image onto canvas
b) make a rectangle follow the cursor (Drawing on a canvas) and (http://billmill.org/static/canvastutorial/ball.html)
c) draw a rectangle to use as a background
What I can't figure out is how to use a rectangle as the background, and then draw an image (png) on the canvas and get it to follow the cursor.
What I have so far looks like this:
var canvas = document.getElementByID('canvas');
var ctx = canvas.getContext('2d');
var WIDTH = canvas.width;
var HEIGHT = canvas.height;
var bgColor = '#FFFFFF';
var cirColor = '#000000';
clear = function() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
}
drawIMG = function(x,y,r) {
ctx.fillStyle = cirColor;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}
draw = function() {
ctx.fillStyle = bgColor;
clear();
ctx.fillRect(0, 0, WIDTH, HEIGHT);
drawIMG(150, 150, 30);
drawIMG(300, 500, 12);
};
draw();
This will draw in the HTML5 canvas element, the height and width of which are specified in the HTML and so are variable, with a white rectangle the size of the canvas beneath two black circles at (150,150) and (300,500). It does that perfectly well.
However, I don't know how to also make JS draw a .png on top of that that follows the cursor. Like I said, I've been able to do most of the steps individually, but I have no idea how to combine them. I know, for instance, that I have to do
img = new Image();
and then
img.src = 'myPic.png';
at some point. They need to be combined with position modifiers like
var xPos = pos.clientX;
var yPos = pos.clientY;
ctx.drawImage(img, xPos, yPos);
But I have no idea how to do that while maintaining any of the other things I've written above (specifically the background).
Thanks for your patience if you read through all of that. I have been up for a while and I'm afraid my brain is so fried I wouldn't recognize the answer if it stripped naked and did the Macarena. I would appreciate any help you could possibly send my way, but I think a working example would be best. I am an initiate in the religion of programming and still learn best by shamelessly copying and then modifying.
Either way, you have my optimistic thanks in advance.
First off, I've made an animated purple fire follow the mouse. Click (edit doesn't exist anymore)here to check it out.
Before you continue, I recommend you check out these websites:
http://www.williammalone.com/articles/create-html5-canvas-javascript-sprite-animation/
William talks about the basic techniques of canvas animations
http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
Paul Irish talks about a recursive animation function that turns at 60 fps.
Using both of their tutorials is pretty a good start for animation.
Now from my understanding you want one 'background' and one animation that follows the cursor. The first thing you should keep in mind is once you draw on your canvas, whatever you draw on, gets replaced. So the first thing I notice that will cause performance issues is the fact you clear your whole canvas, and not what needs to be cleared.
What you need to do is memorize the position and size of your moving element. It doesn't matter what form it takes because your clearRect() should completely remove it.
Now you're probably asking, what if I draw on the rectangle in the background. Well that will cause a problem. You have two solutions. Either, (a) Clear the background and clear your moving animation and draw them back again in the same order or (b) since you know your background will never move, create a second canvas with position = absolute , z-index = -1 , and it's location the same as the first canvas.
This way you never have to worry about the background and can focus on the animation currently going on.
Now getting back to coding part, the first thing you'll want to do is copy Paul Irish's recursive function:
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
Question then is, how to use it? If you go here you can check out how it was done:
function fireLoop()
{
window.requestAnimationFrame(fireLoop);
fire.update();
fire.render();
console.log('you spin me right round baby right round');
follow();
}
This is the loop I use. Every second Paul Irish's function will call the main loop. In this loop. I update the information choose the right animation that needs to be drawn and then I draw on the canvas (after having removed the previous element).
The follow function is the one that chooses the next coordinates for the animation. You'll have to change this part since, you don't want to move the canvas but move the animation. You can use the same code, but you need to apply location to where you want to draw on the canvas.
function follow()
{
$(fireCanvas).offset({
top: getTop(),
left: getLeft()
});
}
function getTop()
{
var off = $(fireCanvas).offset();
if(off.top != currentMousePos.y - $(fireCanvas).height() + 10)
{
if(off.top > currentMousePos.y - $(fireCanvas).height() + 10)
{
return off.top - 1;
}
else
{
return off.top + 1;
}
}
}
function getLeft()
{
var off = $(fireCanvas).offset();
if(off.left != currentMousePos.x - $(fireCanvas).width()/2)
{
if(off.left > currentMousePos.x - $(fireCanvas).width()/2)
{
return off.left - 1;
}
else
{
return off.left + 1;
}
}
}
var currentMousePos = { x: -1, y: -1 };
$(document).mousemove(function(event) {
currentMousePos.x = event.pageX;
currentMousePos.y = event.pageY;
});
If you want me to go into depth about anything specific let me know.
I have been using RaphealJS to create a vector drawing tool, I have all the drawing completed and working
my issues comes in when I resize the browser window and try to draw the mouse pointer is off from the location that is being drawn.
I use the mouse move event on the browser and draw lines , Like so
$(document).mousemove(function(e){
if (IE) {
var dh = $("#details").height();
var dw = $("#details").width();
xx = e.offsetX;
yy = e.offsetY;
} else {
var offset = $("#workcanvas").offset();
xx = e.pageX - offset.left;
yy = e.pageY - offset.top;
}
if (lineObject != null) {
lineObject.updateEnd(xx, yy);
} else {
lineObject = Line(xx, yy, xx, yy, MasterCanvas);
}
});
I create my canvas and background image
var MasterCanvas = Raphael($("#workcanvas").attr("id"));
var MasterBGImage = MasterCanvas.image(imgPath, 0, 0, $("#workcanvas").width(),$("#workcanvas").height());
MasterCanvas.setViewBox(0, 0, $("#workcanvas").width(), $("#workcanvas").height(), true);
and in my window resize event I tried this
MasterCanvas.setSize($("#workcanvas").width(), $("#workcanvas").height());
Now I have beat my head against this for a few days to no avail. Please note: I can the drawing function work, and as long as the window does not resize every thing is great but when the page resizes the drawing point is off.
Just in case anyone else has this problem, it turns out to be a viewBox problem, I had to calculate the mouse position based on the viewBox coordinates not the screen so my original code becomes:
$(document).mousemove(function(e){
var uupos = MasterCanvas.canvas.createSVGPoint();
uupos.x = e.clientX;
uupos.y = e.clientY;
var ctm = MasterCanvas.canvas.getScreenCTM();
if (ctm = ctm.inverse())
uupos = uupos.matrixTransform(ctm);
x = uupos.x;
y = uupos.y;
if (lineObject != null) {
lineObject.updateEnd(x, y);
} else {
lineObject = Line(x, y, x, y, MasterCanvas);
}
});
Edit:
Looks like this solution is SVG only though and it does not work in IE8 which is a requirement for me - any ideas.
Is there something like viewBox coordinates in VML
I need to have a couple of images on one canvas.
I have a trouble with a function clear(); which is used when I need to drag images on canvas.
The problem is that we are Canvas appears only on the last image.
I try to use context.save() and context.restore() but it was not usefull in my case.
switch(i)
{
case 0:
challengerImg = new Image();
challengerImg.onload = function(){
drawImage(this,x,y,i);
};
challengerImg.src = "<?php echo $base_url; ?>/themes/bartik/images/sheep.png";
break;
case 1:
tshirt = new Image();
tshirt.onload = function(){
drawImage(this,x,y,i);
};
tshirt.src = "<?php echo $base_url; ?>/themes/bartik/images/tshirt.png";
break;
}
And function which draw on canvas:
function drawImage(challengerImg,x,y,i){
console.log("Function drawImage start");
var events = new Events("layer0");
var canvas = events.getCanvas();
var context = events.getContext();
var rectX = x;
var rectY = y;
var draggingRect = false;
var draggingRectOffsetX = 0;
var draggingRectOffsetY = 0;
events.setStage(function(){
var mousePos = this.getMousePos();
if (draggingRect) {
rectX = mousePos.x - draggingRectOffsetX;
rectY = mousePos.y - draggingRectOffsetY;
}
this.clear(); //Here is trouble
this.beginRegion();
context.drawImage(challengerImg, rectX, rectY, challengerImg.width, challengerImg.height);
// draw rectangular region for image
context.beginPath();
context.rect(rectX, rectY, challengerImg.width, challengerImg.height);
context.closePath();
this.addRegionEventListener("mousedown", function(){
draggingRect = true;
var mousePos = events.getMousePos();
draggingRectOffsetX = mousePos.x - rectX;
draggingRectOffsetY = mousePos.y - rectY;
});
this.addRegionEventListener("mouseup", function(){
draggingRect = false;
});
this.addRegionEventListener("mouseover", function(){
document.body.style.cursor = "pointer";
});
this.addRegionEventListener("mouseout", function(){
document.body.style.cursor = "default";
});
this.closeRegion();
});
}
context.save and context.restore only works for state of the context (transformation, globalAlpha,...), but not for what is rendered inside.
When you clear your context, it makes it empty.
What you have to do is to :
catch mouse events and change position variables
clear the canvas
redraw all images at their new position
save(), restore() do not handle bitmap data at all :).
To drag stuff around, basically you need to have an offscreen canvas which doesn't contain the element you are drawing, and after each update draw the offscreen canvas plus the element being dragged.
It's probably simpler to use a library that already does it, most do, like http://www.html5canvastutorials.com/labs/html5-canvas-interactive-scatter-plot-with-20000-nodes-using-kineticjs/
The canvas is a flat object - once you draw an image onto it, the image has no more context as it renders as part of the canvas. You can either create an array of rectangle objects with their coordinates to keep track of what's already on the canvas or test the mouse position for the background color to determine whether there is an image or not. Then you can remove the item where the event happened by clearing the canvas and redrawing all the other items again.
Hope this helps!