using globalCompositeOperation with lines? - html

I'm trying to make a clock with HTML 5's canvas element.
What i'm trying to do is make a line for every second, and then erase the previous line.
I want to erase the previous line with drawing another line using the globalCompositeOperation='xor'; but it doesn't work!
Here is the code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Clock</title>
</head>
<body onload="spin()">
<canvas id="myCanvas" width="578" height="400"></canvas>
<script>
var firstTime = 0;
var prevX=null;
var prevY=null;
function spin() {
//get the canvas element
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
//get the right angle for the clock hand
var date = new Date;
var seconds = date.getSeconds();
var a = seconds*6;
var angleRadian = a*Math.PI/180;
var angle = 1/2*Math.PI - angleRadian;
if(a > 360)
a = 0;
//Erase the previous line, if it has been drawn.
if(prevX!=null)
erasePrevLine(angle, canvas, context);
//draw line
drawLine(angle, 100, canvas, context);
//repeat for the next second
setTimeout(spin, 500);
}
function drawLine(angle, radius, canvas, context) {
var centerX = canvas.width/2;
var centerY = canvas.height/2;
var xTarget = centerX + Math.cos(angle)*radius;
var yTarget = centerY - Math.sin(angle)*radius;
//save this state to be erased
prevX = xTarget;
prevY = yTarget;
//draw
context.beginPath();
context.moveTo(centerX,centerY);
context.lineTo(xTarget, yTarget);
context.stroke();
}
function erasePrevLine(angle, canvas, context) {
context.globalCompositeOperation = 'xor';
var centerX = canvas.width/2;
var centerY = canvas.height/2;
prevAngle = angle + (Math.PI/180*6);
var xTarget = prevX;
var yTarget = prevY;
//draw on the previous line
context.beginPath();
context.moveTo(centerX,centerY);
context.lineTo(xTarget, yTarget );
context.stroke();
}
</script>
</body>
</html>
And here is the live example: http://jsfiddle.net/pyerT/1/
Anybody knows the answer? it works fine with shapes and texts..

globalCompositeOperation (xor) will not work on a rotating line like a “clock hand”….here’s why:
Assume you draw a vertical line. Then draw a second vertical line to the right of the first. Assume the second vertical line overlaps the first line by half.
Canvas.globalCompositeOperation=”xor” causes overlapping areas to be removed, so the second line removes half the first line and also half of itself.
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/e24KU/
function drawLine(){
ctx.globalCompositeOperation="xor";
ctx.strokeStyle="red";
ctx.lineWidth=10;
ctx.beginPath();
ctx.moveTo(posX,10);
ctx.lineTo(posX,100);
ctx.stroke();
posX+=5;
}
This is a Fiddle of a “clock hand” sweeping around a centerpoint: http://jsfiddle.net/m1erickson/hW2EY/
However, if we try to use “xor” on a rotating line, the lines overlay at an angle and therefore the xor is incomplete.
Here is a Fiddle showing the line “xor” being ineffective when rotated: http://jsfiddle.net/m1erickson/f7hHx/
[Edited: new code was supplied by OP allowing for an expanded answer]
I looked at your new code and am suggesting some changes and optimizations.
As I said in my original answer, you cannot effectively erase a line that was drawn at an angle. This is due to anti-aliasing that the browser does automatically—antiAliasing for canvas cannot be turned off.
Here is a Fiddle of the results after the changes: http://jsfiddle.net/m1erickson/9QD65/
Changes:
Believe it or not: It is usual to completely erase and completely redraw the canvas during each animation loop! Canvas really is quick enough to handle these redraws—especially now that canvas is GPU accelerated. If you absolutely need to optimize your performance, you can define “dirty” areas of the canvas that must be erased/redrawn and leave the other areas as previously drawn. In practice, once you need this type of performance, your canvas is so complicated that it’s more efficient to completely clear/redraw than to try to define the dirty areas.
Optimizations:
Moved canvas,context,centerX,centerY out of the animation loop since these values can be computed once and reused.
Here is my suggested code for you to look at:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Clock</title>
</head>
<body onload="spin()">
<canvas id="myCanvas" width="578" height="400"></canvas>
<script>
var firstTime = 0;
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var centerX=canvas.width/2;
var centerY=canvas.height/2;
var canvasWidth=canvas.width;
var canvasHeight=canvas.height;
function spin() {
//get the right angle for the clock hand
var date = new Date;
var seconds = date.getSeconds();
var a = seconds*6;
var angleRadian = a*Math.PI/180;
var angle = 1/2*Math.PI - angleRadian;
if(a > 360)
a = 0;
//draw line
drawLine(angle, 100, canvas, context, "black",1);
//repeat for the next second
setTimeout(spin, 500);
}
function drawLine(angle, radius, canvas, context) {
var xTarget = centerX + Math.cos(angle)*radius;
var yTarget = centerY - Math.sin(angle)*radius;
//clear the canvas
context.clearRect(0,0,canvasWidth,canvasHeight);
//draw
context.save();
context.beginPath();
context.moveTo(centerX,centerY);
context.lineTo(xTarget, yTarget);
context.stroke();
context.restore()
}
</script>
</body>
</html>

