Changing Stroke color in HTML 5 Canvas - javascript

I'm trying to draw a circle, kind of a clock, i start at point p1 and draw an arc in black using the canvas 2d context, when i reach the point p1 (complete circle tour) i change color to white, and continue to draw, that should give it an effect like if the black arc is being erased, however this doesn't work as expected, because when i change the context's color, everything change...
how to keep the first circle, in a color, and draw another one on topof it with different color, without changing the color in the whole scene ?
here's my attempt
<!DOCTYPE html />
<html>
<head>
<title></title>
<script type="text/javascript">
var i = 0.01;
var Color = "Black";
var x = 75; // x coordinate
var y = 75; // y coordinate
var radius = 20; // Arc radius
var startAngle = 0; // Starting point on circle
var anticlockwise = false; // clockwise or anticlockwise
function draw() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.beginPath();
var endAngle = i; // End point on circleMath.PI + (Math.PI * 5) /
if (Math.floor(endAngle) >= 6) {
i = 0.01;
if (Color == "Black") {
Color = "White";
} else {
Color = "Black";
}
}
ctx.strokeStyle = Color;
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
ctx.stroke();
i = i + 0.05;
//document.getElementById('display').innerHTML = Math.floor(endAngle) + " " + Color;
}
</script>
</head>
<body onload="window.setInterval(function(){draw()},100);">
<canvas id="canvas" width="150" height="150"></canvas>
<span id="display"></span>
</body>
</html>

You're having a little trouble with your angles. You're essentially redrawing the arc from 0 to your endAngle every time. So at the end when endAngle is greater than 6 you're redrawing from 0-6 with a white arc.
The easy fix is to just set endAngle = 0.01 when you reset i. You may also want to update your startAngle on each iteration to be the end of your last arc, just so that it doesn't draw over itself all the time.
Hope this helps!

Using Shaded's answer, you could do the following:
if (Math.floor(endAngle) > 6.0) {
i = 0.01;
endAngle = i;
startAngle = 0;
if (Color == "Black") {
Color = "White";
ctx.lineWidth = 4;
} else {
Color = "Black";
ctx.lineWidth = 1;
}
}
ctx.strokeStyle = Color;
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
ctx.stroke();
startAngle = endAngle - 0.1;
Because the white will anti-alias with the black behind it, you'll get the jaggies at the edges if the line widths are the same. Increasing the line width alleviates this issue.
EDIT: Updated to remove excessive over-painting as per Shaded's comments.

Related

Why do two lines drawn the same way render differently [duplicate]

