html5 canvas animation, rotating the text is not smooth enough - html

The idea is simple, create a star with text and rotate it.
But its not smooth after making a quick script
here is my fiddle
The star is moving oke, but the text is shaking like a snake :)

Cause
The reason why this happening is due to the browser's text rendering engine. They are translating each point in the text path using the same rotation matrix but due to the engine you will get rounding errors in there causing the text to "jibber".
This is not only happening for canvas but for CSS transformations as well, with text.
Solution
Simply render the text to a canvas "layer" and use that as an image which you rotate instead of the text itself. This rasterizes the text in its normal position and the transformation happens on the bitmap instead which most browsers handle pretty well.
Here is one example integrating the answer I linked to in the comments. I'm showing only the main text as it works as a comparer as well, before and after:
// canvas layer
var tcanvas = document.createElement('canvas'); //tcanvas must be in global scope
var tctx = tcanvas.getContext('2d');
tcanvas.width = canvas.width;
tcanvas.height = canvas.height;
tctx.translate(250, 250);
tctx.fillStyle = "black";
tctx.font = "bold 60px Arial";
tctx.textAlign = 'center';
tctx.fillText('€ 1215,34', 0, 0);
Now the layer is ready and we can replace the text drawing methods with a single drawImage instead:
c.drawImage(tcanvas, -x, -y);
Result of this modification
To draw the "extra" just move those lines down to the layer creation as well. Note that tcanvas in the example must be accessible globally.
If the rotation speed of the text is not intentional just remove the second call to rotate you have there before rendering the text.
Tip: instead of redrawing gradients and the star just render it once to another layer and rotate that as an image as well. This will be much more efficient. You could also avoid save/restore (which are relative costly) by just accumulating the step on rotate() itself and use setTransform to transform the matrix using absolute values.
Ways to optimize memory usage: for text layer use a canvas size where the text fits in exact (don't use fixed values as text size may vary in size and position from browser to browser depending on the text rendering engine), same for star if you go with a layer for that as well.
Hope this helps!

The shake in your text comes from the fact that the context is not in a proper state when you draw the text : you just did quite some operations on it before.
I just added a
c.restore();
c.save();
c.translate(x,y);
before the text part of your code, and the text is solidly hung to the star now :
http://jsfiddle.net/gamealchemist/xUr4f/1/
Edit : There are in fact 2 issues at stake here : 1) the text rotation is not quite on track with the star and 2) the rounding of the text coordinates makes the text shake.
Both Chrome and FF exhibit 1) of course, and with a clean context 1) disappear on both.
For 2) : Chrome is ok with non-integer coordinates text, but FF does round, which creates a shake on a rotating text.
Only solution i see it to 'print' the text on a canvas, then have the canvas rotate. I did it for the 'extra' in this fiddle :
http://jsfiddle.net/xUr4f/4/
There's a loss of quality compared to the fillText, but unless the coordinates rounding can be avoided, it seems as the best solution.

Related

html canvas transform ? is it only: first do the transform, and then you draw into the canvas? Not, stick an image or etc on canvas, and transform it?

