html5 canvas - How can I stretch text? - javascript

In my html 5 canvas, I draw text (that has to be on 1 line) but it needs to be a constant font size and style and the width of the space I have is also constant. Therefore the text needs to fit in that space, but the problem occurs when the text is too long and goes past the space.
So is there a way I can horizontally stretch/compress text? (like in PhotoShop)
Something like convert it to an image then change the width of the image? Not sure if this is the best way...
Thanks

You can use measureText to determine the size of the text first, then scale the canvas if needed: http://jsfiddle.net/eGjak/887/.
var text = "foo bar foo bar";
ctx.font = "30pt Arial";
var width = ctx.measureText(text).width;
if(width <= 100) {
ctx.fillText(text, 0, 100);
} else {
ctx.save();
ctx.scale(100 / width, 1);
ctx.fillText(text, 0, 100);
ctx.restore();
}

A simple solution is to draw your text in another (in memory) canvas and then use drawImage to paste the canvas content in the real destination canvas.
Something like this (let it be parameterizable depending on your needs, here stretching with a ratio of 100 to 80) :
var tempimg = document.createElement('canvas');
tempimg.width = 100;
tempimg.height = 10;
oc = tempimg.getContext('2d');
oc.fillText(...)
yourdestcontext.drawImage(tempimg, 0, 0, 100, 10, x, y, 80, 10);

Related

How can I get the visible height of a font? [duplicate]

I know how to get this height of a font:
By placing the text in a div and getting offset height of the div.
But I would like to get this actual height (Which will depend on font family):
Is that in any way possible using web based programming?
Is there a simple solution? I think the answer is no.
If you're ok with a more involved (and processor-intensive) solution, you could try this:
Render the text to a canvas, then use canvasCtx.getImageData(..) to retrieve pixel information. Next you would do something similar to what this pseudo code describes:
first_y : null
last_y : null
for each y:
for each x:
if imageData[x][y] is black:
if first_y is null:
first_y = y
last_y = y
height = last_y - first_y
This basically looks for the top (lowest y-index) of the lettering (black pixels) and the bottom (highest y-index) then subtracts to retrieve the height.
I was writing the code while Jason answered, but I decided to post it anyway:
http://jsfiddle.net/adtn8/2/
If you follow the comments you should get the idea what's going on and why. It works pretty fast and it's not so complicated as it may sound. Checked with GIMP and it is accurate.
(code to be sure it wont be lost):
// setup variables
var c = document.createElement('canvas'),
div = document.getElementsByTagName('div')[0],
out = document.getElementsByTagName('output')[0];
// set canvas's size to be equal with div
c.width = div.offsetWidth;
c.height = div.offsetHeight;
var ctx = c.getContext('2d');
// get div's font from computed style and apply it to context
ctx.font = window.getComputedStyle(div).font;
// use color other than black because all pixels are 0 when black and transparent
ctx.fillStyle = '#bbb';
// draw the text near the bottom of the canvas
ctx.fillText(div.innerText, 0, div.offsetHeight);
// loop trough the canvas' data to find first colored pixel
var data = ctx.getImageData(0, 0, c.width, c.height).data,
minY = 0, len = data.length;
for (var i = 0; i < len; i += 4) {
// when you found it
if (data[i] != 0) {
// get pixel's y position
minY = Math.floor(i / 4 / c.width);
break;
}
}
// and print out the results
out.innerText = c.height - minY + 'px';
EDIT:
I even made jQuery plugin for this: https://github.com/maciek134/jquery-textHeight
Enjoy.

Canvas grid gets blurry at different zoom levels

