Why use context.save and context.restore()? - javascript

Why is it need to use context.save and context.restore() in any Javascript that is drawing lines to a canvas?
this.context = this.canvas.getContext("2d");
I am certain that it has to do with this line of code this.canvas.getContext("2d"); and canvas was defined above.
this.canvas = document.getElementById(config.canvasId);
Code:
BarChart.prototype.drawGridlines = function(){
var context = this.context;
context.save();
context.strokeStyle = this.gridColor;
context.lineWidth = 2;
// draw y axis grid lines
for (var n = 0; n < this.numGridLines; n++) {
var y = (n * this.height / this.numGridLines) + this.y;
context.beginPath();
context.moveTo(this.x, y);
context.lineTo(this.x + this.width, y);
context.stroke();
}
context.restore();
};

context.save() saves the current state of the context (strokeStyle, lineWidth, etc...) then your code will change those values. At the end of your code you call context.restore() which will restore the previous values that your code has changed.
This way you don't need to manually restore everything you changed and your code will not affect other code on the page that has previously modified these values.

You don't. context.save and context.restore are often used for efficient texture translation and rotation, hence show up in many examples.
Save stores your position, angle and various other canvas states. Then you can move to the position/angle you want to draw your image. Restore can then snap your co-ord grid back to the save point. Much easier than rotating points in space mathematically, and the only way to rotate images i know of with canvas 2d context mode.
If your doing simple drawing of boxes, lines or unrotated images then don't bother.

Related

Is there a way we can use an image as a stroke or fill style like we can with gradients?

Using a gradient instead of a color to draw on Canvas is easy:
let gradient0 = ctx.createLinearGradient(x, topY, x, bottomY)
// add color stops
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.lineWidth = 3;
ctx.strokeStyle = gradient0;
ctx.stroke();
ctx.restore();
I'm hoping there's a feature in HTML5 Canvas to do this easily so I dont have to go writing shaders for it. Googling it didnt turn anything up. So is there a way we can use an image as a stroke or fill style like we can with gradients?
Googling it didnt turn anything up.
If you googled the strokeStyle property you'ld see that you can use a CanvasPattern object to use an Image for a Stroke:
const imageSource = /* must be a DOM "Image-like" object, e.g. HTMLImageElement, SVGElement, HTMLVideoElement, HTMLCanvasElemment, Blob, ImageData - or another existing ImageBitmap which will be cloned */;
// * See https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#dom-createimagebitmap-dev
// * `createImageBitmap` returns a Promise<ImageBitmap> so you must await it before you can use it with `createPattern`.
const sourceImage = await ctx.createImageBitmap( imageSource ); // : ImageBitmap
// Cache this pattern somewhere so you don't recreate it every time you stroke a single path - otherwise that would be very silly.
const pattern = ctx.createPattern( sourceImage, 'repeat-x' ); // : CanvasPattern
ctx.beginPath();
ctx.moveTo( x, topY );
ctx.lineTo( x, bottomY );
ctx.lineWidth = 3;
ctx.strokeStyle = pattern;
ctx.stroke();
ctx.restore();
Note that if you use an OffscreenCanvas instead of an ImageBitmap for the sourceImage used by createPattern then you can apply arbitrary transformations (e.g. affine transformations) to the sourceImage if you want to scale, rotate, change colours, etc of the image being used as the pattern.
Apparently the CanvasRenderingContext2D.createPattern function also accepts other "Image-like" objects, just like createImageBitmap does, though I'm unsure how that affects performance (it is interesting that createImageBitmap returns a Promise but createPattern does not).

Canvas radial gradient vs .png performance

