I have a canvas.
In this canvas, I must draw a grid with red rectangles :
-firstly, I draw vertical rectangles,
-then, I draw horizontal rectangles
Every rectangle have the same opacity (0.3).
Normally, the color in intersection of 2 rectangles must be more red because of the superposition.
So the render must be like this :
But my code doesn't work because the color in intersection isn't more red, the color is the same than a rectangle (you can try it : https://jsfiddle.net/6urj27ua/) :
<canvas id="canvas"></canvas>
<script type="text/javascript">
//The canvas :
c = document.getElementById("canvas");
c.style.border = "solid #000000 1px";
//Size of canvas :
c.width = 300;
c.height = 300;
//The canvas context :
ctx = c.getContext("2d");
//Drawing function :
function draw()
{
//Clear the drawing :
ctx.clearRect(0, 0, c.width, c.height);
/*Define size of a rect :*/
width_rect = 20;
height_rect = 200;
/*Fill color for rect :*/
ctx.fillStyle = "rgba(255, 0, 0, 0.3)";
/*Draw 5 vertical rectangles :*/
for(i = 0; i <= 5 ; i++)
{
ctx.rect(i*(width_rect*2), 0, width_rect, height_rect);
}
/*Draw 5 horizontal rectangles :*/
for(i = 0; i <= 5 ; i++)
{
ctx.rect(0, i*(width_rect*2), height_rect, width_rect);
}
ctx.fill();
}
//Draw :
setInterval("draw()", 300);
</script>
So what's the problem ?
You are almost there. But by using ctx.rect() and ctx.fill(), the whole shape is drawn at once and no 'superposition' is applied.
You can easily fix it by:
replacing ctx.rect() calls with ctx.fillRect()
removing the ctx.fill() which becomes irrelevant
Here is a fixed JSFiddle.
Alternate method
You could also use two distinct paths, but you'd need to clearly circumscribe them with the .beginPath() method: like this.
Related
i made 2 deference size html canvas to drawing
First canvas = width : 400px,height:200px
Second canvas = width : 200px,height :100px
Now when i drawing in first html canvas i send that coordinates(x1,y1,x2,y2) to second canvas.
When first canvas coordinates send in second canvas it's not drawing in same place as first canvas.
is there way to equal first canvas coordinates to second one without changing canvas width and height .
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.strokeStyle = "red";
ctx.moveTo(coord.x, coord.y);
ctx.lineTo(ncoord.x , ncoord.y);
ctx.stroke();
//SECOUND CANVAS
ctx2.beginPath();
ctx2.lineWidth = 5;
ctx2.lineCap = 'round';
ctx2.strokeStyle = "red";
ctx2.moveTo(coord.x, coord.y);
ctx2.lineTo(ncoord.x , ncoord.y);
ctx2.stroke();
when user drwaing in canvas 1 i send that coordinates to both canvas. but in second canvas not drawing in same place as canvas 1.
Note : canvas 1 and 2 have deferent width and height.
I need to slove this without changing width height of the both canvas.
I hope I have made the right assumptions to answer your question. I created two different canvases of two different sizes. The coordinates only fit on the first, bigger, canvas.
You can transform the 'big' coordinates to 'small' coordinates by dividing the width or height of the bigger smaller canvases by the bigger canvases.
For example, the height of the big canvas is 200 but the height of the smaller one is 100. If you divide 100 / 200 you get 0.5. The 'small' coordinates should be half as high as the original ones. See for yourself below:
//just for testing purposes
var coord = {
x: 320,
y: 125
};
var ncoord = {
x: 220,
y: 90
};
function drawBig() {
var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.strokeStyle = "red";
ctx.moveTo(coord.x, coord.y);
ctx.lineTo(ncoord.x, ncoord.y);
ctx.stroke();
}
function drawSmall() {
let bigCanvas = document.getElementById("canvas1");
let smallCanvas = document.getElementById("canvas2");
//Devide the dimensions of the big and small canvas in order to get the magnification factor:
let widthDimension = smallCanvas.width / bigCanvas.width;
let heightDimension = smallCanvas.height / bigCanvas.height
var ctx2 = smallCanvas.getContext("2d");
ctx2.beginPath();
ctx2.lineWidth = 5;
ctx2.lineCap = 'round';
ctx2.strokeStyle = "red";
//Transform the original coordinates to the right dimensions:
ctx2.moveTo(coord.x * widthDimension, coord.y * heightDimension);
ctx2.lineTo(ncoord.x * widthDimension, ncoord.y * heightDimension);
ctx2.stroke();
}
canvas {
border: 1px solid black;
}
<canvas id="canvas1" width="400" height="200"></canvas>
<hr>
<canvas id="canvas2" width="200" height="100"></canvas>
<button onclick="drawBig()">draw big canvas</button>
<button onclick="drawSmall()">draw small canvas</button>
Hope this helps! If not, please comment
There are thousands of moving particles on an HTML5 canvas, and my goal is to draw a short fading trail behind each one. A nice and fast way to do this is to not completely clear the canvas each frame, but overlay it with semi-transparent color. Here is an example with just one particle:
var canvas = document.getElementById('display');
var ctx = canvas.getContext('2d');
var displayHeight = canvas.height;
var backgroundColor = '#000000';
var overlayOpacity = 0.05;
var testParticle = {
pos: 0,
size: 3
};
function render(ctx, particle) {
ctx.globalAlpha = overlayOpacity;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1.0;
ctx.fillStyle = '#FFF';
ctx.fillRect(particle.pos, displayHeight / 2, particle.size, particle.size);
}
function update(particle) {
particle.pos += 1;
}
// Fill with initial color
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
function mainLoop() {
update(testParticle);
render(ctx, testParticle);
requestAnimationFrame(mainLoop);
}
mainLoop();
<canvas id="display" width="320" height="240"></canvas>
There is an apparent problem: with low opacity values, the trail never fades away completely. You can see the horizontal line that (almost) does not fade in my single-particle example. I understand why this happens. ColorA overlayed by semi-transparent ColorB is basically a linear interpolation, and ColorA never fully converges to ColorB if we repeatedly do the following:
ColorA = lerp(ColorA, ColorB, opacityOfB)
My question is, what can I do to make it converge to the background color, so that trails don't remain there forever? Using WebGL or drawing trails manually are not valid options (because of compatibility and performance reasons respectively). One possibility is to loop over all canvas pixels and manually set pixels with low brightness to background color, although it may get expensive for large canvases. I wonder if there are better solutions.
As a workaround which could work in some cases is to set the overlayOpacity up to 0.1 (this value converges) but draw it only every x times and not in every render call.
So when drawn only every other time it keeps more or less the same trail length.
var renderCount = 0;
var overlayOpacity = 0.1;
function render(ctx, particle) {
if((renderCount++)%2 == 0) {
ctx.globalAlpha = overlayOpacity;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.globalAlpha = 1.0;
ctx.fillStyle = '#FFF';
ctx.fillRect(particle.pos, displayHeight / 2, particle.size, particle.size);
}
Obviously the disadvantage is that it looks more jerked and perhaps this may not be acceptable in your case.
Best solution is to use the composite operation "destination-out" and fade to a transparent background. Works well for fade rates down to globalAlpha = 0.01 and event a little lower 0.006 but it can be troublesome below that. Then if you need even slower fade just doe the fade every 2nd or 3rd frame.
ctx.globalAlpha = 0.01; // fade rate
ctx.globalCompositeOperation = "destination-out" // fade out destination pixels
ctx.fillRect(0,0,w,h)
ctx.globalCompositeOperation = "source-over"
ctx.globalAlpha = 1; // reset alpha
If you want a coloured background you will need to render the animation on an offscreen canvas and render it over the onscreen canvas each frame. Or make the canvas background the colour you want.
If someone struggles with this, here is a workaround that worked for me:
// Do this instead of ctx.fillStyle some alpha value and ctx.fillRect
if(Math.random() > 0.8){
ctx.fillStyle = 'rgba(255, 255, 255, '+getRandomNumber(0.1,0.001)+')';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// Define this helper function somewhere in your code
function getRandomNumber(minValue, maxValue) {
return Math.random() * (maxValue - minValue) + minValue;
}
It also works for different colored backgrounds. Adjust trail length by playing around with Math.random() > 0.8 and getRandomNumber(0.1,0.001).
I have two canvas and a select button.
The select button is for changing the scale in the 2nd canvas.
The content of the 1rst canvas is copyed in the 2nd canvas.
When I increase the scale with the select button, the 2nd canvas is resized and scaled perfectly, but his render is bad (the rectangle and the text are blured).
What is the problem ?
Here the source code (you can try it https://jsfiddle.net/0kqqnkmp/) :
<canvas id="canvas"></canvas>
<canvas id="canvas_second"></canvas>
<br>Choose your scale : <select onchange="change_scale(this);" autocomplete="off">
<option>0.5</option>
<option selected>1</option>
<option>1.5</option>
<option>2</option>
</select>
<script type="text/javascript">
//The canvas :
c = document.getElementById("canvas");
c.style.border = "solid #000000 1px";
//The second canvas :
c_second = document.getElementById("canvas_second");
c_second.style.border = "solid #000000 1px";
//Define the original width and height canvas :
ORIGINAL_WIDTH_CANVAS = 300;
ORIGINAL_HEIGHT_CANVAS = 300;
c.width = ORIGINAL_WIDTH_CANVAS;
c.height = ORIGINAL_HEIGHT_CANVAS;
c_second.width = ORIGINAL_WIDTH_CANVAS;
c_second.height = ORIGINAL_HEIGHT_CANVAS;
//The canvas context :
ctx = c.getContext("2d");
ctx_second = c_second.getContext("2d");
//Default scaling
scale = 1;
//Drawing function :
function draw()
{
//Clear the drawing :
ctx.clearRect(0, 0, ORIGINAL_WIDTH_CANVAS, ORIGINAL_HEIGHT_CANVAS);
//Drawing a red rectangle :
ctx.fillStyle = "#000000";
ctx.fillRect(5, 5, 50, 50);
//Drawing a text :
ctx.font = "normal bold 20px sans-serif";
ctx.fillText("Hello world", ORIGINAL_WIDTH_CANVAS-220, ORIGINAL_HEIGHT_CANVAS-10);
//Clear the drawing on the second canvas :
ctx_second.clearRect(0, 0, ORIGINAL_WIDTH_CANVAS, ORIGINAL_HEIGHT_CANVAS);
//Copy drawing on the second canvas :
ctx_second.drawImage(c, 0, 0);
}
//Function for scaling the second canvas :
function change_scale(this_select)
{
//Retrieve the scale value :
scale = parseFloat(this_select.value);
//Resize the second canvas :
c_second.width = ORIGINAL_WIDTH_CANVAS * scale;
c_second.height = ORIGINAL_HEIGHT_CANVAS * scale;
//Apply scaling on the second canvas :
ctx_second.scale(scale, scale);
}
//Draw :
setInterval("draw()", 300);
</script>
Your blurry results are to be expected when you scale up an image.
A canvas is effectively a bitmap image. And a bitmap image becomes blurry when scaled up.
So when you scale & draw your bitmap-canvas#1 onto canvas#2 you will get a blurry result.
The fix is to scale(2,2) canvas#2 and then reissue the same commands that drew your rectangle & text onto the first canvas.
The nice bit is that scale will automatically take care of changing your [x,y] coordinates when redrawing. So you use the exact same [x,y] coordinates that you used to draw into canvas#1.
// scale the second canvas
secondContext.scale(2,2);
//Drawing a red rectangle :
secondContext.fillStyle = "#000000";
secondContext.fillRect(5, 5, 50, 50);
//Drawing a text :
secondContext.font = "normal bold 20px sans-serif";
secondContext.fillText("Hello world", ORIGINAL_WIDTH_CANVAS-220, ORIGINAL_HEIGHT_CANVAS-10);
Trying to wrap my head around the HTML5 canvas, I thought I'd create an image carousel, where images would be changed by an opacity gradient sweep, i.e. the same thing as in my fiddle here, only with canvas. I managed to come up with this fiddle, but I can't understand at all what's happening, or rather, why nothing is.
Here's the code:
var outputCanvas = document.getElementById('output'),
ctx = outputCanvas.getContext('2d'),
eWidth = 50,
speed = 5,
cWidth = 480,
img = document.getElementById('newimg'),
x = 0, y = 0,
reqAnimFrame = window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame;
ctx.drawImage(img, 0, 0, img.width, img.height);
ctx.globalCompositeOperation = "destination-out";
function draw() {
console.log(x);
gradient = ctx.createLinearGradient(x, 0, x+eWidth, 0);
gradient.addColorStop(0, "rgba(255, 255, 255, 0)");
gradient.addColorStop(1, "rgba(255, 255, 255, 1)");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, img.width, img.height);
}
function animate() {
if (x < 480) {
x += Math.floor((cWidth / 1000) * speed);
console.log(x);
draw();
reqAnimFrame(animate);
}
}
reqAnimFrame(animate);
Calling the draw function by itself it seems to work, but once I start firing it with RequestAnimationFrame it just stops working. The gradient gets drawn once, but even though x is updated in the animation loop, the gradient stays put.
I guess there's something I'm just not getting about how canvas and RequestAnimationFrame work.
Note that I'm not looking for a script or library that does the same thing, but rather I'm hoping to actually understand how canvas works, and in particular why my script doesn't.
Here's one way to do a wipe transition between 2 images using Canvas Compositing:
Original Images (before & after):
Canvas during gradient wipe-transition between the images:
create a transparent-to-opaque gradient that is eWidth pixels wide.
clear the canvas
draw the gradient
fill all pixels to the right of the gradient with opaque
draw the first image with source-in compositing. This will display the first image only where the gradient has non-transparent pixels.
draw the second image with 'destination-over' compositing. This will display the second image "under" the existing first image.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw,ch;
var x=0;
var eWidth=100;
var img1=new Image();
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/sailboat.png";
function start(){
cw=canvas.width=img.width;
ch=canvas.height=img.height;
img1.onload=function(){
requestAnimationFrame(animate);
};
img1.src="https://dl.dropboxusercontent.com/u/139992952/multple/sailboat1.png";
}
function draw() {
// create gradient
gradient = ctx.createLinearGradient(x-eWidth,0, x,0);
gradient.addColorStop(0, "rgba(0,0,0, 0)");
gradient.addColorStop(1, "rgba(0,0,0, 1)");
// save the unaltered canvas context
ctx.save();
// clear the canvas
ctx.clearRect(0,0,cw,ch);
// gradient zone
ctx.fillStyle = gradient;
ctx.fillRect(x-eWidth,0,eWidth,ch);
// fully original right of x
ctx.fillStyle='black';
ctx.fillRect(x,0,cw,ch);
// original image with gradient "dissolve" on left
// set compositing to source-in
ctx.globalCompositeOperation='source-in';
ctx.drawImage(img,0,0);
// revealed image
ctx.globalCompositeOperation='destination-over';
ctx.drawImage(img1,0,0);
// restore the context to its unaltered state
ctx.restore();
}
function animate() {
if (x<cw+eWidth){ requestAnimationFrame(animate); }
x+=5;
draw();
}
$('#again').click(function(){
x=0;
requestAnimationFrame(animate);
});
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Wipe transition between images using canvas</h4>
<button id=again>Again</button><br><br>
<canvas id="canvas" width=300 height=300></canvas>
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 ...