I am trying to create a simple canvas grid which will fit itself to the player's current zoom level, but also to a certain canvas height/width proportional screen limit. Here is what I got so far:
JS:
var bw = window.innerWidth / 2; //canvas size before padding
var bh = window.innerHeight / 1.3; //canvas size before padding
//padding around grid, h and w
var pW = 30;
var pH = 2;
var lLimit = 0; //9 line limit for both height and width to create 8x8
//size of canvas - it will consist the padding around the grid from all sides + the grid itself. it's a total sum
var cw = bw + pW;
var ch = bh + pH;
var canvas = $('<canvas/>').attr({width: cw, height: ch}).appendTo('body');
var context = canvas.get(0).getContext("2d");
function drawBoard(){
for (var x = 0; lLimit <= 8; x += bw / 8) { //handling the height grid
context.moveTo(x, 0);
context.lineTo(x, bh);
lLimit++;
}
for (var x = 0; lLimit <= 17; x += bh / 8) { //handling the width grid
context.moveTo(0, x); //begin the line at this cord
context.lineTo(bw, x); //end the line at this cord
lLimit++;
}
//context.lineWidth = 0.5; what should I put here?
context.strokeStyle = "black";
context.stroke();
}
drawBoard();
Now, I succeeded at making the canvas to be at the same proportional level for each screen resolution zoom level. this is part of what I am trying to achieve. I also try to achieve thin lines, which will look the same at all different zooming levels, and of course to remove the blurriness. right now the thickness
of the lines change according to the zooming levels and are sometimes blurry.
Here is jsFiddle (although the jsFiddle window itself is small so you will barely notice the difference):
https://jsfiddle.net/wL60jo5n/
Help will be greatly appreciated.
To prevent blur, you should account for window.devicePixelRatio when setting dimensions of your canvas element (and account for that dimensions during subsequent drawing, of course).
width and height properties of your canvas element should contain values that are proportionally higher than values in CSS properties of the same names. This can be expressed e.g. as the following function:
function setCanvasSize(canvas, width, height) {
var ratio = window.devicePixelRatio,
style = canvas.style;
style.width = '' + (width / ratio) + 'px';
style.height = '' + (height / ratio) + 'px';
canvas.width = width;
canvas.height = height;
}
To remove blurry effect on canvas zoom/scale i used image-rendering: pixelated in css
The problem is that you are using decimal values to draw. Both the canvas width and the position increments in your drawBoard() loop use fractions. The canvas is a bitmap surface, not a vectorial drawing. When you set the width and height of the canvas, you set the actual number of pixels stored in memory. That value cannot be decimal (browsers will probably just trim the decimal part). When you try to draw at decimal positions, the canvas will use pixel interpolation to avoid aliasing, hence the occasional blur.
See a version where I round x before drawing:
https://jsfiddle.net/hts7yybm/
Try rounding the values just before you draw them, but not in your actual logic. That way, the imprecision won't stack as the algorithm keeps adding to the value.
function drawBoard(){
for (var x = 0; lLimit <= 8; x += bw / 8) {
var roundedX = Math.round(x);
context.moveTo(roundedX, 0);
context.lineTo(roundedX, bh);
lLimit++;
}
for (var x = 0; lLimit <= 17; x += bh / 8) {
var roundedX = Math.round(x);
context.moveTo(0, roundedX);
context.lineTo(bw, roundedX);
lLimit++;
}
context.lineWidth = 1; // never use decimals
context.strokeStyle = "black";
context.stroke();
}
EDIT: I'm pretty sure all browsers behave as if the canvas was an img element, so there's no way to prevent aliasing when the user zooms with their browser's zoom function, other than with prefixed css. And even then, I'm not sure the browsers's zoom feature takes that into account.
canvas {
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
-ms-interpolation-mode: nearest-neighbor;
}
Also, make sure the canvas doesn't have any CSS-set dimensions. That only stretches the image after it's been drawn instead of increasing the drawing surface. If you want to fill a block with the canvas by giving it 100% width and height, then you need some JS to compute the CSS-given height and width and set the value of the canvas's width and height property based on that. Then you can make your own implementation of a zoom function within your canvas drawing code, but depending on what you're doing it might be overkill.

Responsive canvas with fixed line width