I'm trying to simulate brushes with the HTML canvas element. To get brush hardness, I'm using a radial gradient, but I'm not entirely sure whether it's faster to create a new radial gradient for every point or saving the radial gradient as an image and use drawImage() instead.
Current code:
var gradient = context.createRadialGradient(x, y, hardness * brushSize, x, y, brushSize);
gradient.addColorStop(0, color);
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
context.fillStyle = gradient;
context.fillRect(x - halfBrushSize, y - halfBrushSize, brushSize, brushSize);
drawImage (apart from creating the image):
context.drawImage(img, x, y);
Gradients are expensive to generate contrary to images which are basically copies. They both need to go through transformation matrix and anti-aliasing process though, but there is no calculation involved with images besides from that.
UPDATE From the comments below people seem to get extremely variable test results depending on browser and hardware. The embedded test is not very accurate and was meant as a pointer, so for this reason I created a more accurate test here. Feel free to post results below in comments.
-- update end --
The following is not the world's most accurate test, but the difference is so large that you get a pretty good pointer in any case to which is faster:
window.performance = window.performance || Date;
setTimeout(go, 250);
function go() {
var ctx = c.getContext("2d");
// create radial gradient
var gr = ctx.createRadialGradient(300, 300, 300, 300, 300, 0);
gr.addColorStop(0, "#000");
gr.addColorStop(1, "#f00");
ctx.fillStyle = gr;
// test gradient fill style
var time1 = performance.now();
for (var i = 1000; i--;) ctx.fillRect(0, 0, c.width, c.height);
var time2 = performance.now() - time1;
o.innerHTML = "Gradient: " + time2.toFixed(4) + "<br>";
// test cached gradient (canvas = image source)
ctx = c2.getContext("2d");
time1 = performance.now();
for (i = 1000; i--;) ctx.drawImage(c, 0, 0);
time2 = performance.now() - time1;
o.innerHTML += "drawImage: " + time2.toFixed(4);
}
<output id=o>Running... please wait</output><br>
<canvas id=c width=600 height=600></canvas><br>
<canvas id=c2 width=600 height=600></canvas>
When it comes to render a radial gradient, you can build the gradient on-the-fly, or use a png as you quoted, yet there's a third possibility : you can use a normalised gradient, that you build once, then use at will at any place/size by using the context transforms.
The code used to create the normalized gradient for a given hardness looks like :
var mySingleGradient = ctx.createRadialGradient(0.5, 0.5, 0.5*hardness, 0.5, 0.5, 0.5);
mySingleGradient.addColorStop(0, color);
mySingleGradient.addColorStop(1, '#000');
Just like when you are using png, you'll run into the issue of caching the gradients for any base color + hardness. But you won't have any png resolution issue, and most probably the size of the gradients will be way smaller than the png's.
You use such a normalised gradient with :
function drawThatGradientHere(ctx, x, y, gradient, brushSize) {
ctx.save();
ctx.translate(x,y);
ctx.scale(brushSize,brushSize);
ctx.fillStyle = gradient;
ctx.fillRect(0,0,1,1);
ctx.restore();
}
I won't go into benchmarking, since there are too many chances to compare apples and oranges without knowing more about the use. Because for instance, the drawImage might very well perform very differently if you are using its scaled version. Mind also that by using an image, you might run into resolution issues (too high : perf, too low : aliasing), that you won't have if you are using a gradient. So even if the gradient was proved slower, you might prefer it because of the way it consistently looks.
A few questions : do you change your hardness often ? do you change the brush size often ? do you change the start/end color of your gradient ?
It's only by answering those question and having a random set of rect/hardness that has the same average distribution of your real use case that you'll be able to benchmark/compare anything.
Last word : If it's becoming hard to say which solution is faster, its time to pick the solution relying on... some other good reason... :-)

Repeating a method to draw a pattern on 2D Canvas

I have a method which draws an image using the context.fillRect() method. I want this image to be drawn repeatedly i.e in a tiled format along the x and y axes as it is small in size (15 pixels in length).
It is to fill my canvas of 700 px width, 500 px height.
Can this be done using the context.createPattern() method? How?
The secret sauce to using context.createPattern is the context.fillStyle property.
First, you create the pattern, then you assign the pattern to context.fillStyle, and finally, you use context.fillRect to draw the pattern:
// assuming img is loaded...
var ptrn = ctx.createPattern(img,'repeat');
ctx.fillStyle = ptrn;
ctx.fillRect(0,0, canvas.width,canvas.height);
There is a complete example on MDN.
Based on your response to apsillers answer, I would recommend you draw your desired pattern to another canvas element. You can then pass the completed drawing to ctx.createPattern.
var drawing = document.createElement('canvas');
//Do necessary drawing
//ctx.fillRect(etc)
var ptrn = ctx.createPattern(drawing,'repeat');
ctx.fillStyle = ptrn;
ctx.fillRect(0,0, canvas.width,canvas.height);
HTML5 Canvas - Repeat Canvas Element as a Pattern

KineticJS click detection inside animated shapes

