It seems that Chrome forces hardware-accelerated transforms on text that is on top of a canvas element.
Can anyone help me understand this behavior? Ultimately, I'd like to scale text on top of a canvas element without having the text converted to a texture.
This fiddle shows the issue: http://jsfiddle.net/Gb6h4/1/
Here is the code:
// Get a reference to the canvas and its context
var $canvas = $("canvas");
var ctx = $canvas[0].getContext('2d');
// Make the canvas fullscreen
var width = $(window).width(),
height = $(window).height();
$canvas.attr({
width: width,
height: height
});
// In Chrome, modifying the canvas context in any way
// seems to force a hardware-accelerated transform
// on the text.
// (The text is scaled as a texture, becoming blurrier.)
// Uncomment the line below and compare the difference.
// ctx.fillStyle = "grey";
// Set up a simple zoom animation on our "Hello!" div
var $div = $(".transformed");
var scale = 1;
setInterval(function () {
scale += 0.005;
$div.css({
transform: "translate(0px, 0px) scale("+scale+")"
});
}, 16);
In the fiddle, by default, the text scales as expected (i.e., a non-accelerated CSS transform). However, after interaction with the canvas context, the text scales differently (as it would in an accelerated transform).
This is a a side-effect of how CSS transforms work on composited layers in Chrome today.
An accelerated 2D context causes a RenderLayer to get its own compositing layer. Moreover, a layer that has a composited sibling with a lower z-index also gets its own compositing layer.
See http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome
Relevant bug report: https://code.google.com/p/chromium/issues/detail?id=122083.
Related
I have a canvas with an image that fills the canvas. This works fine but I would like to have the canvas be full width of the window. At the moment the canvas is the width of the image I put in it.
I've tried the following at the bottom of my script:
function resizeCanvas() {
canvas.setHeight(img.height);
canvas.setWidth(window.innerWidth);
canvas.renderAll();
}
resizeCanvas();
But this does not work.
I've also tried giving the canvas element 100% width in its paramaters and with css, both with no success.
Codepen with working example:
https://codepen.io/twan2020/pen/YzpeEEr
If you increase your screen size the image stops after a while instead of keeping full width of the browser window.
Is this possible? With keeping all objects in their place?
It was not easy to fix but i found some documentation that could make it work.
First edit the code to realWidth and add var realWidth = window.innerWidth;
and then for setting the background remove your current code and add
fabric.Object.NUM_FRACTION_DIGITS = 10;
fabric.Image.fromURL(source, function(img) {
img.scaleToWidth(canvas.width);
canvas.setBackgroundImage(img);
canvas.requestRenderAll();
});
this should fix the problem, and the backgound will take the full size of the screen.
Update for the new calculation of the circle position.
is equal to Newposition= oldPosition * (newWidth / oldWidth)
Here you can see that it work https://codepen.io/AlenToma/pen/ExNEooX
I am currently trying to work on this script, which was created by someone else. Yes, I grabbed it, yes I will give credits.
The problem I am having, is trying to center the text even if the window has been resized. When you move the cursor on the text, it explodes randomly. When I resize the window, I need that same text (and those exploded characters) to move. I can easily just put new text in using fillText(), but then I replace the exploded characters.
Obviously I have tried this in my example:
window.onresize = function(event) {
reload(canvas_id);
}
var reload = function(canvas_id) {
canvas = document.getElementById(canvas_id);
context = canvas.getContext("2d");
canvas.width = window.innerWidth;
}
This resizes the canvas perfectly, but the text won't be centered anymore. To center the text when I place it, I do this:
(window.innerWidth / 2) - (Math.round(bgContext.measureText(keyword).width/2))
bgContext being the canvas.getContext("2d"); obviously.
Here's a JSFiddle showing this issue: http://jsfiddle.net/2e8o5db8/1/
First, tell canvas to draw text aligned from a center X rather than the default left-aligned
context.textAlign = 'center';
And then set the x,y in fillText to the center of the canvas.
fillText('Hello', canvas.width/2, 50);
By using canvas.width/2 you will redraw the text at center-canvas even if the canvas is resized.
You will have to redraw the text after using canvas.width=... because the canvas contents will automatically be cleared.
I am trying to animate rectangles with background image using Raphaël.js, see this demo. While the Rectangle size has been set to 60x60 and also the image size is absolutely 60x60 image is not fitting inside the rectangles!
To me this is just happening when I use the animate() function and without that all images perfectly fit inside the rectangles.
Why is this happening and how I can solve the issue?
var r = Raphael('canvas', 500, 500);
r.canvas.style.backgroundColor = '#ccc';
var st = r.set();
st.push(
r.rect(100,100,60,60),
r.rect(180,100,60,60),
r.rect(100,200,60,60)
);
st.attr({fill:"url(http://cdn2.image-tmart.com/prodimgs/1/13010995/Rose-Bouquet-of-Peach-Heart-Home-Decoration-Simulation-Red-Inside-Pink-Outside_3_60x60.jpg?1363942767)", "stroke-width": 0});
st.animate({transform: "t200,100"}, 500);
In Raphael.js Element.attr({fill: 'url(...)'}) creates a tiled <pattern> to fill in shapes and texts. However, Raphael.js is aimed to be compatible with both SVG and VML (supported by IE 8), so in my opinion it makes some compromises like automatically adjusting the position of <pattern>. Thus, when you translate <rect>, the <pattern> is translated reversely so they look fixed inside the canvas.
Using Paper.image(src, x, y, w, h) is likely to solve your problem, with the same visual behavior. Because <image> coordinate will not be changed implicitly by Raphael.js. Like this:
var r = Raphael('canvas', 500, 500);
r.canvas.style.backgroundColor = '#ccc';
var st = r.set();
st.push(
r.image(null, 100,100,60,60),
r.image(null, 180,100,60,60),
r.image(null, 100,200,60,60)
);
st.attr({src:"http://cdn2.image-tmart.com/prodimgs/1/13010995/Rose-Bouquet-of-Peach-Heart-Home-Decoration-Simulation-Red-Inside-Pink-Outside_3_60x60.jpg?1363942767"});
st.animate({transform: "t200,100"}, 500);
I also recommend Snap.svg, which is from the same author as Raphael.js, but it is for SVG only and has less side effects.
Let's put some text on a HTML5 <canvas> with
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d'),
ctx.textBaseline = 'top';
ctx.textAlign = 'left';
ctx.font = '14px sans-serif';
ctx.fillText('Bonjour', 10, 10);
When zooming the canvas on text, one can see pixelation.
Is there a way of zooming on a canvas without having pixelation on text ?
When you fillText on the canvas, it stops being letters and starts being a letter-shaped collection of pixels. When you zoom in on it, the pixels become bigger. That's how a canvas works.
When you want the text to scale as a vector-based font and not as pixels, don't draw them on the canvas. You could create <span> HTML elements instead and place them on top of the canvas using CSS positioning. That way the rendering engine will render the fonts in a higher resolution when you zoom in and they will stay sharp. But anything you draw on the canvas will zoom accordingly.
Alternatively, you could override the browsers zoom feature and create your own zooming algorithm, but this will be some work.
When the user zooms in or out of the window, the window.onresize event handler is triggered. You can use this trigger to adjust the width and the height of the canvas css styling accordingly (not the properties of the canvas. That's the internal rendering resolution. Change the width and height attributes of the style which is the resolution it is scaled to on the website).
Now you effectively disabled the users web browser from resizing the canvas, and also have a place where you can react on the scaling input events. You can use this to adjust the context.scale of your canvas to change the size of everything you draw, including fonts.
Here is an example:
<!DOCTYPE html>
<html>
<head>
<script type="application/javascript">
"use strict"
var canvas;
var context;
function redraw() {
// clears the canvas and draws a text label
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.font = "60pt sans-serif";
context.fillText("Hello World!", 100, 100);
}
function adjustSize() {
var width = window.innerWidth;
var height = window.innerHeight;
// resize the canvas to fill the whole screen
var style = canvas.style;
style.width = width + "px";
style.height = height + "px";
// backup the old current scaling factor
context.save();
// change the scaling according to the new zoom factor
context.scale(1000 / width, 1000 / height);
// redraw the canvas
redraw();
// restore the original scaling (important because multiple calls to scale are relative to the current scale factor)
context.restore();
}
window.onload = function() {
canvas = document.getElementById("myCanvas");
context = canvas.getContext("2d");
adjustSize();
}
window.onresize = adjustSize;
</script>
</head>
<body>
<canvas id ="myCanvas" width = 1000 height = 1000 ></canvas>
</body>
</html>
If you only need to scale text you can simply scale the font size.
A couple of notes on that however: fonts, or typefaces, are not just straight forward to scale meaning you will not get a smooth progress. This is because fonts are often optimized for certain sizes so the sizes in between so to speak are a result of the previous and next size. This can make the font look like it's moving around a little when scaled up and is normal and expected.
The approach here uses a simply size scale. If you need an absolute smooth scale for animation purposes you will have to use a very different technique.
The simple way is:
ctx.font = (fontSize * scale).toFixed(0) + 'px sans-serif';
An online demo here.
For animation purposes you would need to do the following:
Render a bigger size to an off-screen canvas which is then used to draw the different sizes
When the difference is too big and you get problems with interpolation you will have to render several of these cached text images at key sizes so you can switch between them when scaling factor exceeds a certain threshold.
In this demo you can see that at small sizes the pixels gets a bit "clumpy" but otherwise is much smoother than a pure text approach.
This is because the browser uses bi-linear interpolation rather than bi-cubic with canvas (this may or may not change in the future) so it's not able to interpolate properly when the difference gets to big (see below for solution with this issue).
The opposite happens at big sizes as the text gets blurry also due to interpolation.
This is where we would have to switch to a smaller (or bigger) cached version which we then scale within a certain range before we again switch.
The demo is simplified to show only a single cached version. You can see halfway through that this works fine. The principle would be in a full solution (sizes being just examples):
(Update Here is a demo of a switched image during scale).
-- Cached image (100px)
-- Draw cached image above scaled based on zoom between 51-100 pixels
-- Cached image (50px) generated from 100px version / 2
-- Draw cached image above scaled based on zoom between 26-50 pixels
-- Cached image (25px) generated from 50px version / 2
-- Draw cached image above scaled based on zoom between 1-25 pixels
Then use a "sweet spot" (which you find by experiment a little) to toggle between the cached versions before drawing them to screen.
var ctx = canvas.getContext('2d'),
scale = 1, /// initial scale
initialFactor = 6, /// fixed reduction scale of cached image
sweetSpot = 1, /// threshold to switch the cached images
/// create two off-screen canvases
ocanvas = document.createElement('canvas'),
octx = ocanvas.getContext('2d'),
ocanvas2 = document.createElement('canvas'),
octx2 = ocanvas2.getContext('2d');
ocanvas.width = 800;
ocanvas.height = 150;
ocanvas2.width = 400; /// 50% here, but maybe 75% in your case
ocanvas2.height = 75; /// experiment to find ideal size..
/// draw a big version of text to first off-screen canvas
octx.textBaseline = 'top';
octx.font = '140px sans-serif';
octx.fillText('Cached text on canvas', 10, 10);
/// draw a reduced version of that to second (50%)
octx2.drawImage(ocanvas, 0, 0, 400, 75);
Now we only need to check the sweet spot value to find out when to switch between these versions:
function draw() {
/// calc dimensions
var w = ocanvas.width / initialFactor * scale,
h = ocanvas.height / initialFactor * scale;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (scale >= sweetSpot) {
ctx.drawImage(ocanvas, 10, 10, w, h); /// use cached image 1
} else {
ctx.drawImage(ocanvas2, 10, 10, w, h); /// use cached image 2
}
}
So why not just draw the second cached image with a font? You can do that but then you are back to the issue with fonts being optimized for certain sizes and it would generate a small jump when scaling. If you can live with that then use this as it will provide a little better quality (specially at small sizes). If you need smooth animation you will have to reduce a larger cached version in order to keep the size 100% proportional.
You can see this answer on how to get a large image resized without interpolation problems.
Hope this helps.
I'm trying to allow the user to draw a rectangle on the canvas (like a selection box). I'm getting some ridiculous results, but then I noticed that even just trying the code from my reference here, I get huge fuzzy lines and don't know why.
it's hosted at dylanstestserver.com/drawcss. the javascript is inline so you can check it out. I am using jQuery to simplify getting the mouse coordinates.
The blurry problem will happen if you use css to set the canvas height and width instead of setting height and width in the canvas element.
<style>
canvas { height: 800px; width: 1200px; } WRONG WAY -- BLURRY LINES
</style>
<canvas height="800" width="1200"></canvas> RIGHT WAY -- SHARP LINES
For some reason, your canvas is stretched. Because you have its css property width set to 100%, it is stretching it from some sort of native size. It's the difference between using the css width property and the width attribute on the <canvas> tag. You might want to try using a bit of javascript to make it fill the viewport (see jQuery .width()):
jQuery(document).ready(function(){
var canvas = document.getElementById('drawing');
canvas.width(($(window).width()).height($(window).height());
var context = canvas.getContext('2d');
//...
The way I do it is to set the canvas element to a width and height in the css, and then set the canvas.width = canvas.clientWidth and canvas.height = canvas.clientHeight
var canvas = document.getElementById("myCanvas");
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
var context = canvas.getContext("2d");
You haven't indicated canvas size in pixels, so it is scaled up. It is 300x150 here. Try setting the width, height
On retina displays you also need to scale (in addition to the accepted answer):
var context = canvas.getContext('2d');
context.scale(2,2);
The css sizing issue mentioned in these comments is correct, but another more subtle thing that can cause blurred lines is forgetting to call make a call to context.beginPath() before drawing a line. Without calling this, you will still get a line, but it won't be smoothed which makes the line looks like a series of steps.
I found the reason mine was blurry was that there was a slight discrepancy between the inline width and the CSS width.
I have both inline width/height parameters AND css width/height assigned to my canvas, because I need to keep its physical dimensions static, while increasing its inline dimensions for retina screens.
Check yours are the same if you have a situation like mine.