I'm drawing a line chart with canvas. The chart is responsive, but the line has to have a fixed width.
I made it responsive with css
#myCanvas{
width: 80%;
}
,so the stroke is scaled.
The only solution I have found is to get the value of the lineWidth with the proportion between the width attribute of the canvas and its real width.
To apply it, I clear and draw the canvas on resize.
<canvas id="myCanvas" width="510" height="210"></canvas>
<script type="text/javascript">
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function draw(){
var canvasattrwidth = $('#myCanvas').attr('width');
var canvasrealwidth = $('#myCanvas').width();
// n sets the line width
var n = 4;
var widthStroke = n * (canvasattrwidth / canvasrealwidth) ;
ctx.lineWidth = widthStroke;
ctx.beginPath();
ctx.moveTo( 0 , 10 );
ctx.lineTo( 200 , 100 );
ctx.stroke();
}
$(window).on('resize', function(){
ctx.clearRect(0, 0, c.width, c.height);
draw();
});
draw();
</script>
This is my first canvas and I think there is an easier way to made the lineWidth fixed (not to clear and draw everytime on resize), but I cannot find it.
There is a question with the similar problem
html5 canvas prevent linewidth scaling
but with the method scale(), so I cannot use that solution.
There is no way to get a real world dimension of details for the canvas such as millimeters or inches so you will have to do it in pixels.
As the canvas resolution decreases the pixel width of a line needs to decrease as well. The limiting property of line width is a pixel. Rendering a line narrower than a pixel will only approximate the appearance of a narrower line by reducing the opacity (this is done automatically)
You need to define the line width in terms of the lowest resolution you will expect, within reason of course and adjust that width as the canvas resolution changes in relation to this selected ideal resolution.
If you are scaling the chart by different amounts in the x and y directions you will have to use the ctx.scale or ctx.setTransform methods. As you say you do not want to do this I will assume that your scaling is always with a square aspect.
So we can pick the lowest acceptable resolution. Say 512 pixels for either width or height of the canvas and select the lineWidth in pixels for that resolution.
Thus we can create two constants
const NATIVE_RES = 512; // the minimum resolution we reasonably expect
const LINE_WIDTH = 1; // pixel width of the line at that resolution
// Note I Capitalize constants, This is non standard in Javascript
Then to calculate the actual line width is simply the actual canvas.width divided by the NATIVE_RES then multiply that result by the LINE_WIDTH.
var actualLineWidth = LINE_WIDTH * (canvas.width / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
You may want to limit that size to the smallest canvas dimension. You can do that with Math.min or you can limit it in the largest dimension with Math.max
For min dimention.
var actualLineWidth = LINE_WIDTH * (Math.min(canvas.width, canvas.height) / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
For max dimension
var actualLineWidth = LINE_WIDTH * (Math.max(canvas.width, canvas.height) / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
You could also consider the diagonal as the adjusting factor that would incorporate the best of both x and y dimensions.
// get the diagonal resolution
var diagonalRes = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height)
var actualLineWidth = LINE_WIDTH * (diagonalRes / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
And finally you may wish to limit the lower range of the line to stop strange artifacts when the line gets smaller than 1 pixel.
Set lower limit using the diagonal
var diagonalRes = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height)
var actualLineWidth = Math.max(1, LINE_WIDTH * (diagonalRes / NATIVE_RES));
ctx.lineWidth = actualLineWidth;
This will create a responsive line width that will not go under 1 pixel if the canvas diagonal resolution goes under 512.
The method you use is up to you. Try them out a see what you like best. The NATIVE_RES I picked "512" is also arbitrary and can be what ever you wish. You will just have to experiment with the values and method to see which you like best.
If your scaling aspect is changing then there is a completely different technique to solve that problem which I will leave for another question.
Hope this has helped.

Viewing transparent image and deciding where does the opacity starts and ends on X and Y coordinates

I have PNG image that is has a lot of white space that must be there. Let's say the image is 1000 by 1000 pixels and it has a black square 100 by 100 pixels located at 450px by 450px which is exactly in the center (the black square could be anywhere on the transparent image).
Now, is there a way to load the big image (the 1000x1000 pixel one) and search to find the X and Y position of the black square that is in the middle?
This should be cross browser compatible or at lest the major browser compatible.
The example on here: http://jsfiddle.net/cwolves/GaEeG/2/ shows how to see if the mouse is over the transparent part of an image. The problem is that I'm not sure how to scan with the code to get where transparency opacity starts from X and Y side of the image. It also seems to work only on Chrome.
var imgData,
width = 200,
height = 200;
$('#mask').bind('mousemove', function(ev){
if(!imgData){ initCanvas(); }
var imgPos = $(this).offset(),
mousePos = {x : ev.pageX - imgPos.left, y : ev.pageY - imgPos.top},
pixelPos = 4*(mousePos.x + height*mousePos.y),
alpha = imgData.data[pixelPos+3];
$('#opacity').text('Opacity = ' + ((100*alpha/255) << 0) + '%');
});
function initCanvas(){
var canvas = $('<canvas width="'+width+'" height="'+height+'" />')[0],
ctx = canvas.getContext('2d');
ctx.drawImage($('#mask')[0], 0, 0);
imgData = ctx.getImageData(0, 0, width, height);
}
I can only use PHP or JavaScript/plugins to do this.
I hope there's a way to do this. Thanks.

Transparent Background not working

I'm making a small simple game in HTML and Javascript but I've run into an error. The sprite I've made on the "canvas" is not able to reach the canvas borders (the edge of the game world). After looking at it alot, I deduced it wasn't the code but the fact that the background of the image isn't transparent. But this makes no sense because the image file does have a transparent background.
How can I completely get rid of the background? Or is something in the code causing the sprite to have it's own border?
Image file:
What it looks like when run
http://prntscr.com/4btb32
Code:
// JavaScript Document
var canvasWidth = 800;
var canvasHeight = 600;
$('#gameCanvas').attr('width', canvasWidth);
$('#gameCanvas').attr('height', canvasHeight);
var keysDown = {};
$('body').bind('keydown', function(e){
keysDown[e.which] = true;
});
$('body').bind('keyup', function(e){
keysDown[e.which] = false;
});
var canvas = $('#gameCanvas')[0].getContext('2d');
var FPS = 30;
var image = new Image();
image.src = "ship.png";
var playerX = (canvasWidth/2) - (image.width/2);
var playerY = (canvasHeight/2) - (image.height/2);
setInterval(function() {
update();
draw();
}, 1000/FPS);
function update(){
if(keysDown[37]){
playerX -= 10;
}
if(keysDown[38]){
playerY -= 10;
}
if(keysDown[39]){
playerX += 10;
}
if(keysDown[40]){
playerY += 10;
}
playerX = clamp(playerX, 0, canvasWidth - image.width);
playerY = clamp(playerY, 0, canvasHeight - image.height);
}
function draw() {
canvas.clearRect(0,0, canvasWidth, canvasHeight);
canvas.strokeRect(0, 0, canvasWidth, canvasHeight);
canvas.drawImage(image, playerX, playerY);
}
function clamp(x, min, max){
return x < min ? min : (x > max ? max : x);
}
Thanks,
Ab
Your clamp function is making is so the x and y values are always between 0 and the canvas size minus the image size. This makes it so that your image will never leave the canvas (making the full image always on the canvas), and your image size is actually a bit larger than the ship image. The transparency of the image has nothing to do with the ship leaving the canvas, it has to do with the size. If you want to be able to have the ship leave the canvas, or get right next to the edge then decrease the size of the image to not have a border on it.
Alternatively you could have your clamp function clamp to 0 - image.width and canvasWidth + image.width (or height). This will allow the ship to fully disappear off the canvas.
Whether or not the sprite has a transparent background, the height and width of the image are still being used. you need to create your sprite in a way that the ship fills the entire canvas that you are painting it on.
The image will be as wide and tall as your background... so your ship wont reach the edge because there is still a background on your image that extend beyond your ship.

Categories