I am drawing five horizontal lines to an HMTL 5 2D canvas:
var canvas_ctx = my_canvas.getContext("2d");
canvas_ctx.lineWidth = 0.5;
canvas_ctx.strokeStyle = "black";
{
let line_x = 0;
let line_length = canvas_ctx.width;
let offset = 5;
let numLines = 5;
let numYincrement = 10;
for (let i=0;i<numLines * numYincrement;i+=numYincrement) {
//canvas_ctx.beginPath();
canvas_ctx.moveTo(line_x,i + offset);
canvas_ctx.lineTo(line_length,i + offset);
canvas_ctx.stroke();
//canvas_ctx.closePath();
}
}
This should, ideally result in 5 black lines. Instead, the color of the lines seems to fade with each new line (as if it's a gradient!), so that line 5 is gray. If I uncomment canvas_ctx.beginPath(); and canvas_ctx.closePath();, all lines become gray. Why is this happening??
Strokes do overlap from both sides of the coordinates.
var ctx = c.getContext('2d');
ctx.strokeStyle="red";
// draw big
ctx.scale(30, 30);
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(5, 10);
ctx.stroke();
drawPixelGrid();
function drawPixelGrid() {
// simply renders where the pixel bounds are
ctx.beginPath();
// remove the zoom
ctx.setTransform(1,0,0,1,0,0);
ctx.strokeStyle = 'gray';
ctx.lineWidth = 2; // avoid the problem we are demonstrating by using a perfect lineWidth ;-)
for(let y=0; y<=300; y+=30) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
for(let x=0; x<=300; x+=30) {
ctx.moveTo(x, 0);
ctx.lineTo(x, 300);
}
}
ctx.stroke();
}
<canvas id="c" height=300></canvas>
But obviously, a pixel can't be set to two colors at the same time. So browsers apply antialiasing, which will fade your pixel color to an other color, being the result of mixing the background and the foreground color.
So for a black stroke over a white or transparent background, this leads to actual gray pixels being rendered. Here I'll keep using red as an example:
var ctx = c.getContext('2d');
ctx.strokeStyle="red";
// first draw as on a 10*10 canvas
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(5, 10);
ctx.stroke();
// zoom it
ctx.imageSmoothingEnabled = 0;
ctx.globalCompositeOperation = 'copy';
ctx.drawImage(c, 0,0,9000,9000);
drawPixelGrid();
// this is not red...
function drawPixelGrid() {
ctx.globalCompositeOperation = 'source-over';
ctx.beginPath();
ctx.setTransform(1,0,0,1,0,0);
ctx.strokeStyle = 'gray';
ctx.lineWidth = 2;
for(let y=0; y<=300; y+=30) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
for(let x=0; x<=300; x+=30) {
ctx.moveTo(x, 0);
ctx.lineTo(x, 300);
}
}
ctx.stroke();
}
<canvas id="c" height=300></canvas>
One way to avoid it is generally to apply an offset on your coordinates so that the line extends correctly on pixels boundaries. E.g for a 1px lineWidth, you would apply a 0.5 offset:
var ctx = c.getContext('2d');
ctx.strokeStyle="red";
// first draw as on a 10*10 canvas
ctx.beginPath();
ctx.moveTo(5.5, 0); // offset +0.5px
ctx.lineTo(5.5, 10);
ctx.stroke();
// zoom it
ctx.imageSmoothingEnabled = 0;
ctx.globalCompositeOperation = 'copy';
ctx.drawImage(c, 0,0,9000,9000);
drawPixelGrid();
// now we've got a real red
function drawPixelGrid() {
ctx.globalCompositeOperation = 'source-over';
ctx.beginPath();
ctx.setTransform(1,0,0,1,0,0);
ctx.strokeStyle = 'gray';
ctx.lineWidth = 2;
for(let y=0; y<=300; y+=30) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
for(let x=0; x<=300; x+=30) {
ctx.moveTo(x, 0);
ctx.lineTo(x, 300);
}
}
ctx.stroke();
}
<canvas id="c" height=300></canvas>
But in your case, you are drawing at 0.5px lineWidth, so no offset will be able to get rid of this antialiasing.
So if you want perfect color, choose a correct lineWidth.

Wierd effects with JavaScript gauge

I am making a guage in javascript for a project at work and based on the value of the guage, the whole thing should change color and it does, however the colors go nuts on some values and I can't figure out why this is going on.
Normaly the behaviour should be the following:
Between 0% and 25% - Red
Between 26% and 75% - Yellow
Between 76% and 100% - Green
When the code runs it apparently behaves as it should except...
If it's between 3% and 9% it yellow (should be red)
If it's 100% it's red (should be green)
If it's 0% it's red, but the bar does a 360 degree spin... wut?
I have looked at the code for over 2 hours and I cannot find the logic in why the bugs, I was wondering is someone here might see something I missed.
HTML:
<canvas id="canvas" width="300" height="300">
CSS:
body {
background: #333;
}
/*Centering the gauge*/
#canvas {
display: block;
width: 300px;
margin: 100px auto;
}
/*Custom font for numbers*/
#font-face {
font-family: "bebas";
src: url("http://thecodeplayer.com/uploads/fonts/BebasNeue.otf");
}
JS:
window.onload = function(){
//canvas initialization
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//dimensions
var W = canvas.width;
var H = canvas.height;
//Variables
var red = "25%";
var yellow = "75%";
var green = "100%";
var degrees = 0;
var new_degrees = 724;
var difference = 0;
var color = "lightgreen";
var bgcolor = "#222";
var redcolor = "orangered";
var yellowcolor = "goldenrod";
var greencolor = "lightgreen";
var text;
var animation_loop, redraw_loop;
var startAngle = 1 * Math.PI;
var endAngle = 1.7 * Math.PI;
function init()
{
//Clear the canvas everytime a chart is drawn
ctx.clearRect(0, 0, W, H)
//Background 360 degree arc
ctx.beginPath();
ctx.strokeStyle = bgcolor;
ctx.lineWidth = 30;
ctx.arc(W/2, H/2, 100, startAngle, endAngle, false); //you can see the arc now
ctx.stroke();
//gauge will be a simple arc
//Angle in radians = angle in degrees * PI / 180
var radians = degrees * Math.PI / 1030;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 30;
//The arc starts from the rightmost end. If we deduct 90 degrees from the angles
// CHANGE THIS LINE HERE
//the arc will start from the left
ctx.arc(W/2, H/2, 100, startAngle, radians - 180*Math.PI/180, false);
//you can see the arc now
ctx.stroke();
//Lets add the text
ctx.fillStyle = color;
ctx.font = "50px bebas";
text = Math.floor(degrees/720*100) + "%";
if (text < "25%") {
color = redcolor;
} else if (text > "25%") {
color = yellowcolor;
} else if (text > "75%") {
color = greencolor;
}
//Lets center the text
//deducting half of text width from position x
var text_width = ctx.measureText(text).width;
//adding manual value to position y since the height of the text cannot
//be measured easily. There are hacks but we will keep it manual for now.
ctx.fillText(text, W/2 - text_width/2, H/2 + 15);
}
function draw()
{
//Cancel any movement animation if a new chart is requested
if(typeof animation_loop != undefined) clearInterval(animation_loop);
//random degree from 0 to 360
new_degrees = Math.round(Math.random()*360);
//new_degrees = 721;
difference = new_degrees - degrees;
//This will animate the gauge to new positions
//The animation will take 1 second
//time for each frame is 1sec / difference in degrees
animation_loop = setInterval(animate_to, 1000/difference);
}
//function to make the chart move to new degrees
function animate_to()
{
//clear animation loop if degrees reaches to new_degrees
if(degrees == new_degrees)
clearInterval(animation_loop);
if(degrees < new_degrees)
degrees++;
else
degrees--;
init();
}
//Lets add some animation for fun
draw();
redraw_loop = setInterval(draw, 2000); //Draw a new chart every 2 seconds
}
You can see the code at http://codepen.io/rgaspary/pen/Glfdn
You are using strings instead of numbers. As a result, the comparison is alphanumeric instead of a number comparison.
For example, the string "9%" comes after the string "25%".
I have no idea if synchronosity is the culprit, but...
using setInterval() is generally a bad idea.
Use setTimeout(), and when your draw() function finishes executing, call it recursively with another setTimeout().