OK, I admit I tried to be clever: I thought if I overrode Shape's drawFunc property I could simply draw whatever inside a rectangle and still use KineticJS's click detection. Here's my attempt:
var shape = new Kinetic.Shape({
drawFunc: function(context) {
var id = 26; // Id of a region inside composite image.
context.beginPath();
context.rect(0, 0, w, h);
context.closePath();
this.fill(context);
this.stroke(context);
context.drawImage(copyCanvas, (id % 8) * w, flr(id / 8) * h,
w, h, 0, 0, w / 2, h / 2);
},
draggable: true
});
So, the idea was to draw a rectangle, and use drawImage() to draw something on top of the rectangle (like a texture, except it changes from time to time because copyCanvas itself changes). All the meanwhile, I expected event handling (drag-n-drop, in particular) to still 'just work'. Well, here's what happens: the part of the rectangle not covered by my drawImage() correctly detects clicks. However, the one fourth of the rectangle that is covered by the image refuses to respond to clicks! Now, my question is why? I dug into the KineticJS code, and looked to me that click detection simply means drawing to a buffer and seeing if a given x, y point has non-zero alpha. I can't see how this could be affected by my drawing an image on top of my rectangle.
Any ideas what's going on?
OK, so I went ahead and looked at the source code. Here's the definitive answer:
KineticJS assigns a random and unique RGB color to each shape that's created using a global map from RGB colors to shape objects. The draw() function of the shape is called twice: once with the 'real' canvas, and once with a 'buffer' canvas used for hit detection. When using the 'buffer' canvas, KineticJS switches the stroke and fill colors to the unique RGB color of the given shape. The same 'buffer' canvas is used for all shapes on a layer. Thus hit detection simply becomes reading the RGB value of a given point and looking up the corresponding shape in the global map. Now, in my example I drew an image in a way that circumvented KineticJS's juggling of colors used for hit detection. Thus, when I clicked on the image area, KineticJS saw some unknown RGB color on the buffer canvas with no known shape assigned to it.
The solution is not to draw the image for the 'buffer' (or 'hit detection') phase: a simple rectangle will do. In case you're wondering, here's the correct code for the drawFunc:
var width = 200;
var height = 100;
var myShape = new Kinetic.Shape({
drawFunc: function(context) {
if (layer.bufferCanvas.context == context) {
context.beginPath();
context.rect(0, 0, width, height);
context.closePath();
this.fill(context);
this.stroke(context);
} else {
context.drawImage(someCanvasWithAnythingOnIt, 0, 0, width, height,
0, 0, width, height);
}
}});
Can I collect my own reward?
I think your problem lies in the order. There is a depth associated with each object that you draw and the default ordering is like a stack, last drawn is on top.
Now that you have modified the code, making 2 draws inside the shape draw function, I still think the ordering is preserved and hence, the object is not able to detect the input. Try changing the order, i.e. draw image first and then the rectangle and see if the problem is solved.
Else, share a jsFiddle for an example.

Using jQuery / HTML5 to animate a circle

For a personal project, I'm tyring to make a timer application (for controlling Poker blind schedules). I know there are several solutions already on the market, but for reasons which are too lengthy to go into here, we need a bespoke system. Although the output template of the system will ultimately be definable by the end user, we would like to include a widget which shows a circle that gets animated as the time counts down. Here's an illustration of a working example, showing the yellow circle and what we'd like to achieve (or something similar, anyway):
My question is, how would one code the animation as shown below using either jQuery or raw HTML5 / CSS3 animations? This is for a web application using jQuery, so there are no other library options I can use.
Advanced thanks!
If you can use HTML5, canvas is probably your best bet. Mozilla has some decent tutorials on drawing arcs. This should be enough to get you started.
var canvas = document.getElementById('canvasid'),
width = canvas.width,
height = canvas.height,
ctx = canvas.getContext('2d');
function drawTimer(deg) {
var x = width / 2, // center x
y = height / 2, // center y
radius = 100,
startAngle = 0,
endAngle = deg * (Math.PI/180),
counterClockwise = true;
ctx.clearRect(0, 0, height, width);
ctx.save();
ctx.fillStyle = '#fe6';
// Set circle orientation
ctx.translate(x, y);
ctx.rotate(-90 * Math.PI/180);
ctx.beginPath();
ctx.arc(0, 0, radius, startAngle, endAngle, counterClockwise);
ctx.lineTo(0, 0);
ctx.fill();
}
setInterval(function() {
// Determine the amount of time elapsed; converted to degrees
var deg = (elapsedTime / totalTime) * 360;
drawTimer(deg);
}, 1000);
You can achieve this with CSS3 and jQuery.
Check out my example here http://blakek.us/css3-pie-graph-timer-with-jquery/ which with slight modification you could make the timer look how you want it.
In HTML5 you can draw in a canvas.
For example:
// Create the yellow face
context.strokeStyle = "#000000";
context.fillStyle = "#FFFF00";
context.beginPath();
context.arc(100,100,50,0,Math.PI*2,true);
context.closePath();
context.stroke();
context.fill();
Link
You should really check out Processing!
Especially Processing.js. There have been some interesting things made with it for iPhone
First solution i can think of is html5 canvas implementation. From the example above it becomes clear we're talking mobile, so what support does canvas get around mobile browsers i can't tell. developer.mozilla.org provides sufficient documentation. Using canvas to achieve such count down should be pretty simple task. Good luck.
HTML5 Canvas arc command (MDN) is probably what you're looking for. Just decrease the end angle over time to have your timer decrease.

Categories