Context scaling breaks some drawing - html

I'm trying to draw on a virtual canvas of proportions 1x1 so that I don't have to constantly multiply out the actual dimensions.
This works perfectly when I draw circles, but it would appear as though it does not work well with rectangles, and I'm not sure why.
When I have the following code:
var canvas = this.canvas;
var ctx = canvas.getContext("2d");
ctx.scale(this.canvas.width, this.canvas.height);
ctx.beginPath();
ctx.arc(.5, .5, .1, 0, 2*Math.PI);
ctx.fillStyle = "red";
ctx.fill();
It works fine, and the circle scales with the canvas.
However, when I merely change it to the following:
var canvas = this.canvas;
var ctx = canvas.getContext("2d");
ctx.scale(this.canvas.width, this.canvas.height);
ctx.fillStyle = "blue";
ctx.fillRect(0,0, .0001, .001);
ctx.beginPath();
ctx.arc(.5, .5, .1, 0, 2*Math.PI);
ctx.fillStyle = "red";
ctx.fill();
the rectangle takes up the entirety of the screen and even covers up the circle, even though the circle is drawn after the rectangle. It should, obviously, be taking up a very minute amount of space on the canvas.
It might be worth noting that this occurs in a game loop.
However, the following works as expected, with a red circle appearing above a blue backdrop
var canvas = this.canvas;
var ctx = canvas.getContext("2d");
ctx.fillStyle = "blue";
ctx.fillRect(0,0, 50, 50);
ctx.beginPath();
ctx.arc(10, 10, 20, 0, 2*Math.PI);
ctx.fillStyle = "red";
ctx.fill();

If you're calling this repeatedly in a game loop, the scale method will increase the scale of your transform, every time through the loop. So you end up with everything growing. Since you're not showing your actual code, it's hard to tell why the circle isn't affected.
Try calling scale() only once, or use save() and restore(), or just reset the transform at the start of the loop, before calling scale():
ctx.setTransform(1, 0, 0, 1, 0, 0);

Related

html5 canvas text slide in behind nothing

I want to have html5 canvas text slide in behind nothing, its an effect where text appears like coming out behind some obstacle, but the obstacle is invisible.
Here is some youtube video showing how to do it in after effect:
https://www.youtube.com/watch?v=KIYMy7vLLRo
I know how to slide a text along canvas, and one idea I got is having two canvases on top of each other, and top canvas is smaller and contains the text that is initially out of canvas and slides in. But if there is a way to do it with just one canvas that would be great.
You can using a clipping path to mask out part of the text.
Save the existing clipping path using the save() method. Create a shape/path and make it the new clipping path using the clip() method. Draw your text. Store the previous clipping path using the restore() method.
For example, suppose your canvas is 100 pixels by 100 pixels. The following will draw text on only the left side of the canvas.
context.save();
context.rect(0, 0, 50, 100);
context.clip();
context.fillText("Draw some text.", 30, 50);
context.restore();
jsFiddle : https://jsfiddle.net/CanvasCode/vgpov2yk/3
var c = document.getElementById('myCanvas');
var ctx = c.getContext("2d");
// Positions for the text startint off
var xText = -100;
var yText = 150;
// This will update the canvas as fast as possible (not good)
setInterval(function () {
// Clear the canvas
ctx.fillStyle = "#F0F";
ctx.fillRect(0, 0, c.width, c.height);
ctx.save()
// We create a clip rect
ctx.rect(200,0,400,400);
ctx.clip();
// Draw text
ctx.fillStyle = "#FFF";
ctx.font = "30px Arial";
ctx.fillText("Hello World", xText, yText);
ctx.restore();
// Increase the text x position
if (xText < 200) {
xText += 0.5;
}
}, 15);
All you have to do is use a clip rect which is like a mask in image editing.

Using globalCompositeOperation in few phases