Related

HTML Canvas, How do you create a circle at the position of the mouse when clicked and then for the circle to increase in radius?

So, I have tried attempting this myself and have searched heavily online and I can't seem to solve this particular issue. I am attempting to make a very simple effect that looks like a very basic water ripple. I intend for the user to be able to click somewhere on the canvas, and for an empty circle (with a black stroke) to appear where the mouse has clicked (starting at a radius of zero), and continuously expand the radius as an animation.
I currently have this code:
<!DOCTYPE html>
<html>
<head>
<!-- Search Engine Optimisation (SEO) -->
<title> Ripple </title>
<meta description="Codelab assignment 3">
<meta keywords="Uni, assignment, ripple, interactive, discovery">
<!-- End of Metadata -->
<!-- Links -->
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<canvas id="myCanvas" width="1024" height="768" style="border: 1px solid"></canvas>
</body>
<script type="text/javascript">
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var radius = 0;
//Have a rectangle fill the canvas and add a hit region
//Call the ripple function from the rectangle function
//Track mouse position in rectangle
function ripple(e) {
// ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.beginPath();
ctx.arc(e.clientX,e.clientY,radius,0,2*Math.PI);
//ctx.closePath();
ctx.stokeStyle = "black";
ctx.stroke();
radius++;
requestAnimationFrame(ripple);
}
canvas.addEventListener('mousedown', ripple);
</script>
</html>
This is what it currently does:
Screenshot
I really appreciate any help!
You'd have to pass the mouse event when calling the ripple function through requestAnimationFrame.
also, you'll need to set the radius to 0 and clear running animation frame (if any) on mouse click
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var radius = 0;
var rAF;
function ripple(e) {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.beginPath();
ctx.arc(e.offsetX, e.offsetY, radius, 0, 2 * Math.PI);
ctx.stokeStyle = "black";
ctx.stroke();
radius++;
rAF = requestAnimationFrame(function() {
ripple(e);
});
}
canvas.addEventListener('mousedown', function(e) {
if (rAF) cancelAnimationFrame(rAF);
radius = 0;
ripple(e);
});
body{margin:10px 0 0 0;overflow:hidden}canvas{border:1px solid #ccc}
<canvas id="canvas" width="635" height="208"></canvas>
note: use e.offsetX and e.offsetY to get proper mouse coordinates relative to canvas.

HTML5-canvas. My drawing isn't smooth. I've tried everything

var canvas;
var context;
var isDrawing = false;
window.onload = function()
{
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
canvas.width=window.innerWidth;
canvas.height=window.innerHeight;
canvas.addEventListener("mousemove", putPoint);
canvas.addEventListener("mousedown", engage);
canvas.addEventListener("mouseup", disengage);
canvas.addEventListener("mouseout", disengage);
context.lineWidth = 2*radius;
context.lineJoin = context.lineCap = 'round';
};
var radius=0.5;
var engage = function(e)
{
isDrawing = true;
putPoint(e)
}
var disengage = function()
{
isDrawing = false;
context.beginPath();
}
var putPoint=function (e)
{
if(isDrawing)
{
context.lineTo(e.clientX-canvas.offsetLeft, e.clientY-canvas.offsetTop);
context.stroke();
context.beginPath();
context.arc(e.clientX-canvas.offsetLeft, e.clientY-canvas.offsetTop, radius, 0, Math.PI*2);
context.fill();
context.beginPath();
context.moveTo(e.clientX-canvas.offsetLeft, e.clientY-canvas.offsetTop);
}
}
body
{
margin: 2px;;
}
canvas
{
border: 1px solid black;
display:block;
}
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title> Drawing app </title>
<link href="style.css" rel="stylesheet">
<script src="script2.js"> </script>
</head>
<body>
<canvas id="canvas" width="1900" height="1000"> Your browser doesn't support canvas. </canvas>
</body>
</html>
This is my code. Please try draw something. You should see a lot of pixels.
It looks awful. I would like to line looked like this:https://sketch.io/sketchpad/. Please set there 1px and check it. It's beautiful, smooth line without pixels. I would like to achieve it.
Here is nice drawing: http://codepen.io/kangax/pen/zofsp.
But:
- Every time you draw a new path - > canvas is cleaning. It is caused by 26th line of code.
- When I delete it ( I mean 26th line of code ) drawing breaks :c
What I've tried?
-shadows
-gradients
-ctx.translate(0.5,0.5);
-bezier curve
And it doesen't work.
I suppose that beautiful lines I can obtain using this: ctx.clearRect(x1,y1,x2,y2).
But I have no ideas how to use it :c
I am going to create drawing app so I need smooth lines.
Mouse data is always a but rough and there are many ways that you can smooth it. The holy grail for line smoothing is a bezier fit, but I have yet to work out how to do that in real time.
The next best thing is optimising and smoothing the line to an appox fit..
Rather than write the answer out again see the following anwser https://stackoverflow.com/a/33882382/3877726
It provides a way to set the optimization and smoothing from none (sometimes you want the bumps and lumps) all the way to super smooth..

is mouse in user drawn area on canvas

Basically, a user uploads a picture and then can paint on it, and save the result. Another user can then view the photo and if they click in the same area as painted, something happens.
So user 1 can make an area click-able for user 2 by drawing on the photo.
now the upload bit is fine, and painting with help from a tutorial and example I've got sussed out. But defining what area is click-able is a bit harder. For something like a rectangle its easy enough, I made an example.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var button = new Object();
button.x = 50;
button.y = 50;
button.width = 50;
button.height = 50;
button.rgb = "rgb(0, 0, 255)";
function drawbutton(buttonobject)
{
context.fillStyle = buttonobject.rgb;
context.fillRect (buttonobject.x, buttonobject.y, buttonobject.width, buttonobject.height);
context.strokeRect(buttonobject.x, buttonobject.y, buttonobject.width, buttonobject.height);
}
drawbutton(button);
function checkIfInsideButtonCoordinates(buttonObj, mouseX, mouseY)
{
if(((mouseX > buttonObj.x) && (mouseX < (buttonObj.x + buttonObj.width))) && ((mouseY > buttonObj.y) && (mouseY < (buttonObj.y + buttonObj.height))))
return true;
else
return false;
}
$("#myCanvas").click(function(eventObject) {
mouseX = eventObject.pageX - this.offsetLeft;
mouseY = eventObject.pageY - this.offsetTop;
if(checkIfInsideButtonCoordinates(button, mouseX, mouseY))
{
button.rgb = "rgb(0, 255, 0)";
drawbutton(button);
} else {
button.rgb = "rgb(255, 0, 0)";
drawbutton(button);
}
});
but when it comes to other shapes like circles, or just someone smothering the page, how would you go about detecting that ?
one thought I had was using the edited layer, making it hidden, and detecting a pixel color of say blue, from here but that limits the color use of the photo and im not entirely sure how to implement it. any other ideas ?
EDIT:
I figured out circles after some tinkering, using Pythagoras theorem to see if mouse coordinates are smaller than the radius, but this assumes circle center of 0,0, so then offset mouse by circles actual center. example
function checkIfInsideButtonCoordinates(buttonObj, mouseX, mouseY) {
actualX = mouseX - buttonObj.x
actualY = mouseY - buttonObj.y
mousesqX = actualX * actualX
mousesqY = actualY * actualY
sqR = buttonObj.r * buttonObj.r
sqC = mousesqX + mousesqY
if (sqC < sqR) return true;
else return false;
}
Here’s how to test whether user#2 is inside user#1’s paintings
Create a second canvas used to hit-test whether user#2 is inside of user#1’s paintings.
The hit-test canvas is the same size as the drawing canvas, but it only contains user#1’s paintings…not the image.
When user#1 is painting, also draw their paintings on the hit canvas.
When user#1 is done painting, save all their paintings from the hit canvas.
You have at least 2 ways to save user#1’s paintings from the hit canvas:
Serialize all the canvas commands needed to recreate the shapes/paths that user#1 paints.
Save the hit canvas as an image using canvas.toDataURL.
When user#2 clicks, check if the corresponding pixel on the hit canvas is filled or is transparent (alpha>0).
// getImageData for the hit-test canvas (this canvas just contains user#1's paintings)
imageDataData=hitCtx.getImageData(0,0,hit.width,hit.height).data;
// look at the pixel under user#2's mouse
// return true if that pixel is filled (not transparent)
function isHit(x,y){
var pixPos=(x+y*hitWidth)*4+3;
return( imageDataData[pixPos]>10)
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/etA5a/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:15px; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var hit=document.getElementById("hit");
var hitCtx=hit.getContext("2d");
var user2=document.getElementById("user2");
var ctx2=user2.getContext("2d");
var canvasOffset=$("#user2").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var imageDataData;
var hitWidth=hit.width;
var img=document.createElement("img");
img.onload=function(){
// left canvas: image+user#1 paintings
ctx.globalAlpha=.25;
ctx.drawImage(img,0,0);
ctx.globalAlpha=1.00;
scribble(ctx,"black");
// mid canvas: just user#1 paintings (used for hittests)
scribble(hitCtx,"black");
// right canvas: user#2
ctx2.drawImage(img,0,0);
imageDataData=hitCtx.getImageData(0,0,hit.width,hit.height).data;
}
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/colorhouse.png";
function scribble(context,color){
context.beginPath();
context.moveTo(70,2);
context.lineTo(139,41);
context.lineTo(70,41);
context.closePath();
context.rect(39,54,22,30);
context.arc(73,115,3,0,Math.PI*2,false);
context.fillStyle=color;
context.fill();
}
function handleMouseMove(e){
var mouseX=parseInt(e.clientX-offsetX);
var mouseY=parseInt(e.clientY-offsetY);
// If user#2 has a hit on user#1's painting, mid-canvas turns red
var color="black";
if(isHit(mouseX,mouseY)){ color="red"; }
scribble(hitCtx,color);
}
function isHit(x,y){
var pixPos=(x+y*hitWidth)*4+3;
return( imageDataData[pixPos]>10)
}
$("#user2").mousemove(function(e){handleMouseMove(e);});
}); // end $(function(){});
</script>
</head>
<body>
<p>Left: original image with user#1 painting</p>
<p>Mid: user#1 painting only (used for hit-testing)</p>
<p>Right: user#2 (move mouse over hit areas)</p>
<canvas id="canvas" width=140 height=140></canvas>
<canvas id="hit" width=140 height=140></canvas>
<canvas id="user2" width=140 height=140></canvas><br>
</body>
</html>

