More than one clip call per context in canvas - javascript

I cannot seem to get a second clip call working in canvas. See fiddle: http://jsfiddle.net/m2hL17nu/
Notice how the first radial grad is clipped but the second isn't.
I have seen Can you have multiple clipping regions in an HTML Canvas? but save restore still doesn't seem to be letting the next clip() work.
Thanks in advance for your help. See code below:
var x1 = 300,
y1 = 100,
x2 = 50,
y2 = 50,
r = 20;
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
function createRadialGradient (xa, ya, xb, yb, r) {
var grd = context.createRadialGradient(xa, ya, 0, xb, yb, r);
grd.addColorStop(0, 'rgba(0,0,0,1)');
grd.addColorStop(1, 'rgba(0,0,0,0)');
context.fillStyle = grd;
context.fill();
}
context.save();
context.rect(x1-r,y1-r,r,r);
context.clip();
context.rect(0, 0, canvas.width, canvas.height);
createRadialGradient(x1, y1, x1, y1, r);
context.restore();
context.save();
context.rect(x2-r,y2,r,r);
context.strokeStyle = 'black';
context.clip();
context.rect(0, 0, canvas.width, canvas.height);
createRadialGradient(x2, y2, x2, y2, r);
context.stroke();

you should use beginPath() and closePath() before drawing the clips and after the clip() methods respectively:
var x1 = 300,
y1 = 100,
x2 = 50,
y2 = 50,
r = 20;
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
function createRadialGradient (xa, ya, xb, yb, r) {
var grd = context.createRadialGradient(xa, ya, 0, xb, yb, r);
grd.addColorStop(0, 'rgba(0,0,0,1)');
grd.addColorStop(1, 'rgba(0,0,0,0)');
context.fillStyle = grd;
context.fill();
}
context.save();
context.beginPath();
context.rect(x1-r,y1-r,r,r);
context.closePath();
context.clip();
context.rect(0, 0, canvas.width, canvas.height);
createRadialGradient(x1, y1, x1, y1, r);
context.restore();
context.save();
context.beginPath();
context.rect(x2-r,y2,r,r);
context.closePath();
context.clip();
context.strokeStyle = 'black';
context.rect(0, 0, canvas.width, canvas.height);
createRadialGradient(x2, y2, x2, y2, r);
context.stroke();
<canvas id="myCanvas" width="500" height="500"></canvas>

The save() and restore() does not affect the content of the path object itself - only the state of the context (which includes clipping state).
restore() can remove the clip definition but if the path still contains data it will be activated next time you call clip regardless, together with new data added to the path.
To make it work you need to make sure you're working with a clean path. For that simply call beginPath() before defining the clip area with rect(). closePath() is really not necessary with clip() as clip() (and fill()) will close implicit due to it being impossible to clip with an open path.
Then before doing actual drawing call beginPath() again as clipping is now a part of the context state. Finally use restore() to remove the clipping definition (revert state to previous).
context.save(); // store current state to stack
context.beginPath(); // clean path
context.rect(x1-r,y1-r,r,r);
context.clip(); // path is closed; clip is now activated
context.beginPath(); // clean path for your new shape
context.rect(0, 0, canvas.width, canvas.height);
createRadialGradient(x1, y1, x1, y1, r);
...
context.restore(); // removes the clip from state
var ctx = canvas.getContext('2d');
ctx.save();
ctx.beginPath();
ctx.rect(50, 50, 50, 50);
ctx.clip();
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#900';
ctx.fill();
ctx.restore();
ctx.save();
ctx.beginPath();
ctx.rect(150, 80, 80, 60);
ctx.clip();
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#009';
ctx.fill();
ctx.restore();
<canvas id=canvas width=500 height=180></canvas>

Related

html5 canvas drawing partially