I'm drawing many things on my context. Shapes, texts, images..
I want to use achieve the same effect i'm getting using the clip method on the context with globalCompositeOperation (Using the clip is harder for me to perform and i don't know if possible for texts)
The user can draw few shapes. and then start a mask phase. to draw some more shapes, texts.. which would draw into the mask and then the next draw will be clipped in the masked phase. and then continue to regular drawing...
For ex.
The user draw this drawing
Then started masked mode and drew this 2 red lines
Then he stopped drawing into the mask, and start drawing rectangle to consider the mask
And finally applied the mask clipping and the result should look like this
I've managed to clip the rectangle with the lines if there were no earlier drawings.
// Starting the mask phase
ctx.strokeStyle = 'red';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(240, 140);
ctx.moveTo(80, 20);
ctx.lineTo(300, 140);
ctx.stroke();
ctx.globalCompositeOperation = 'source-out';
ctx.fillStyle = 'cyan';
ctx.fillRect(50, 70, 250, 20);
// Setting the composition back
ctx.globalCompositeOperation = 'source-over';
but when i'm adding my drawings in the beginning of the code, the composition considering it as well.
ctx.fillStyle = 'brown';
ctx.beginPath();
ctx.arc(80, 80, 50, 50, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'yellow';
ctx.fillRect(80, 60, 150, 40);
ctx.fillStyle = 'black';
ctx.font = '40pt arial';
ctx.fillText('Hello', 130, 110);
// How to tell the context to start from here the compisition ???
How to tell the context to start composition from a certain point, if possible ?
I could create another canvas and draw the mask there.. and then draw the new canvas on the main canvas. But there is better solution ?
You can change the compositing mode at any point in the drawing flow by changing .globalCompositeOperation. But, as you've discovered, any new compositing mode will also affect existing canvas content.
Your intuition is correct about using a second "staging canvas" to do compositing that won't destroy your existing content.
You can use an in-memory canvas to do compositing and create your rect-with-erased-lines. Then you can drawImage this in-memory canvas to your main canvas. Since the compositing was done on your in-memory canvas, your existing circle-hello content is not undesirably affected by compositing.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// create an in-memory canvas to do compositing
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/temp6a.png";
function start(){
ctx.drawImage(img,0,0);
addCompositedRectangle();
ctx.drawImage(c,0,0);
}
function addCompositedRectangle(){
// resize the in-memory canvas
// Note: this automatically clears any previous content
// and also resets any previous compositing modes
c.width=300; // largest width;
c.height=140; // largest height;
// Starting the mask phase
cctx.strokeStyle = 'red';
cctx.lineWidth = 5;
cctx.beginPath();
cctx.moveTo(20, 20);
cctx.lineTo(240, 140);
cctx.moveTo(80, 20);
cctx.lineTo(300, 140);
cctx.stroke();
cctx.globalCompositeOperation = 'source-out';
cctx.fillStyle = 'cyan';
cctx.fillRect(50, 70, 250, 20);
// Setting the composition back
cctx.globalCompositeOperation = 'source-over';
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>

HTML5 CANVAS Shadow a bug? or a code defect?

I'm faced with the problem about drawing of canvas.
Canvas is saved to data:image once.
And it is made to redraw.
Two problems occur at this time.
A shadow will be applied to the object which has not drawn the shadow.
A shadow will become deep if save and a redraw are repeated.
<canvas id="SAMPLE" width="960" height="480"></canvas>
<script type="text/javascript" src="./jquery.js"></script>
<script>
var canvas = $("#SAMPLE"),
ctx = canvas[0].getContext("2d");
// first object (no shadow)
ctx.strokeStyle = "#0067ef";
ctx.fillStyle = "#0067ef";
ctx.lineCap = "round";
ctx.lineWidth = "15";
ctx.globalAlpha = 1;
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowColor = "#363636";
ctx.beginPath();
ctx.moveTo( 10, 10);
ctx.lineTo(200, 200);
ctx.stroke();
ctx.closePath();
// second object (has shadow)
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowColor = "#363636";
ctx.beginPath();
ctx.moveTo(300, 10);
ctx.lineTo(400, 200);
ctx.stroke();
ctx.closePath();
// Canvas is saved to data:image once.
var save = canvas[0].toDataURL();
// clear canvas
ctx.clearRect(0, 0, 960, 480);
// redraw
var img = new Image();
img.src = save;
img.onload = function() {
ctx.drawImage(this,0,0);
};
</script>
It was normal when the picture at the time of save and before a redraw was investigated.
Two problems occur at the time of a redraw.
A shadow will be applied to the object which has not drawn the shadow.
A shadow will become deep if save and a redraw are repeated.
Does my code have a defect?
Because your fillStyle and other parameters are preserved, so upon drawing image, the effect is stacking up.
Use save() and restore() to prevent this:
ctx.save();
// first object (no shadow)
ctx.strokeStyle = "#0067ef";
ctx.fillStyle = "#0067ef";
/*
...
*/
ctx.closePath();
ctx.restore();
// Canvas is saved to data:image once.
JSFiddle demo with one extra canvas and the appended image for comparison.
Comment out the save() and restore() and re-run the fiddle, you'll notice the different.

Gap between shapes after scaling

While using scale in HTML5 canvas, I noticed that sometimes small gaps appear between elements. For example:
context.scale(0.995, 1);
context.fillRect(0, 0, 100, 100);
context.fillRect(100, 0, 100, 100);
Without scale, two rectangles are close next to each other, but with scale, there's tiny gap between. Is there some way to get rid of it without rounding scale factor?
As said in my comment this is a rendering artefact because of antialiasing. As workaround you may use an off-screen buffer which you render un-scaled and then put that image onto your original canvas with correct scaling turned on. If you do so the line should disappear.
The following snippet might give you an idea:
var buffer = document.createElement('canvas');
buffer.width = 200;
buffer.height = 100;
var context1 = buffer.getContext('2d');
context1.fillRect(0, 0, 100, 100);
context1.fillRect(100, 0, 100, 100);
var canvas = document.getElementById('canvasID');
var context = canvas.getContext('2d');
context.scale(0.995, 1);
context.drawImage(buffer, 0, 0);
context.fillRect(0, 120, 100, 100);
context.fillRect(100, 120, 100, 100);
Compare top two rectangles in my example (off-screen rendering) with the bottom ones which were drawn directly onto the canvas.

How to change the opacity (alpha, transparency) of an element in a canvas element?

Using the HTML5 <canvas> element, I would like to load an image file (PNG, JPEG, etc.), draw it to the canvas completely transparently, and then fade it in. I have figured out how to load the image and draw it to the canvas, but I don't know how to change its opacity.
Here's the code I have so far:
var canvas = document.getElementById('myCanvas');
if (canvas.getContext)
{
var c = canvas.getContext('2d');
c.globalAlpha = 0;
var img = new Image();
img.onload = function() {
c.drawImage(img, 0, 0);
}
img.src = 'image.jpg';
}
Will somebody please point me in the right direction like a property to set or a function to call that will change the opacity?
I am also looking for an answer to this question, (to clarify, I want to be able to draw an image with user defined opacity such as how you can draw shapes with opacity) if you draw with primitive shapes you can set fill and stroke color with alpha to define the transparency. As far as I have concluded right now, this does not seem to affect image drawing.
//works with shapes but not with images
ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
I have concluded that setting the globalCompositeOperation works with images.
//works with images
ctx.globalCompositeOperation = "lighter";
I wonder if there is some kind third way of setting color so that we can tint images and make them transparent easily.
EDIT:
After further digging I have concluded that you can set the transparency of an image by setting the globalAlpha parameter BEFORE you draw the image:
//works with images
ctx.globalAlpha = 0.5
If you want to achieve a fading effect over time you need some kind of loop that changes the alpha value, this is fairly easy, one way to achieve it is the setTimeout function, look that up to create a loop from which you alter the alpha over time.
Some simpler example code for using globalAlpha:
ctx.save();
ctx.globalAlpha = 0.4;
ctx.drawImage(img, x, y);
ctx.restore();
If you need img to be loaded:
var img = new Image();
img.onload = function() {
ctx.save();
ctx.globalAlpha = 0.4;
ctx.drawImage(img, x, y);
ctx.restore()
};
img.src = "http://...";
Notes:
Set the 'src' last, to guarantee that your onload handler is called on all platforms, even if the image is already in the cache.
Wrap changes to stuff like globalAlpha between a save and restore (in fact use them lots), to make sure you don't clobber settings from elsewhere, particularly when bits of drawing code are going to be called from events.
Edit: The answer marked as "correct" is not correct.
It's easy to do. Try this code, swapping out "ie.jpg" with whatever picture you have handy:
<!DOCTYPE HTML>
<html>
<head>
<script>
var canvas;
var context;
var ga = 0.0;
var timerId = 0;
function init()
{
canvas = document.getElementById("myCanvas");
context = canvas.getContext("2d");
timerId = setInterval("fadeIn()", 100);
}
function fadeIn()
{
context.clearRect(0,0, canvas.width,canvas.height);
context.globalAlpha = ga;
var ie = new Image();
ie.onload = function()
{
context.drawImage(ie, 0, 0, 100, 100);
};
ie.src = "ie.jpg";
ga = ga + 0.1;
if (ga > 1.0)
{
goingUp = false;
clearInterval(timerId);
}
}
</script>
</head>
<body onload="init()">
<canvas height="200" width="300" id="myCanvas"></canvas>
</body>
</html>
The key is the globalAlpha property.
Tested with IE 9, FF 5, Safari 5, and Chrome 12 on Win7.
This suggestion is based on pixel manipulation in canvas 2d context.
From MDN:
You can directly manipulate pixel data in canvases at the byte level
To manipulate pixels we'll use two functions here - getImageData and putImageData.
getImageData usage:
var myImageData = context.getImageData(left, top, width, height);
The putImageData syntax:
context.putImageData(myImageData, x, y);
Where context is your canvas 2d context, and x and y are the position on the canvas.
So to get red green blue and alpha values, we'll do the following:
var r = imageData.data[((x*(imageData.width*4)) + (y*4))];
var g = imageData.data[((x*(imageData.width*4)) + (y*4)) + 1];
var b = imageData.data[((x*(imageData.width*4)) + (y*4)) + 2];
var a = imageData.data[((x*(imageData.width*4)) + (y*4)) + 3];
Where x is the horizontal offset, y is the vertical offset.
The code making image half-transparent:
var canvas = document.getElementById('myCanvas');
var c = canvas.getContext('2d');
var img = new Image();
img.onload = function() {
c.drawImage(img, 0, 0);
var ImageData = c.getImageData(0,0,img.width,img.height);
for(var i=0;i<img.height;i++)
for(var j=0;j<img.width;j++)
ImageData.data[((i*(img.width*4)) + (j*4) + 3)] = 127;//opacity = 0.5 [0-255]
c.putImageData(ImageData,0,0);//put image data back
}
img.src = 'image.jpg';
You can make you own "shaders" - see full MDN article here
You can. Transparent canvas can be quickly faded by using destination-out global composite operation. It's not 100% perfect, sometimes it leaves some traces but it could be tweaked, depending what's needed (i.e. use 'source-over' and fill it with white color with alpha at 0.13, then fade to prepare the canvas).
// Fill canvas using 'destination-out' and alpha at 0.05
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = "rgba(255, 255, 255, 0.05)";
ctx.beginPath();
ctx.fillRect(0, 0, width, height);
ctx.fill();
// Set the default mode.
ctx.globalCompositeOperation = 'source-over';
I think this answers the question best, it actually changes the alpha value of something that has been drawn already. Maybe this wasn't part of the api when this question was asked.
Given 2d context c.
function reduceAlpha(x, y, w, h, dA) {
let screenData = c.getImageData(x, y, w, h);
for(let i = 3; i < screenData.data.length; i+=4){
screenData.data[i] -= dA; //delta-Alpha
}
c.putImageData(screenData, x, y );
}
Set global Alpha draw the object that has opacity then set back to normal.
//////////////////////// circle ///////////////////////
ctx.globalAlpha = 0.75;
ctx.beginPath();
ctx.arc(x1, y1, r1, 0, Math.PI*2);
ctx.fillStyle = colour;
ctx.fill();
ctx.closePath();
ctx.globalAlpha = 1;
How i made it..on canvas i first draw rect in a selfrun function 0,0,canvas.width,canvas.height as a background of canvas and i set globalAlpha to 1 .then i draw other shapes in ather own functions and set their globalAlpha to 0.whatever number they dont affect each other even images.
Like Ian said, use c.globalAlpha = 0.5 to set the opacity, type up the rest of the settings for the square, then follow up with c.save();. This will save the settings for the square then you can c.rect and c.fillStyle the square how you want it. I chose not to wrap it with c.restore afterwards and it worked well
If you use jCanvas library you can use opacity property when drawing. If you need fade effect on top of that, simply redraw with different values.
You can't. It's immediate mode graphics. But you can sort of simulate it by drawing a rectangle over it in the background color with an opacity.
If the image is over something other than a constant color, then it gets quite a bit trickier. You should be able to use the pixel manipulation methods in this case. Just save the area before drawing the image, and then blend that back on top with an opacity afterwards.