Mouse Coordinates in HTML5 Canvas

I have tried many different ways of trying to get mouse coordinates in HTML5 canvas in compliment with video and none have seemed too work very well in either Chrome or Safari.
At the moment I am using:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded,false);
var videoElement;
var VideoDiv;
var Object1;
var Mouse = {
x:0
x:y}
function eventWindowLoaded(){
videoElement = document.createElement("video");
videoDiv = document.createElement('div');
document.body.appendChild(videoDiv);
videoDiv.appendChild(videoElement);
videoDiv.setAttribute("style", "display:none;");
var videoType = supportedVideoFormat(videoElement);
if (videoType == ""){
alert("no video support");
return;
}
videoElement.setAttribute("src", "different_movement>" + videoType);
videoElement.addEventListener("canplaythrough", videoLoaded, false);
}
function supportedVideoFormat(video){
var returnExtension= "";
if(video.canPlayType("video/webm") =="probably" || video.canPlayType("video/webm") == "maybe"){
returnExtension = "webm";
} else if (video.canPlayType("video/mp4") == "probably" || video.canPlayType("video/mp4") == "maybe"){
returnExtension = "mp4";
}else if(video.canPlayType("video/ogg") == "probably" || video.canPlayType("video/ogg") == "maybe"){
returnExtension = "ogv";
}
return returnExtension;
}
function videoLoaded(event){
canvasApp();
}
canvasOne.onmousemove = function (event){
Mouse={
x: event.offsetX,
y: event.offsetY}
}
}
function canvasApp(){
function drawScreen(){
context.drawImage(videoElement, 0, 0);
context.fillStyle = '#ffffff';
context.fillText(Mouse.x, 280, 280);
context.fillText(Mouse.y, 280, 300);
}
var theCanvas = document.getElementByID('canvasOne');
var context = theCanvas.getContext('2d');
videoElement.play();
setinterval(drawScreen, 33);
}
</script>
</head>
<body>
<canvas id="canvasOne" width="640" height="480">
Your browser does not support HTML5 Canvas.
</canvas>
</div>
</body>
</html>
The result of this is the 0,0 will be shown on the video from the initial variable set at 0,0 but then instead of changing as the mouse is moved around the screen, it stays 0,0. This leads me to believe that it is the part of the code that is finding the mouse coordinates that is not working.
I have tried various other attempts at finding mouse coordinates including:
Mouse={
x: event.pageX,
y: event.pageX}
,
if (e.pageY) {
posy = e.pageY;
} else if (e.clientY) {
posy = e.clientY + document.body.scrollTop
+ document.documentElement.scrollTop;
}
,
var mouseX;
var mouseY;
var pieceX;
var pieceY;
if (e.pageX || e.pageY) {
mouseX = e.pageX;
mouseX = e.pageY;
} else {
mouseX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
mouseY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
My end product is supposed to be a video that has mouse interactions that will play sounds when certain parts on the video are clicked (thus the part of video). I have tried not using canvas at all for this, and instead positioning a image on top of the canvas which has image mapping on it, but it doesn't seem to work.
Another issue I am going to run into when I figure out mouse coordinates is what I will test collisions with the mouse coordinates to initiate it to play the sounds.
EDIT:
Completely rewrote the code using e.offset, seems to work.
I used <iframe> to set an html page with a canvas element positioned top left of document. Then when I get clientX-Y it's origin is the top left of the canvas document, that's in the iframe, that you can have positioned anywhere on the canvas-containing document. It's easy as pie.
<iframe scrolling="no" height="100%" width="100%" src="canvas.html"></iframe>
also, I got the canvas to scale when it is scaled by style sheet. I added this to my canvas program.
c = canvas element, ctx = canvas context;
ctx.scale(c.width/630,c.height/800); // I originally intended it to be 630x800
(note: I am not sure if this answers your problem, but it is how I find coordinates without having to offset.)

HTML5 Canvas, shadowColor & shadowBlur

My question is regarding this shadowBlur feature used on the 2nd (outer) rectangle below. The shadowBlur feature is applied to every shape after this rectangle. (If you comment out the shadowColor and shadowBlur lines 21 & 22, and then uncomment the shadowColor and shadowBlur lines on lines 14 & 15, you should see what I mean.) My question is, how do I apply shadowBlur to one specific portion of the Canvas drawing without applying the feature to every succeeding portion of the Canvas. In this example I have tried creating separate variable for each canvas and context, but the problem still persists.
Attribution: These examples are based on examples from html5canvastutorials.com
<!DOCTYPE html>
<html lang="en"
<head>
<script type="text/javascript">
function addRect(){
var canvas1=document.getElementById("myCanvas");
var ctx=canvas1.getContext("2d");
var canvas3=document.getElementById("myCanvas");
var ctx3=canvas3.getContext("2d");
ctx.rect(60,60,180,80);
ctx.fillStyle="green";
//ctx.shadowColor="black";
//ctx.shadowBlur = 10;
ctx.fill();
ctx3.lineWidth = 3;
ctx3.strokeStyle='red';
ctx3.shadowColor="black";
ctx3.shadowBlur = 10;
ctx3.strokeRect(45,45,210,110);
}
function addOval(){
var canvas2=document.getElementById("myCanvas");
var context=canvas2.getContext("2d");
// define center of oval
var centerX = 288;
var centerY = 250;
// define size of oval
var height = 100;
var width = 250;
var controlRectWidth = width * 1.33;
context.beginPath();
context.moveTo(centerX,centerY - height/2);
// draw left side of oval
context.bezierCurveTo(centerX-controlRectWidth/2,centerY-height/2,
centerX-controlRectWidth/2,centerY+height/2,
centerX,centerY+height/2);
// draw right side of oval
context.bezierCurveTo(centerX+controlRectWidth/2,centerY+height/2,
centerX+controlRectWidth/2,centerY-height/2,
centerX,centerY-height/2);
context.fillStyle="red";
context.fill();
context.lineWidth=5;
context.strokeStyle="blue";
context.stroke();
context.closePath();
}
</script>
</head>
<body onload="addRect(); addOval();">
<canvas id="myCanvas" width="700" height="400">
Your browser does not support the canvas element.
</canvas>
</body>
</html>
Use either this:
ctx.save();
ctx.shadowColor="black";
ctx.shadowBlur = 10;
ctx.strokeRect(45,45,210,110);
ctx.restore();
Or this:
ctx.shadowColor="black";
ctx.shadowBlur = 10;
ctx.strokeRect(45,45,210,110);
ctx.shadowColor= undefined;
ctx.shadowBlur = undefined;
I am not sure about 'undefined' in second case - something to nullify/reset the value.
var canvas3=document.getElementById("myCanvas");
var ctx3=canvas1.getContext("2d");
change canvas1 to canvas3 in the second line. Your ctx3 is actually pointing to canvas1 which i think is wrong.
You can also consider setting the shadow color to "transparent" instead of undefined or null. This also seems to do the trick.