Canvas radial gradient vs .png performance - javascript

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... :-)

Related

How to transform a rectangle into a circle

I want to turn a rectangular image into a circle using the HTMLCanvas element. (Finally I only need the upper half of the circle but that could be easily managed by cutting the resulting circle in half.)
From this
To this
My idea was to do a simple line by line transformation. So far I have just the basic drawing logic but I'm totally lost with the math for the transformation.
<!DOCTYPE html>
<body>
<canvas id="canvas"></canvas>
</body>
<script type="text/javascript">
var img = new Image();
img.onload = function init() {
var img = this;
var imgH = img.height;
var imgW = img.width;
// make the canvas the same size as the image.
var c = document.getElementById("canvas");
c.width = imgW;
c.height = imgH;
var ctx = c.getContext("2d");
var halfHeight = imgH/2;
// draw the upper part
// line by line
for(var i = 0; i < halfHeight; i++) {
// I'm totally lost here.
// current output without transformation
ctx.drawImage(img, 0, i, imgW, 1, 0, i, imgW, 1);
}
// add the second half which must not be transformed
ctx.drawImage(img, 0, halfHeight, imgW, halfHeight, 0, halfHeight, imgW, halfHeight);
};
img.src = "https://i.stack.imgur.com/52TjZ.png";
</script>
</html>
A fiddle
https://jsfiddle.net/kirschkern/amq7t6ru/2/
(I need it in pure JS and 2d. No three.js, no webgl.)
Any help is highly appreciated.
I don't know much about Javascript but as this seems more of a mathematical question, I'll have my shot.
Replace the lines
// I'm totally lost here.
// current output without transformation
ctx.drawImage(img, 0, i, imgW, 1, 0, i, imgW, 1);
with
var xMargin = -Math.sqrt(1-Math.pow((i-halfHeight)/halfHeight,2))*imgW/2+imgW/2;
ctx.drawImage(img, 0, i, imgW, 1, xMargin, i, imgW-(2*xMargin), 1);
This distorts the upper half of the image as an ellipse (a circle would work only if your input image would be a square) as this:
Does this solve your question?
Explanation
I took the equation of a shifted ellipse from Wikipedia and set c1 and a to be equal to imgW/2 and c2 and b to imgH/2. Taking i for y let me compute x; I saved one of the solutions as xMargin. The width of the picture at the given vertical coordinate would be the original width minus twice the margin.
In the end, I fed drawImage() with these inputs, see the documentation.
Plain 2D JavaScript does not have primitives to distort images like this. So a simple drawImage will not be enough.
What you can do is approximate things. Write a function which for every point in the distorted image (the one with the circle) computes the corresponding position in the original image. Then you can do one of four things, in increasing order of effort and resulting quality.
Iterate over all the pixels in the destination image, and look up the corresponding pixel value in the source image.
Like before, but with subsampling: take several positions inside the square of the source pixel, and average the resuling colors for a smoother appearance.
Approximate the affine transformation in a given point (for this you will likely need partial derivatives of your mapping function) and use it to draw an affinely transformed image.
Same as 3 but with projective instead of affine transforms. That would arguably make it 3D in its formulation.
Like 1 or 2 but implement all of that in WebGL as a fragment shader. I know you said you don't want that, but in terms of performance and resulting quality this should give the best results.

Changing Pattern Size in Html5 Canvas

My Problem: I've got an ImageObject, that is being used to create a PatternObject. My problem is, that i changed the width and height properties of the Image before creating the pattern, but that doesn't actually remap the image (and that is also no problem). The thing is, that I now have got a pattern, that is of a different size (the original image size) than the image itself. If I want to draw a line with the fillStyle of that pattern, it doesn't fit (because I need a pattern of the new size).
My question: Is there an easy way, to achieve that the pattern's width and height can be adjusted?
My tries and why I dont like them:
1) Render the original image to a canvas with the new size and create the pattern from that. Didn't use this, because the pattern cannot be loaded directly as a result of the canvas being created and rendered to too slowly. But I want that pattern directly
2) Calculate the variance between the new image size and the original one, change the lineWidth of the context, so the patterns height fits exactly and scale the line down, so it has a nice size. Didn't use that because I render in realtime and this is way too slow to be used later in webapps.
Using canvas (your step 1) is the most flexible way.
It's not slower using a canvas to draw on another canvas than using an image directly. They both use the same element basis (you're blitting a bitmap just as you do with an image).
(Update: Drawing using pattern as style do go through an extra step of a local transformation matrix for the pattern in more recent browsers.)
Create a new canvas in the size of the pattern and simple draw the image into it:
patternCtx.drawImage(img, 0, 0, patternWidth, patternHeight);
Then use the canvas of patternCtx as basis for the pattern (internally the pattern caches this image the first time it's drawn, from there, if possible, it just doubles out what it has until the whole canvas is filled).
The other option is to pre-scale the images to all the sizes you need them to be, load them all in, and then choose the image which size is the one you need.
The third is to draw the image yourself as a pattern. This however is not so efficient compared to the built-in method, though using the above mentioned method (internal) you can get a usable result.
Example of manual patterning:
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = function() {
fillPattern(this, 64, 64);
change.onchange = change.oninput = function() {
fillPattern(img, this.value, this.value);
}
};
img.src = "//i.stack.imgur.com/tkBVh.png";
// Fills canvas with image as pattern at size w,h
function fillPattern(img, w, h) {
//draw once
ctx.drawImage(img, 0, 0, w, h);
while (w < canvas.width) {
ctx.drawImage(canvas, w, 0);
w <<= 1; // shift left 1 = *2 but slightly faster
}
while (h < canvas.height) {
ctx.drawImage(canvas, 0, h);
h <<= 1;
}
}
<input id=change type=range min=8 max=120 value=64><br>
<canvas id=canvas width=500 height=400></canvas>
(or with a video as pattern).

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