I want to create the logo above using the html5 canvas but at the end of the day, it displayed only a triangle.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Coa</title>
</head>
<body>
<canvas id="logo" width="900" height="80">
<h1>COA</h1>
</canvas>
<script>
//function that draw the logo. I create a JavaScript function for drawing the
var drawLogo = function(){
var canvas = document.getElementById('logo');
var context = canvas.getContext("2d");
//applying gradient
var gradient = context.createLinearGradient(0, 0, 0, 40);
gradient.addColorStop(0, "#aa0000");
gradient.addColorStop(1, "#ff0000");
// use the strokepath,beginPath() and closePath() methods to start drawing a line, stroke and close when finish
context.fillStyle = gradient;
context.strokeStyle = gradient;
context.lineWidth = 2;
context.beginPath();
context.moveTo(0, 40);
context.lineTo(30, 0);
context.lineTo(60, 40);
context.lineTo(285, 40);
context.fill();
context.closePath();
//adding text
context.font = "italic 40px 'Arial'";
context.fillText("Coa", 60,36);
//Moving the Origin so as to fit the square under the triangle
context.save();
context.translate(20, 20);
context.fillRect(0, 0, 20, 20);
//use a path to draw the inner triangle
context.fillStyle("#ffffff");
context.strokeStyle("#ffffff");
context.lineWidth = 2;
context.beginPath();
context.moveTo();
context.lineTo(0, 20);
context.lineTo(10, 0);
context.lineTo(20, 20);
context.lineTo(0, 20);
context.fill();
context.closePath();
context.restore();
};
//Then invoke this method after first checking for the existence of the <canvas> element
var canvas = document.getElementById("logo");
if(canvas.getContext){
drawLogo();
}
</script>
</body>
</html>
I don't know what am doing wrong that makes the the code not working properly.
I have searched the internet and couldn't find anything tangible that solve the problem. Any help will be appreciated.
UPDATE: Try this:
if (document.getElementById('logo')) {
var canvas = document.getElementById("logo");
var context = canvas.getContext("2d");
//applying gradient
var gradient = context.createLinearGradient(0, 0, 0, 40);
gradient.addColorStop(0, "#aa0000");
gradient.addColorStop(1, "#ff0000");
context.strokeStyle = gradient;
context.fillStyle = gradient;
context.lineWidth = 2;
context.beginPath();
context.moveTo(0, 40);
context.lineTo(30, 0);
context.lineTo(60, 40);
context.lineTo(285, 40);
context.stroke();
context.fillRect(20, 20, 20, 20);
context.beginPath();
context.moveTo(30, 20);
context.lineTo(37, 28);
context.lineTo(35, 40);
context.lineTo(25, 40);
context.lineTo(23, 28);
context.lineTo(30, 20);
context.fillStyle="#ffffff";
context.fill();
context.fillStyle="#000000";
context.font = "italic 40px Arial";
context.fillText("coa", 60,36);
}
I moved around some of your code and it works now for me on Firefox. It looks like you haven't put the pentagon in the canvas yet. Here is a JSFillde:
https://jsfiddle.net/o289buyk/
if (document.getElementById('logo')) {
var canvas = document.getElementById("logo");
var context = canvas.getContext("2d");
//applying gradient
var gradient = context.createLinearGradient(0, 0, 0, 40);
gradient.addColorStop(0, "#aa0000");
gradient.addColorStop(1, "#ff0000");
// use the strokepath,beginPath() and closePath() methods to start drawing a line, stroke and close when finish
context.fillStyle = gradient;
context.strokeStyle = gradient;
context.lineWidth = 2;
context.beginPath();
context.moveTo(0, 40);
context.lineTo(30, 0);
context.lineTo(60, 40);
context.lineTo(285, 40);
context.fill();
context.closePath();
//adding text
context.font = "italic 40px 'Arial'";
context.fillText("Coa", 60,36);
//Moving the Origin so as to fit the square under the triangle
context.save();
context.translate(20, 20);
context.fillRect(0, 0, 20, 20);
//use a path to draw the inner triangle
context.fillStyle("#ffffff");
context.strokeStyle("#ffffff");
context.lineWidth = 2;
context.beginPath();
context.moveTo();
context.lineTo(0, 20);
context.lineTo(10, 0);
context.lineTo(20, 20);
context.lineTo(0, 20);
context.fill();
context.closePath();
context.restore();
}