HTML 5 Canvas: remove color gradient from line edges

I'm trying to draw a black line on a white-filled canvas. For some reasons, only the inner pixels of that line (lineWidth = 3) are really black. The more I go to the edges of the line, the broghter in steps of the greyscale it gets. If I draw it with lineWith = 1, no pixels are really black.
I wrote a little demonstration of what happens: http://jsfiddle.net/xr5az/
Just for completeness, here is the code again:
<html>
<head>
<script type="text/javascript">
function page_loaded()
{
var cv = document.getElementById("cv");
var ctx = cv.getContext("2d");
ctx.fillStyle = "#ffffff";
ctx.strokeStyle = "#000000";
ctx.lineWidth = 3;
ctx.lineCap = "round";
ctx.lineJoin = "bevel";
ctx.fillRect(0, 0, cv.width, cv.height);
ctx.moveTo(5, 5);
ctx.lineTo(10, 10);
ctx.stroke();
var imgdata = ctx.getImageData(0, 0, cv.width, cv.height);
for (var y = 0; y < cv.height; y ++)
{
for (var x = 0; x < cv.width; x ++)
{
var idx = 4 * (y * cv.width + x);
var c = imgdata.data[idx]<<16 | imgdata.data[idx+1]<<8 | imgdata.data[idx+2];
if (c != 0xffffff)
{
document.getElementById("debug").innerHTML += x+"x, "+y+"y: "+c.toString(16)+"<br/>";
}
}
}
}
</script>
</head>
<body onload="page_loaded()">
<p>
<canvas id="cv" width="15" height="15"></canvas>
</p>
<p>
Found colors:
<div id="debug"></div>
</body>
</html>
Why isn't the line just black and how may I fix this issue?
Thanks a lot!
draw vertically or horizontally and add 0.5 (jsfiddle.net/xr5az/2/) - you will get only black and white colours. (note that i hided line begin and end, it would be grey ...)
ctx.moveTo(-2, 5.5);
ctx.lineTo(17, 5.5);
ctx.stroke();
when you add angle, system adds extra grey colors pixels, without it, line would look bad.
if you want to get line only from black color, draw many separate pixels (or squares if you want lineWidth > 1) between line start and end points.
p.s. sorry for broken fiddle link, system kicked my message even with code included like 10 times ...

Executing function onclick when function is in document.ready

