Jaggies text when fillText in canvas in chrome - css

I am trying to draw text in canvas but the drawn text has jaggies especially in chrome 31.0.1650.
I have tried -webkit-font-smoothing:antialiased,text-shadow but all go in vain.
How to tackle this problem?
Thanks in advance.
Here is the style code:
<style>
#my_canvas{
-webkit-font-smoothing: antialiased;
text-shadow: 1px 1px 1px rgba(0,0,0,0.004);
text-indent: -9999px;
}
</style>
The code in body:
<canvas id="my_canvas" height="300" width="2200"></canvas>
<script>
var canvas = document.getElementById("my_canvas");
var context = canvas.getContext("2d");
context.imageSmoothingEnabled =true;
context.fillStyle = "BLACK";
context.font = "bold 100px Arial";
context.fillText("A quick brown fox jumps over the lazy dOg", 50, 200);
</script>

This is an issue with the Chrome text engine.
There is a technique you can use to get around this though:
Setup an off-screen canvas double the size of the on-screen canvas
Draw the text to the off-screen canvas in scale: font size and position
Draw the off-screen canvas to main canvas scaled down (half in this case).
Live demo here
The difference is subtle (as expected) but improves the quality. Here is an enlargement:
Sidenotes: CSS does not affect content of the canvas, only elements. Image smoothing is enabled by default and affects only images but not text or shapes (we will use this though for this technique).
var scale = 2; // scale everything x2.
var ocanvas = document.createElement('canvas');
var octx = ocanvas.getContext('2d');
ocanvas.width = canvas.width * scale; // set the size of new canvas
ocanvas.height = canvas.height * scale;
// draw the text to off-screen canvas instead at double sizes
octx.fillStyle = "BLACK";
octx.font = "bold " + (100 * scale) + "px Arial";
octx.fillText("A quick brown fox jumps over the lazy dOg", 50*scale, 200*scale);
// key step is to draw the off-screen canvas to main canvas
context.drawImage(ocanvas, 0, 0, canvas.width, canvas.height);
What happens is that we are introducing interpolation in the mix here. Chrome will draw the text very rough also to the off-screen canvas, but after the text becomes rasterized we can deal with it as an image.
When we draw the canvas (aka image) to our main canvas at half the size, interpolation (sub-sampling) kicks in and will low-pass filter the pixels giving a smoother look.
It will of course take more memory but if result is important that is a small price to pay nowadays.
You will probably notice that other values than 2 doesn't work so well. This is because the canvas typically uses bi-linear interpolation rather than bi-cubic (which would allow us to use 4). But I think 2x serves us well in this case.
Why not using transform, ie. scale() ? Chrome's text engine (or the way it's used) does not rasterize the text at 1x and then transforms it. It takes the vectors, transforms them and then rasterize the text which will give the same result (ie. scale 0.5, draw double).

Related

Blend HTML text with canvas