Overwrite drawn over canvas when drawing transparent shapes

I can't see that this had been posted already, so here goes.
Let's say i draw 2 squares on the canvas.
var c = document.getElementById('test'), ctx = c.getContext('2d');
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.fill();
This produces this image:
If i change globalAlpha to 0.5, i get this:
However, i want to produce this:
As in, all pixels are transparent and any images under it will appear, but the pixels created by the red triangle will overwrite the existing blue triangle where it is drawn.
And ctx.globalComposisteOperation doesn't seem to help in this instance due to it also factoring the transparency and the fact i want to keep both squares.
Is there any way to do this with current methods?
Use Compositing to clear the red triangle before drawing it.
Using compositing is slightly better than clipping because you don't have to clear the clip. Clearing a clip requires saving the entire context state and then restoring that context state -- many properties involved. Compositing just requires changing 1 property forth and back.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// fill the blue rect
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
// define the red rect path
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
// clear the red rect path using compositing
ctx.globalCompositeOperation='destination-out';
ctx.fillStyle='black';
ctx.fill();
ctx.globalCompositeOperation='source-over';
// fill the red rect
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.fill();
body{ background-color:white; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=512 height=512></canvas>
Layers
Do it the photoshop way and create layers to do the work for you. Creating a layer (second canvas) is no more trouble than loading an image. You can create dozens and have no problem and it makes this type of work easy.
First create a second canvas (layer)
// canvas is original canvas
var layer = document.createElement("canvas");
layer.width = canvas.width; // same size as original
layer.height = canvas.height;
var ctx1 = layer.getContext("2d");
Then draw your triangles on the second canvas with alpha = 1;
var tri = (x,c)=>{
ctx1.fillStyle = c;
ctx1.beginPath();
ctx1.moveTo(25 + x, 0);
ctx1.lineTo(50 + x, 50);
ctx1.lineTo(0 + x, 50);
ctx1.closePath();
ctx1.fill();
}
tri(0,"#00f");
tri(25,"#f00");
Then just draw that layer on top of the canvas you are working on with the alpha value you want.
ctx.globalAlpha = 0.5;
ctx.drawImage(layer,0,0);
If you don't need the extra layer delete the canvas and context by dereferencing them.
ctx1 = undefined;
layer = undefined;
Or you can keep the layer , and make another layer for the background and mix them in real time to get the FX just right
//
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
var layer = document.createElement("canvas");
layer.width = canvas.width; // same size as original
layer.height = canvas.height;
var ctx1 = layer.getContext("2d");
var tri = (x,c)=>{
ctx1.fillStyle = c;
ctx1.beginPath();
ctx1.moveTo(25 + x, 0);
ctx1.lineTo(50 + x, 50);
ctx1.lineTo(0 + x, 50);
ctx1.fill();
}
tri(0,"#00f");
tri(25,"#0f0");
tri(50,"#f00");
ctx.globalAlpha = 0.5;
ctx.drawImage(layer,0,0);
layer = ctx1 = undefined;
You can clear the area for the second rectangle before drawing it.
var c = document.getElementById('game'),
ctx = c.getContext('2d');
ctx.globalAlpha = 0.5;
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, 50, 50);
// Clear the extra bit of the blue rectangle
ctx.clearRect(25, 25, 25, 25);
ctx.fillStyle = "red";
ctx.fillRect(25, 25, 50, 50);
<canvas id="game" width="320" height="240"></canvas>
I would honestly suggest just changing the colors and avoid using an alpha.
var c = document.getElementById('game'),
ctx = c.getContext('2d');
ctx.fillStyle = "#8080FF";
ctx.fillRect(0, 0, 50, 50);
ctx.fillStyle = "#ff8080";
ctx.fillRect(25, 25, 50, 50);
<canvas id="game" width="320" height="240"></canvas>
var c = document.getElementById('test'), ctx = c.getContext('2d');
ctx.save();
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.clip();
ctx.clearRect(0, 0, 100, 100);
ctx.fill();
ctx.restore();
Just use clip() to take the current path, clear everything in there with clearRect, then draw the path as normal.
#markE is right, use compositing instead of clipping (really heavy).
However, his solution will only work if you've drawn everything with an rgba color. Maybe I read your question wrongly, but if you're going from an opaque shape and want to make it transparent, then you should rather use the copy gCO and the globalAlpha property.
This will have less performance impact since drawImage is faster than fill, and will allow you to perform a fade-out effect ; but it really depends on your needs.
var ctx = c.getContext('2d');
// initial blue
ctx.fillStyle = "#0000FF";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
setTimeout(function drawRed() {
ctx.fillStyle = "#FF0000";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.fill();
}, 500);
btn.onclick = function makeItTransparent() {
// if we weere to make this into an animation, we would set it only once
ctx.globalCompositeOperation = 'copy';
ctx.globalAlpha = .8;
// draw the canvas on itself
ctx.drawImage(c, 0, 0);
// once again, in an animation we won't reset this to default
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
};
/* checkboard background */
canvas {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGElEQVQYlWNgYGBowIKxgqGgcJA5h3yFAOI3GQFqqi5ZAAAAAElFTkSuQmCC");
}
<canvas id="c"></canvas>
<button id="btn">make it transparent</button>