My goal is to bring in an image on to part of the canvas, then scale it, move/translate it, and optionally skew it, also rotate and make alpha changes, kind of the primary "2d image manipulations", in an animated form, which is: do little changes over time from the starting state to the target end state.
Well, I figured to be efficient, I should use the canvas/2d context transform, https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform -- as it does the first 3: scale, move/translate, and skew, "all in one." I did half that code, and now I'm looking at examples and seeking to debug it. All the examples I see, are do 1) some transform, away from the "unity transform":
{ a:1, b:0, c: 0, d:1, e:0, f:0 }; // this basic transform does nothing
and then 2) draw into that. But that's the opposite order from what I want: which is draw on the canvas (the image), and then do an animation over time using the above primary changes (scale, translate, skew, rotate, and alpha). My question is: does it only "work this way", meaning I must setup the (single) transformation on the page first, and then "draw into that?"
I hope not ... that won't give me what I want, and I have to "ditch it", and go to 5 individual "transformations." Comments?
Yes that only works this way, canvas transforms and compositing mode and filters and lineWidth and fillStyle etc. properties are only applied to the next drawing operations.
The canvas itself only holds pixels information, it has no concept of drawn object. Your js code has to do this part.
So for what you wish, you can simply redraw everything every time:
reset the transform so we can clear ctx.setTransform(1,0,0,1,0,0);
Clear the canvas ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height)
Set the transform to your new matrix ctx.translate(x,y); ctx.scale(s)...
Draw your transformed graphics ctx.fill(); ctx.drawImage(...
Wait next frame to do it again requestAnimationFrame(update)

Why canvas text quality is low after requestAnimationFrame usage?

I draw the text on canvas and then apply requestAnimationFrame to change its color:
var text = 'Sample text';
ctx.fillText(text,canvas_width/2,100);
requestAnimationFrame(animate);
function animate(time){
ctx.fillText(text,-offset,100);
}
See the demo with full code used.
Initially text looks OK (jf you comment requestAnimationFrame() line):
but after animation used it looks like below -
You may notice white pixels there, which looks awful (please ignore the colors used - they are applied to demonstrate the problem).
What could be wrong there?
I was thinking that it is probably caused by my offset/position calculations - textWidth/2, cw/2, but could it return different result from time to time?
I've tested the code with Google Chrome 39.0.2171.95 (64-bit) under OSX 10.10.1.
You're not clearing the canvas, so the old text will stay there. That is the problem that is giving you your white pixels. This problem is (presumably) because of anti-aliasing.
To fix this problem, like I have mentioned, you're not clearing the canvas on every frame. Make sure you add this before you draw anything on the canvas:
ctx.clearRect(0, 0, canvas.width, canvas.height)

Draw stroke on HTML canvas with different levels of opacity

The problem
I'm trying to create a brush tool with opacity jitter (like in Photoshop). The specific problem is:
Draw a stroke on an HTML canvas with different levels of opacity. Pixels with higher opacity should replace pixels with lower opacity; otherwise, pixels are left unchanged.
Transparency should not be lost in the process. The stroke is drawn on a separate canvas and merged with a background canvas afterwards.
The result should look like this. All code and the corresponding output can be found here (JSFiddle).
Because you can't stroke a single path with different levels of opacity (please correct me if I'm wrong) my code creates a path for each segment with different opacity.
Non-solution 1, Using the 'darken' blend mode
The darken blend mode yields the desired result when using opaque pixels but doesn't seem to work with transparency. Loosing transparency is a dealbreaker.
With opaque pixels:
With transparent pixels:
Non-solution 2, Using the 'destination-out' compositing operator
Before drawing a new stroke segment, subtract its opacity from subjacent pixels by using the 'destination-out' compositing operator. Then add the new stroke segment with 'source-over'. This works almost but it's a little bit off.
Looking for a solution
I want to avoid manipulating each pixel by hand (which I have done in the past). Am I missing something obvious? Is there a simple solution to this problem?
"Links to jsfiddle.net must be accompanied by code."
Because you can't stroke a single path with different levels of opacity (please correct me if I'm wrong)
You're wrong =)
When you use globalCompositeOperation = 'destination-out' (which you are in lineDestinationOut) you need to set the strokeStyle opacity to 1 to remove everything.
However, simply changing that in your fiddle doesn't have the required effect due to the order of your path build. Build the 10% transparent one first, the whole length, then delete and draw the two 40% transparent bits.
Here's a jsfiddle of the code below
var canvas = document.getElementById('canvas');
var cx = canvas.getContext('2d');
cx.lineCap = 'round';
cx.lineJoin = 'round';
cx.lineWidth = 40;
// Create the first line, 10% transparency, the whole length of the shape.
cx.strokeStyle = 'rgba(0,0,255,0.1)';
cx.beginPath();
cx.moveTo(20,20);
cx.lineTo(260,20);
cx.lineTo(220,60);
cx.stroke();
cx.closePath();
// Create the first part of the second line, first by clearing the first
// line, then 40% transparency.
cx.strokeStyle = 'black';
cx.globalCompositeOperation = 'destination-out';
cx.beginPath();
cx.moveTo(20,20);
cx.lineTo(100,20);
cx.stroke();
cx.strokeStyle = 'rgba(0,0,255,0.4)';
cx.globalCompositeOperation = 'source-over';
cx.stroke();
cx.closePath();
// Create the second part of the second line, same as above.
cx.strokeStyle = 'black';
cx.globalCompositeOperation = 'destination-out';
cx.beginPath();
cx.moveTo(180,20);
cx.lineTo(260,20);
cx.stroke();
cx.strokeStyle = 'rgba(0,0,255,0.4)';
cx.globalCompositeOperation = 'source-over';
cx.stroke();
cx.closePath();
Use two layers to draw to:
First calculate the top layer opacity 40% - 10% and set this as alpha on top layer
Set bottom layer to 10%
Set top layer with dashed lines (lineDash) (calculate the dash-pattern size based on size requirements)
Draw lines to both layers and the bottom layer will be a single long line, the top layer will draw a dashed line on top when stroked.
Copy both layers to main canvas when done.
#HenryBlyth's answer is probably the best you're going to get; there's no native API to do what you're being asked to do (which, in my opinion, is kinda weird anyways... opacity isn't really supposed to replace pixels).
To spell out the solution in one paragraph: Split up your "stroke" into individual paths with different opacities. Draw the lowest opacity paths as normal. Then, draw the higher opacities with "desitination-out" to remove the low-opacity paths that overlap. Then, draw the high opacity paths as usual, with "source-over", to create the effect desired.
As suggested in the comments to that answer, #markE's comment about making each path an object that is pre-sorted before drawing is a great suggestion. Since you want to perform manual drawing logic that the native API can't do, turning each path into an object and dealing with them that way will be far easier than manually manipulating each pixel (though that solution would work, it could also drive you mad.)
You mention that each stroke is being done on another canvas, which is great, because you can record the mouseevents that fire as that line is being drawn, create an object to represent that path, and then use that object and others in your "merged" canvas without having to worry about pixel manipulation or anything else. I highly recommend switching to an object-oriented approach like #markE suggested, if possible.