I am currently rendering a canvas below some HTML elements (currently a h1 and a span). The canvas contains a kaleidoscope based on an image with two major colors: one pretty dark (almost black), and one really bright, and it can be moved by moving the mouse. The HTML elements are rendered with a color: white style.
The problem I encounter is when the kaleidoscope renders a huge white part. The text becomes invisible. Is it possible to make the text display the negative color of the part of the canvas right under it ? So for example, if the part of the canvas under the text is white, the text would be black ? Here is a screenshot of the problem:
You can set the color to white and use css blending-mode difference:
h1{
color:white;
mix-blend-mode: difference;
}
Demo
Sure, you can use the "difference" blending mode to make the text change color:
ctx.globalCompositeOperation = "difference";
ctx.fillText(myText, x, y);
Example
var ctx = c.getContext("2d");
var toggle = false;
for(var x = 0, step = c.width / 16; x < c.width; x += step) {
toggle = !toggle;
ctx.fillStyle = toggle ? "#000" : "#fff";
ctx.fillRect(x, 0, step, c.height);
}
ctx.globalCompositeOperation = "difference";
ctx.font = "32px sans-serif";
ctx.textAlign = "center";
ctx.fillText("ALTERNATING", c.width>>1, c.height>>1);
<canvas id=c></canvas>
Creative options: draw background slightly transparent to make white light-grey (setting the canvas element's CSS opacity), use shadow or outline for the text.

Scaling a canvas nicely with css

I'm trying to draw an image on a canvas, then use css to fit the canvas within a certain size. It turns out that many browsers don't scale the canvas down very nicely. Firefox on OS X seems to be one of the worst, but I haven't tested very many. Here is a minimal example of the problem:
HTML
<img>
<canvas></canvas>
CSS
img, canvas {
width: 125px;
}
JS
var image = document.getElementsByTagName('img')[0],
canvas = document.getElementsByTagName('canvas')[0];
image.onload = function() {
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
image.src = "http://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Helvetica_Neue_typeface_weights.svg/783px-Helvetica_Neue_typeface_weights.svg.png"
Running in a codepen: http://codepen.io/ford/pen/GgMzJd
Here's the result in Firefox (screenshot from a retina display):
What's happening is that both the <img> and <canvas> start at the same size and are scaled down by the browser with css (the image width is 783px). Apparently, the browser does some nice smoothing/interpolation on the <img>, but not on the <canvas>.
I've tried:
image-rendering, but the defaults seem to already be what I want.
Hacky solutions like scaling the image down in steps, but this didn't help: http://codepen.io/ford/pen/emGxrd.
Context2D.imageSmoothingEnabled, but once again, the defaults describe what I want.
How can I make the image on the right look like the image on the left? Preferably in as little code as possible (I'd rather not implement bicubic interpolation myself, for example).
You can fix the pixelation issue by scaling the canvas's backing store by the window.devicePixelRatio value. Unfortunately, the shoddy image filtering seems to be a browser limitation at this time, and the only reliable fix is to roll your own.
Replace your current onload with:
image.onload = function() {
var dpr = window.devicePixelRatio;
canvas.width = image.width * dpr;
canvas.height = image.height * dpr;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
Results:
Tested on Firefox 35.0.1 on Windows 8.1. Note that your current code doesn't handle browser zoom events, which could reintroduce pixelation. You can fix this by handling the resize event.
Canvas is not quite meant to be css zoomed : Try over-sampling : use twice the required canvas size, and css scaling will do a fine job in down-scaling the canvas.
On hi-dpi devices you should double yet another time the resolution to reach the
same quality.
(even on a standard display, X4 shines a bit more).
(Image, canvas 1X, 2X and 4X)
var $ = document.getElementById.bind(document);
var image = $('fntimg');
image.onload = function() {
drawAllImages();
}
image.src = "http://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Helvetica_Neue_typeface_weights.svg/783px-Helvetica_Neue_typeface_weights.svg.png"
function drawAllImages() {
drawImage(1);
drawImage(2);
drawImage(4);
}
function drawImage(x) {
console.log('cv' + x + 'X');
var canvas = $('cv' + x + 'X');
canvas.width = x * image.width;
canvas.height = x * image.height;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
img,
canvas {
width: 125px;
}
<br>
<img id='fntimg'>
<canvas id='cv1X'></canvas>
<canvas id='cv2X'></canvas>
<canvas id='cv4X'></canvas>
<br>
It's not good idea to scale canvas and think that you solved the image scale problem.you can pass your dynamic value to canvas,and then draw with that size whatever you want.
here is link of canvas doc: http://www.w3docs.com/learn-javascript/canvas.html
Simple answer, you can't do it. The canvas is just like a bitmap, nothing more.
My idea:
You should redraw the whole surface on zooming, and make sure you scale the image you're drawing to the canvas. As it is a vector graphic, this should work. But you're going to have to redraw the canvas for sure.

Rotate background-image in a rotated div?

I have a div shape with before: and after: so it looks like a cross shape (Rotated).
But now my problem is, that the background is logically also rotated. I'd like that the background image isn't rotated and the image should be the size of the div.
I already tried to add a transform rotate to the place where I added the background but it didnt rotate back. Also for the size I tried background-size to adjust it, didnt work either.
Here is my jsbin: http://jsbin.com/iYogaCE/29/edit
thanks in advance!
nick
Well, I tried for a while to get a version working with pure CSS and HTML, but I was unable to do so. I believe that double pseudo selectors, aka ::after and ::before, would allow it to be possible, but I don't think that you can do it in pure CSS in one object currently.
With that being said, the way I accomplished it using one element is the much more common way - by using a canvas. With canvas it becomes pretty simple. Hopefully the comments make it easy to understand
Live demo here
// Gets a list of all the canvases to create an X for
var canvases = document.getElementsByClassName('profile');
// Allows the X to be drawn on multiple canvases without being redrawn
var tempCanvas = drawX();
// Gives the canvases a background image (the person's profile)
// If you wanted different images for each you could easily create an array
// and iterate through it for each canvas
var background = new Image();
background.src = "http://asta-design.ch/gameotion/wp-content/uploads/2013/03/placeholder.jpg";
// Once the image has loaded, apply the Xs
background.onload = function() {
// Do it for each canvas
for(var i = 0, j = canvases.length; i < j; i ++)
{
// Gets the current canvas and context
var canvas = canvases[i];
var context = canvas.getContext('2d');
// Allows the portrait only to be shown through the generated X
context.globalCompositeOperation = "destination-atop";
// Draws the profile picture
context.drawImage(background, 0,0, canvas.width, canvas.height)
// Cuts out everything that is not within the X
context.drawImage(tempCanvas, 0, 0);
}
}
// Creates the X to use as the cut out
function drawX() {
// Creates a hidden canvas to draw the X on
var offscreenCanvas = document.createElement('canvas');
var offscreenCtx = offscreenCanvas.getContext('2d');
// The width/height of the original canvas, not sure why "canvas.width" doesn't work here...
var size = 200;
offscreenCanvas.width = size;
offscreenCanvas.height = size;
// Creates the rectangles sloped positively
offscreenCtx.save();
offscreenCtx.translate(3 * size / 4, 3 * size / 4);
offscreenCtx.rotate(Math.PI/4);
offscreenCtx.fillRect(-size/2, -size/2, size * .3, size);
// Loads the state before the first rectangle was created
offscreenCtx.restore();
// Creates the rectangles sloped positively
offscreenCtx.translate(3 * size / 4, 1 * size / 4);
offscreenCtx.rotate(-Math.PI/4);
offscreenCtx.fillRect(-size/2, -size/2, size * .3, size);
// Returns the canvas with the X
return offscreenCanvas;
}
You can't rotate a CSS background independently of the element it is attached to.
The only way you're going to be able to do this is to have the rotated content in an additional element inside your existing one, and only rotate the inner element.
eg:
<div> <-- background applied to this element
<div>....</div> <-- but this one is rotated
</div>
Now your background will remain static while the content inside it rotates.
If you can't have any extra markup, you could still achieve this without changing the HTML, by using CSS the :before selector to create an additional pseudo-element behind the main element. Apply the background to that instead of the main element; after that it's similar to what I described above with the extra markup.
Hope that helps.

CSS Skew only container, not content

I'm having trouble figuring out how to make the following layout work. I'm not restricted to pure CSS - I know JS will be involved to make it cross-browser - but a CSS solution would be awesome. Here's what I am trying to achieve:
I've tried the following code, skewing the container and then skewing the image in the opposite direction, but it just gives me a square image. Chrome inspector shows me that the container is being skewed properly, but skewing the image back makes it square again. Adding an overflow:hidden to the container kind of works but the edges of the angle become jagged. Here's what I have tried:
http://codepen.io/anon/pen/ubrFz
Please help! :)
Need to tweak the positioning and the size of the container so you can crop it, and apply the backface-visibility rule:
.skew {
-webkit-backface-visibility : hidden; /* the magic ingredient */
-webkit-transform : skew(16deg, 0);
overflow : hidden;
width : 300px;
height : 260px;
position : relative;
left : 50px;
border : 1px solid #666
}
.skew img {
-webkit-transform : skew(-16deg, 0);
position : relative;
left : -40px;
}
http://codepen.io/anon/pen/HLtlG <- before (aliased)
http://codepen.io/anon/pen/wnlpt <- after (anti-aliased)
In lieu of a CSS solution, you could also achieve the effect by using a canvas and some JS; and compositing a series of cropped images onto that canvas. The benefit of the canvas method being that you'll potentially get smoother edges on the crops, and it is potentially a bit better supported.
A canvas element in HTML;
<canvas id="mycanvas"></canvas>
And JS;
var img1 = new Image();
var img2 = new Image();
var img3 = new Image();
img1.src = '../my/image1.jpg';
img2.src = '../my/image2.jpg';
img3.src = '../my/image3.jpg';
var can = document.getElementById("mycanvas");
var ctx = can.getContext('2d');
var imgs = [img1, img2, img3]; //array of JS image objects that you've set up earlier
can.width = 1000;
can.height = 100;
for (var i=0; i < imgs.length; i++) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(800 - (200 * i), 0);
ctx.lineTo(900 - (200 * i), 100);
ctx.lineTo(0, 100);
ctx.closePath();
ctx.clip();
ctx.drawImage(imgs[i], 0, 0);
}
The code is just off the top of my head - I haven't tested it. But basically - lets say you have a canvas that is a maximum of 1000px wide and 100px high. What happens above is, you set up a clipping area with a diagonal line across the canvas from point (800,0) to (900,100) and then draw the image into that clipping area... Then set up a new clipping path 200 pixels shorter for each image (note the '200 * i' bit).
Obviously the math needs to be adjusted for an arbitrary number of images and so on... But the idea is there.
A bit trickier than pure CSS maybe - but as I said - possibly a bit better supported cross-browser (IE's notwithstanding...).
EDIT
Did a quick test - looks like you need to set the canvas dimensions - and also obviously wait for all images to load properly before you can composite them on the canvas.

How can I autoscale the font size to fit the contents of a div?

I have a div with some text:
<div style="white-space:nowrap;overflow:none;width:50px;">
With some text in it
</div>
How can I scale the font size of the text so all of the text is visible?
Contrary-wise. You could wrap the text in an interior DIV, measure its width with JavaScript. Test if that width is wider than the parent DIV. Get the current font size, and incrementally move it down 1px at a time until inner DIV's width is less than or equal to the outer DIV's width.
I've been doing something like this, to set the text scale relative to the parent (or window) width / height. You can avoid jQuery by using offsetWidth and offsetHeight instead of width.
var setBodyScale = function () {
var scaleSource = $(window).width(), // could be any div
scaleFactor = 0.055,
maxScale = 500,
minScale = 75; //Tweak these values to taste
var fontSize = (scaleSource * scaleFactor) - 8; //Multiply the width of the body by the scaling factor:
if (fontSize > maxScale) fontSize = maxScale;
if (fontSize < minScale) fontSize = minScale; //Enforce the minimum and maximums
$('html').css('font-size', fontSize + '%'); // or em
}
Short Answer: You don't.
You would have to try a size, render it, see if it fits, try another size, render it see if it fits, etc. Then you have to handle the case where the calculated font size is so small no one can read the text.
There are other options, if the text doesn't fit, add an ellipsis (...) to the end of the text, when you mouse over it, the div could expand, you could use a popup window or tooltip with the full text, or put the full text in a larger area of the screen.
Find another way.
Came across this JQuery plugin in my quest to find the same.
Github
Demo
Also came across this Jquery script when I was looking for the same thing. It has the added benefit over the others, as far as I quickly tell, is that it also adjusts for height as well as width.
Comes from here: http://www.metaltoad.com/blog/resizing-text-fit-container
function adjustHeights(elem) {
var fontstep = 2;
if ($(elem).height()>$(elem).parent().height() || $(elem).width()>$(elem).parent().width()) {
$(elem).css('font-size',(($(elem).css('font-size').substr(0,2)-fontstep)) + 'px').css('line-height',(($(elem).css('font-size').substr(0,2))) + 'px');
adjustHeights(elem);
}
}

Resources