HTML5 CANVAS Shadow a bug? or a code defect? - html

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.

Related

How to fill colour in Line using ctx html?

Please check this link
https://www.jovianarchive.com/content/charts/628003987200000000_.png. i am creating same chart but all the lines and geometrical shapes are dynamic. in line there is colour with combination of balck and red (Look like stripe) want the same.
My code
ctx.fillStyle = 'blue';
ctx.fill();
ctx.strokeStyle = 'blue';
ctx.stroke();
You need create a Path before call stroke method:
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
ctx.fillStyle = 'blue';
ctx.strokeStyle = 'blue';
//point 0, 0
ctx.moveTo(0, 0);
//to point 300, 150
ctx.lineTo(300, 150);
ctx.closePath();
ctx.stroke();
ctx.fill();
You can change the line width and add shadow. Look in my project:
https://codepen.io/Luis4raujo/pen/GRNEPXO
If this comments help u, vote in my answer!
You might want to consider to create a pattern. You can create it from an image or from an off screen canvas. Here's a quick demo of the second way: https://codesandbox.io/s/awesome-khorana-ystxp?file=/index.html
More info about pattern creation in html canvas: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createPattern

HTML5 canvas apply color to image where shape overlays

I have this image drawn to a HTML5 canvas:
What I want to do is apply color to just a part of it.
The part where I want to apply color is defined by the following overlay image:
So, basically, I would like to guide my coloring by the overlay. So where the overlay pixels meets the main image pixels I should apply a color on the main image. At least that's how I see it working.
Notice that the overlay matches the whole image except for the lacing.
The catch is that I would like to retain the main image texture while applying the color. You can see that it has a leather texture and a "real" feel which I want to keep.
Can you please show me some methods of achieving this or share some thoughts?
Thank you!
globalCompositeOperation is your friend here.
Basically, you draw your overlay, then you set the gCO to 'source-atop' composite mode, which will make all your future drawings to only stay where there were already opaque pixels drawn, so it is important that your overlay has transparent parts.
So then you just fill a rectangle of your desired command, and finally you draw your original image, either behind, or blended to the new shape we just created.
var ctx = canvas.getContext('2d');
var loaded = 0;
function onload(){
if(++loaded === 2){
canvas.width = this.width;
canvas.height = this.height;
ctx.font = "40px sans-serif";
draw();
}
}
var original = new Image();
var overlay = new Image();
original.onload = overlay.onload = onload;
original.src = 'https://i.stack.imgur.com/vIKpI.png';
overlay.src = 'https://i.stack.imgur.com/10Tre.png';
// list of blending modes.
// Note that destination-over is a composite mode,
// which place the new drawings behind the already-there ones
var currentMode = 0;
var modes = ['destination-over', 'lighter', 'multiply', 'screen', 'overlay', 'darken',
'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light',
'exclusion', 'hue', 'saturation', 'color', 'luminosity' ];
function draw(){
// switch between different Blending modes
var mode = modes[currentMode];
currentMode = (currentMode+1)%(modes.length);
// clear previous
ctx.clearRect(0,0,canvas.width, canvas.height);
// draw our overlay
ctx.drawImage(overlay, 0,0);
// this will keep new drawings only where we already have existing pixels
ctx.globalCompositeOperation = 'source-atop';
ctx.fillStyle = 'red';
ctx.fillRect(0,0,canvas.width, canvas.height);
// now choose between the list of blending modes
ctx.globalCompositeOperation = mode;
// draw our original image
ctx.drawImage(original, 0,0);
// go back to default
ctx.globalCompositeOperation = 'source-over';
// just so we can know which one is shown
ctx.fillStyle = 'black';
ctx.fillText(mode, 40,40)
// do it again
setTimeout(draw, 1000)
}
canvas{
width: 100%;
}
<canvas id="canvas"></canvas>

HTML5 Canvas image resize on Chrome & easeljs

I'm struggling to make smooth image resized in canvas in Chrome. In firefox it works well, but in Chrome I'm stuck on making it smooth.
Here is the jsfiddle
http://jsfiddle.net/flashmandv/oxtrypmy/
var AVATAR_SIZE = 100;
var WHITE_BORDER_SIZE = 3;
var stage = new createjs.Stage("canvas");
var avCont = new createjs.Container();
stage.addChild(avCont);
avCont.x = avCont.y = 20;
//add white circle
var whiteBorderCircle = new createjs.Shape();
var radius = (AVATAR_SIZE+WHITE_BORDER_SIZE*2)/2;
whiteBorderCircle.graphics.beginFill("white").drawCircle(radius, radius, radius);
avCont.addChild(whiteBorderCircle);
//add avatar image mask
var avatarMask = new createjs.Shape();
avatarMask.graphics.beginFill("red").drawCircle(AVATAR_SIZE/2+WHITE_BORDER_SIZE, AVATAR_SIZE/2+WHITE_BORDER_SIZE, AVATAR_SIZE/2);
//add avatar image
var image = new Image();
image.onload = function(){
var bitmap = new createjs.Bitmap(image);
bitmap.mask = avatarMask;
var bounds = bitmap.getBounds();
bitmap.scaleX = (AVATAR_SIZE+WHITE_BORDER_SIZE*2) / bounds.width;
bitmap.scaleY = (AVATAR_SIZE+WHITE_BORDER_SIZE*2) / bounds.height;
avCont.addChild(bitmap);
stage.update();
};
image.src = 'http://files.sharenator.com/sunflowers-s800x800-423444.jpg';
Notice the jagged image
Please help
It is due to how clipping works in Chrome. Clip masks are pretty brutal in Chrome while in Firefox you get anti-aliasing along the non-straight edges.
Here is a proof-of-concept for this (run this in Chrome and in FF to see the difference):
http://jsfiddle.net/r65fcqoy/
The only way to get around this is to use composite modes instead, which basically means you need to rewrite your code unless the library you're using support this in some way.
One use of a composite mode is to use it to fill anything inside an existing drawing on the canvas.
We'll first create the filled circle we want the image to appear inside
Change comp mode to source-in and draw image
Then we go back to normal comp mode and draw the outer border
Here is an approach using vanilla JavaScript where you can control how you plug things together - this is maybe not what you're after but there is really not much option if the library as said doesn't support comp mode instead of clipping:
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
img = new Image,
x = 70, y =70;
var AVATAR_SIZE = 100;
var WHITE_BORDER_SIZE = 3;
var radius = (AVATAR_SIZE+WHITE_BORDER_SIZE*2)/2;
img.onload = function() {
// first draw the circle for the inner image:
ctx.arc(x, y, AVATAR_SIZE*0.5, 0 , 2*Math.PI);
ctx.fill();
// now, change composite mode:
ctx.globalCompositeOperation = 'source-in';
// draw image in top
ctx.drawImage(img, x-AVATAR_SIZE*0.5, y-AVATAR_SIZE*0.5,
AVATAR_SIZE, AVATAR_SIZE);
// change back composite mode to default:
ctx.globalCompositeOperation = 'source-over';
// now draw border
ctx.beginPath();
ctx.arc(x, y, radius + 5, 0, 2*Math.PI);
ctx.closePath();
ctx.lineWidth = 10;
ctx.strokeStyle = '#ffa94e';
ctx.stroke();
};
img.src = 'http://i.stack.imgur.com/PB8lN.jpg';
<canvas id=canvas width=500 height=180></canvas>
Another solution to this would be in onload function to add another shape above the masked image to simply cover the jagged edges of the clipping mask

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>

Context scaling breaks some drawing

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);