How do I clearRect in my JS canvas?

I may be having a Homer moment here but I am trying to clear the canvas at every iteration to show the square rotating but the clearRect doesn't seem to work.
Can anyone see what I am doing wrong?
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var angle = 0;
setInterval(function() {
angle++;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(100, 100);
ctx.rotate(angle);
ctx.rect(0, 0, 30, 30);
ctx.stroke();
ctx.restore();
}, 50);
<canvas width='300' height='300' id='myCanvas'></canvas>
You are missing a beginPath() in the code to clear the path. This will cause all rectangles to be redraw each time (and will eventually slow down the whole process).
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var angle = 0;
setInterval(function() {
angle++;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(100, 100);
ctx.rotate(angle);
ctx.beginPath();
ctx.rect(0, 0, 30, 30);
ctx.stroke();
ctx.restore();
}, 50);
<canvas width='300' height='300' id='myCanvas'></canvas>

add an image instead of an object on canvas to exhibit zoom/scale property

I want to add an image on the canvas and then zoom/Scale it
I am trying to add this image file but unable to do so though I can add square and then zoom the canvas.
https://dl.dropboxusercontent.com/u/139992952/houseIcon.png
how can I add the image.
Fiddle:
http://jsfiddle.net/ndYdk/11/
function draw(scale, translatePos){
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
// clear canvas
context.clearRect(0, 0, canvas.width, canvas.height);
context.save();
context.translate(translatePos.x, translatePos.y);
context.scale(scale, scale);
context.beginPath(); // begin custom shape
context.rect(20, 20, 50, 50);
context.fillStyle = 'red';
context.fill();
context.lineWidth = 2;
context.strokeStyle = 'black';
context.stroke();
context.closePath(); // complete custom shape
var grd = context.createLinearGradient(-59, -100, 81, 100);
context.fillStyle = grd;
context.fill();
context.stroke();
context.restore();
}
Any help will be really appreciated.

Is it possible to use two different strokestyles on same path in canvas?

Is it possible to create a path in canvas of which the lines are stroked differently? For example so you could draw a box being green on the inside, with 4 different colored edges. I've included some code, but that does not work, because it draws the path twice (first time unfinished path, second time the whole path).
JavaScript
window.onload = function()
{
canvas = document.getElementById("canvas1");
if (canvas.getContext)
{
ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.save();
ctx.moveTo(0, 0);
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.lineTo(100, 100);
ctx.stroke();
ctx.strokeStyle = "rgb(0, 0, 255)";
ctx.lineTo(200, 100);
ctx.stroke();
ctx.restore();
}
}
HTML
<canvas id = "canvas1" width = "400" height = "400">
Your browser does not support the HTML5 canvas tag.
</canvas>
I think you'll have to use two paths.
ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100, 100);
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.strokeStyle = "rgb(0, 0, 255)";
ctx.stroke();

Categories

Resources