Overwrite drawn over canvas when drawing transparent shapes - javascript

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>

Related

How can I get the "outline" of a Path stroke, and get the points to create a filled Path shape?

I want to retrieve the corresponding points to a 'outlinestroke' and save it as a Shape, instead of a "path with a stroke"
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineWidth = 12;
ctx.lineCap = 'round'
ctx.quadraticCurveTo(20, 100, 200, 20);
ctx.stroke();
</script>
</body>
</html>
This is the result of the code:
But I want to have the Outline of this stroke, and turn it into a Path and give it a stroke.
The fill should be transparent.
And only have a small outline.
Is there a way to "trace or convert" the stroke to a outline path to get the following result:
And if this is not possible:
Before drawing, to use the given points to define the shape of a path.
Here is what I tried:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var width = 12;
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineWidth = width;
ctx.quadraticCurveTo(20, 100, 200, 20);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 1;
ctx.fillStyle = "#ccc";
ctx.moveTo(20-width/2, 270);
ctx.quadraticCurveTo(20-width/2, 350+width/2, 200-width/2, 270+width/2);
ctx.lineTo(200-width/2, 270-width/2);
ctx.quadraticCurveTo(20+width/2, 350-width/2, 20+width/2, 270-width/2);
ctx.lineTo(20-width/2, 270);
ctx.fillStyle = "#999";
ctx.fill();
ctx.stroke();
ctx.closePath();
Which results in the following:
There are no API features to turn a strokes outline into a path.
You can however use a composite operation to create the inner transparency.
Example
Creating outline using globalCompositeOperation = "destination-out";
The gradient is just to show it is transparent.
const OUTLINE_WIDTH = 1; // in pixels
var ctx = canvas.getContext("2d");
ctx.lineWidth = 22;
ctx.lineCap = 'round'
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.quadraticCurveTo(20, 100, 200, 20);
ctx.stroke();
ctx.globalCompositeOperation = "destination-out";
ctx.lineWidth = 22 - OUTLINE_WIDTH * 2;
ctx.stroke();
ctx.globalCompositeOperation = "source-over"; // restore default
canvas {
border:1px solid #aaa;
background: linear-gradient(90deg, rgba(180,255,224,1) 0%, rgba(169,169,255,1) 100%);
}
<canvas id="canvas"></canvas>

How do I clear ONLY rectangle borders? (JS , Canvas)

I'm trying to clear only the strokeRect() and keep content in that rectangle.
In this example, how do I clear green rectangle without affecting the red one?
var cut = [50, 70, 100, 100]
var cut1 = [60, 85, 60, 60]
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.rect(cut[0], cut[1], cut[2], cut[3]);
ctx.lineWidth = 3;
ctx.strokeStyle = 'green';
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.rect(cut1[0], cut1[1], cut1[2], cut1[3]);
ctx.lineWidth = 3;
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.closePath();
<canvas id="canvas" width=400px height=200px></canvas>
Can't figure out how..
Thanks in advance!
You need to clear the canvas and draw the red rectangle again.
var c = document.getElementById("canvas");
c.width=400;
c.height=200;
var ctx = c.getContext("2d");
var cut = [50, 70, 100, 100]
var cut1 = [60, 85, 60, 60]
function drawRectangle(cut,fill){
ctx.beginPath();
ctx.rect(cut[0], cut[1], cut[2], cut[3]);
ctx.lineWidth = 3;
ctx.strokeStyle = fill;
ctx.stroke();
ctx.closePath();
}
drawRectangle(cut,"green");
drawRectangle(cut1,"red");
clear.addEventListener("click",()=>{
ctx.clearRect(0,0, c.width, c.height);
drawRectangle(cut1,"red");
});
canvas{display:block;}
<button id="clear">clear</button>
<canvas id="canvas" width=400px height=200px></canvas>
In your case, clear & redraw is what you really need, but just for completeness, to clear in any shape, you can use compositing operations.
By setting the compositing mode to destination-out, all the pixels that should normally have been filled by your drawings will actually get rendered as transparent.
So to make a simple clear-stroke-rect:
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(20, 20, 80, 80);
// clear
ctx.globalCompositeOperation = 'destination-out';
ctx.lineWidth = 5;
ctx.strokeRect(40,40,30,30);
// set back to 'normal'
ctx.globalCompositeOperation = 'source-over';
<canvas id="canvas"></canvas>
But using compositing, you can really clear in any shape, even from bitmap with transparency:
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = function() {
ctx.fillStyle = 'red';
ctx.fillRect(20, 20, 80, 80);
// clear
ctx.globalCompositeOperation = 'destination-out';
ctx.drawImage(img, 40,40);
// set back to 'normal'
ctx.globalCompositeOperation = 'source-over';
};
img.src = 'https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png';
<canvas id="canvas"></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();

Getting single shadow for fill and stroke on HTML canvas

I have to draw a stroked fill on canvas. For this I call ctx.fill and ctx.stroke separately. Due to this, the shadow of the stroke draws on top of the fill which I want to avoid.
Could somebody please tell if there is a way to avoid this?
Here is my code:
ctx1.fillStyle = "blue";
ctx1.strokeStyle = "black";
ctx1.shadowColor = "rgba(0,255,0, 1)";
ctx1.shadowOffsetX = 50;
ctx1.shadowOffsetY = 50;
ctx1.lineWidth = "20";
ctx.beginPath();
ctx.moveTo(300, 100);
ctx.lineTo(400, 100);
ctx.lineTo(400, 200);
ctx.lineTo(300, 200);
ctx.closePath();
ctx1.fill();
ctx1.stroke();
http://jsfiddle.net/abWYZ/3/
Every time you perform a draw action on the context the shadow is also drawn. The way canvas works is every thing that's drawn is placed on top of what was previously there. So whats happening is the fill is performed, making a shadow of it, and then the stroke is drawn, which makes a shadow on top of all previous drawn objects.
Here is one possible solution.
Live Demo
// Grab the Canvas and Drawing Context
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
ctx.save();
ctx.fillStyle = "blue";
ctx.strokeStyle = "black";
ctx.lineWidth="20";
ctx.beginPath();
ctx.moveTo(300, 100);
ctx.lineTo(400, 100);
ctx.lineTo(400, 200);
ctx.lineTo(300, 200);
ctx.closePath();
ctx.shadowColor = "rgba(0,255,0, 1)";
ctx.shadowOffsetX = 50;
ctx.shadowOffsetY = 50;
ctx.stroke();
ctx.fill();
// clear the shadow
ctx.shadowColor = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// restroke w/o the shadow
ctx.stroke();
If you use an approach like this I suggest making a function called toggleShadow or something along those lines allowing you to control when the shadows are drawn.

Categories

Resources