Fitting Or Warping Text (or an image) Into A Custom Envelope Outline Using HTML5 Canvas

Let's say that I use some HTML5 markup:
<canvas id="e" width="400" height="200"></canvas>
<script>
var canvas = document.getElementById("e");
var context = canvas.getContext("2d");
context.fillStyle = "red";
context.font = "bold 72px JerseyLetters";
context.fillText("Bulls", 50, 100);
</script>
To make some cool text like this:
Then I decide I want these letters to fit into an envelope that looks like this:
Hoping to get something like this:
How would I go about (1) defining an envelope like the one above and then (2) putting the text in the envelope using HTML5 Canvas to get the result?
I am open to either something that directly places text in the envelope or a solution that first creates an image and then fits the image in an envelope.
Thanks!
EDIT
I added the tags "webgl" and "three.js" to this question on the advice of #markE. I will research those two packages in the mean time as well. I'm very new to .
webGL way:
Do it as a image-processing with pixel-shader.
Render text with 2d canvas, bind webGL texture with buffer and fill texture with canvas image (rendered text). Have prepared envelope that actually maps the area that envelope holds and also every pixel play role of the UV coordinate from the first image. Running that as pixel shader, you have image-to-be-squeezed and envelope (uvs) you'll output final image. That way, it's completely font and text independent. You could even probably make one image-processing step more so you could load any envelope shape and process it on spot, so it becomes font, text and envelope-shape independent.
I'm not sure how well did I explain this.
Hope this helps, though.
SVG provides these sort of text transforms. See http://tavmjong.free.fr/SVG/TEXT_PATH/TextPath.html
EDIT: This link appears to be converting the text to actual SVG. Probably not going to be helpful for you, sorry.

Add Text Shadow on Canvas that has also an image on it

I am using the canvas and HTML5. I have an icon in my canvas and a text and when I am trying to add shadow in my Text with this code:
ctx.shadowColor = textShadowColor;
ctx.shadowBlur = 1;
ctx.shadowOffsetX = 1;
ctx.shadowOffsetY = 1;
the shadow goes also to my image. What could be the problem. As I see this shadowColor goes on the canvas and not really on the text. Should a have different canvas for the text and the image?
Thanks in advance
Are you drawing the text or image first?
If you're drawing the image first there should be no problem:
http://jsfiddle.net/NAanu/
If you're drawing the text first you need to clear the shadow so the picture doesn't get drawn with it also. Here's an example of drawing the text with a shadow first:
http://jsfiddle.net/NAanu/1/
I use save and restore to save and clear the shadow state from the context. You could just set them all to their default values instead, though.
When you set properties on the context you need to think of it like loading up a paintbrush with paint.
Setting any context property, like the shadowColor to red is like loading red paint onto the edge of your paintbrush. Anything you paint from then on will have red on it.
The only way to stop that is to clean your paintbrush (set the color and shadowOffset back to their defaults) or to use save() and restore(), which is saying "remember that my paintbrush was once clear, so later I can recall this clear paintbrush to use again".