I'm fairly new to Javascript so I was hoping for a bit of help. I've been playing with animating a graph using canvas and javascript. I have it so that when the page loads, it will fill up the graph to a pre-determined percentage.
I also have some buttons that have an onclick on them, and the aim is for clicking these buttons to execute the animating function again but with a different percentage. However, clicking on them doesn't so a thing.
Any help you could give me on this would be great.
Code:
window.onload = function(){
//canvas initialization
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//dimensions
var W = canvas.width;
var H = canvas.height;
//Variables
var degrees = 0;
var new_degrees = 0;
var difference = 0;
var color = "#c70505"; //green looks better to me
var bgcolor = "#222";
var text;
var animation_loop, redraw_loop;
function init()
{
//Clear the canvas everytime a chart is drawn
ctx.clearRect(0, 0, W, H);
//Background 360 degree arc
ctx.beginPath();
ctx.strokeStyle = bgcolor;
ctx.lineWidth = 30;
ctx.arc(W/2, H/2, 100, 0, Math.PI*2, false); //you can see the arc now
ctx.stroke();
//gauge will be a simple arc
//Angle in radians = angle in degrees * PI / 180
var radians = degrees * Math.PI / 180;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 30;
//The arc starts from the rightmost end. If we deduct 90 degrees from the angles
//the arc will start from the topmost end
ctx.arc(W/2, H/2, 100, 0 - 90*Math.PI/180, radians - 90*Math.PI/180, false);
//you can see the arc now
ctx.stroke();
//Lets add the text
ctx.fillStyle = color;
ctx.font = "50px bebas";
text = Math.floor(degrees/360*100) + "%";
//Lets center the text
//deducting half of text width from position x
text_width = ctx.measureText(text).width;
//adding manual value to position y since the height of the text cannot
//be measured easily. There are hacks but we will keep it manual for now.
ctx.fillText(text, W/2 - text_width/2, H/2 + 15);
}
function draw(percent)
{
//Cancel any movement animation if a new chart is requested
if(typeof animation_loop != undefined) clearInterval(animation_loop);
//random degree from 0 to 360
new_degrees = percent
difference = new_degrees - degrees;
//This will animate the gauge to new positions
//The animation will take 1 second
//time for each frame is 1sec / difference in degrees
animation_loop = setInterval(animate_to, 1000/difference);
}
//function to make the chart move to new degrees
function animate_to()
{
//clear animation loop if degrees reaches to new_degrees
if(degrees == new_degrees)
clearInterval(animation_loop);
if(degrees < new_degrees)
degrees++;
else
degrees--;
init();
}
draw(100);
}
<canvas id="canvas" width="300" height="300"></canvas>
<button onclick="draw(360)">100%</button>
<button onclick="draw(270)">75%</button>
The draw function is out of scope as soon as the anonymous function you have assigned to window.onload finishes executing. If you declare the functions in the global scope instead, you'll be able to call them anywhere. Here's a working fiddle.
Try my update:
<script>
//canvas initialization
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//dimensions
var W = canvas.width;
var H = canvas.height;
//Variables
var degrees = 0;
var new_degrees = 0;
var difference = 0;
var color = "#c70505"; //green looks better to me
var bgcolor = "#222";
var text;
var animation_loop, redraw_loop;
function init()
{
//Clear the canvas everytime a chart is drawn
ctx.clearRect(0, 0, W, H);
//Background 360 degree arc
ctx.beginPath();
ctx.strokeStyle = bgcolor;
ctx.lineWidth = 30;
ctx.arc(W/2, H/2, 100, 0, Math.PI*2, false); //you can see the arc now
ctx.stroke();
//gauge will be a simple arc
//Angle in radians = angle in degrees * PI / 180
var radians = degrees * Math.PI / 180;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 30;
//The arc starts from the rightmost end. If we deduct 90 degrees from the angles
//the arc will start from the topmost end
ctx.arc(W/2, H/2, 100, 0 - 90*Math.PI/180, radians - 90*Math.PI/180, false);
//you can see the arc now
ctx.stroke();
//Lets add the text
ctx.fillStyle = color;
ctx.font = "50px bebas";
text = Math.floor(degrees/360*100) + "%";
//Lets center the text
//deducting half of text width from position x
text_width = ctx.measureText(text).width;
//adding manual value to position y since the height of the text cannot
//be measured easily. There are hacks but we will keep it manual for now.
ctx.fillText(text, W/2 - text_width/2, H/2 + 15);
}
function draw(percent)
{
//Cancel any movement animation if a new chart is requested
if(typeof animation_loop != undefined) clearInterval(animation_loop);
//random degree from 0 to 360
new_degrees = percent
difference = new_degrees - degrees;
//This will animate the gauge to new positions
//The animation will take 1 second
//time for each frame is 1sec / difference in degrees
animation_loop = setInterval(animate_to, 1000/difference);
}
//function to make the chart move to new degrees
function animate_to()
{
//clear animation loop if degrees reaches to new_degrees
if(degrees == new_degrees)
clearInterval(animation_loop);
if(degrees < new_degrees)
degrees++;
else
degrees--;
init();
}
draw(100);
</script>
<canvas id="canvas" width="300" height="300"></canvas>
<button onclick="draw(360)">100%</button>
<button onclick="draw(270)">75%</button>

How to animate and erase an arc in HTML5

Here is my question : I have an animation, that builds are circle. See : http://jsfiddle.net/2TUnE/
JavaScript:
var currentEndAngle = 0
var currentStartAngle = 0;
var currentColor = 'black';
setInterval(draw, 50);
function draw() { /***************/
var can = document.getElementById('canvas1'); // GET LE CANVAS
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var startAngle = currentStartAngle * Math.PI;
var endAngle = (currentEndAngle) * Math.PI;
currentEndAngle = currentEndAngle + 0.01;
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
context.lineWidth = 15;
// line color
context.strokeStyle = currentColor;
context.stroke();
/************************************************/
}
When the circle is completely drawn, I would like it to start erasing, the same way it was created (so slowly removes the black). Once the whole circle is erased, i would create the black circle again, creating some kind of "waiting / loading" effect.
What i tried to do, is check if the currentEndAngle is 2 (so the circle is complete), and then move the startAngle, but it didn't work.
Any idea?
Thanks!
EDIT : Forgot to say, the animation is gonna be over an image, so it has to be "transparent" and not white
Look whats up in this JSFiddle: http://jsfiddle.net/fNTsA/
This method is basically your code, only we use a modulo to control state. Checking if the radius is 2 is only half-right, to toggle drawing white or drawing black you should do half the radius modulo 2. The first time around you have floor(0..2/2) % 2 == 0, the second you have floor(2..4/2) % 2 == 1, and so on.
Also because the line is antialiased, it helps to have the start angle overwrite what's been drawn already, otherwise you get extra white lines you probably don't want. For the same reason, when drawing the white circle, you should draw a slightly thicker line (smaller radius, thicker line). Otherwise the antialiasing leaves behind some schmutz -- a faint outline of the erased circle.
I put the radius and width into globals which you'd put at the top:
var lineRadius = 75;
var lineWidth = 15;
And likewise this is my modulo thing, pretty standard:
currentStartAngle = currentEndAngle - 0.01;
currentEndAngle = currentEndAngle + 0.01;
if (Math.floor(currentStartAngle / 2) % 2) {
currentColor = "white";
radius = lineRadius - 1;
width = lineWidth + 3;
} else {
currentColor = "black";
radius = lineRadius;
width = lineWidth;
}
Fun challenge! Try the following (updated fiddle here). I've tried to include plenty of comments to show my thinking.
// Moved these to global scope as you don't want to re-declare
// them in your draw method each time your animation loop runs
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
// Use objects to hold our draw and erase props
var drawProps = {
startAngle: 0,
speed: 2,
color: 'black',
counterClockwise: false,
globalCompositeOperation: context.globalCompositeOperation,
lineWidth: 15
};
var eraseProps = {
startAngle: 360,
speed: -2,
color: 'white',
counterClockwise: true,
globalCompositeOperation: "destination-out",
lineWidth: 17 // artefacts appear unless we increase lineWidth for erase
};
// Let's work in degrees as they're easier for humans to understand
var degrees = 0;
var props = drawProps;
// start the animation loop
setInterval(draw, 50);
function draw() { /***************/
degrees += props.speed;
context.beginPath();
context.arc(
x,
y,
radius,
getRadians(props.startAngle),
getRadians(degrees),
props.counterClockwise
);
context.lineWidth = props.lineWidth;
context.strokeStyle = props.color;
context.stroke();
// Start erasing when we hit 360 degrees
if (degrees >= 360) {
context.closePath();
props = eraseProps;
context.globalCompositeOperation = props.globalCompositeOperation;
}
// Start drawing again when we get back to 0 degrees
if (degrees <= 0) {
canvas.width = canvas.width; // Clear the canvas for better performance (I think)
context.closePath();
props = drawProps;
context.globalCompositeOperation = props.globalCompositeOperation;
}
/************************************************/
}
// Helper method to convert degrees to radians
function getRadians(degrees) {
return degrees * (Math.PI / 180);
}

Categories