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.
Related
Can an HTML canvas element be internally cropped to fit its content?
For example, if I have a 500x500 pixel canvas with only a 10x10 pixel square at a random location inside it, is there a function which will crop the entire canvas to 10x10 by scanning for visible pixels and cropping?
Edit: this was marked as a duplicate of Javascript Method to detect area of a PNG that is not transparent but it's not. That question details how to find the bounds of non-transparent content in the canvas, but not how to crop it. The first word of my question is "cropping" so that's what I'd like to focus on.
A better trim function.
Though the given answer works it contains a potencial dangerous flaw, creates a new canvas rather than crop the existing canvas and (the linked region search) is somewhat inefficient.
Creating a second canvas can be problematic if you have other references to the canvas, which is common as there are usually two references to the canvas eg canvas and ctx.canvas. Closure could make it difficult to remove the reference and if the closure is over an event you may never get to remove the reference.
The flaw is when canvas contains no pixels. Setting the canvas to zero size is allowed (canvas.width = 0; canvas.height = 0; will not throw an error), but some functions can not accept zero as an argument and will throw an error (eg ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height); is common practice but will throw an error if the canvas has no size). As this is not directly associated with the resize this potencial crash can be overlooked and make its way into production code.
The linked search checks all pixels for each search, the inclusion of a simple break when an edge is found would improve the search, there is still an on average quicker search. Searching in both directions at the same time, top and bottom then left and right will reduce the number of iterations. And rather than calculate the address of each pixel for each pixel test you can improve the performance by stepping through the index. eg data[idx++] is much quicker than data[x + y * w]
A more robust solution.
The following function will crop the transparent edges from a canvas in place using a two pass search, taking in account the results of the first pass to reduce the search area of the second.
It will not crop the canvas if there are no pixels, but will return false so that action can be taken. It will return true if the canvas contains pixels.
There is no need to change any references to the canvas as it is cropped in place.
// ctx is the 2d context of the canvas to be trimmed
// This function will return false if the canvas contains no or no non transparent pixels.
// Returns true if the canvas contains non transparent pixels
function trimCanvas(ctx) { // removes transparent edges
var x, y, w, h, top, left, right, bottom, data, idx1, idx2, found, imgData;
w = ctx.canvas.width;
h = ctx.canvas.height;
if (!w && !h) { return false }
imgData = ctx.getImageData(0, 0, w, h);
data = new Uint32Array(imgData.data.buffer);
idx1 = 0;
idx2 = w * h - 1;
found = false;
// search from top and bottom to find first rows containing a non transparent pixel.
for (y = 0; y < h && !found; y += 1) {
for (x = 0; x < w; x += 1) {
if (data[idx1++] && !top) {
top = y + 1;
if (bottom) { // top and bottom found then stop the search
found = true;
break;
}
}
if (data[idx2--] && !bottom) {
bottom = h - y - 1;
if (top) { // top and bottom found then stop the search
found = true;
break;
}
}
}
if (y > h - y && !top && !bottom) { return false } // image is completely blank so do nothing
}
top -= 1; // correct top
found = false;
// search from left and right to find first column containing a non transparent pixel.
for (x = 0; x < w && !found; x += 1) {
idx1 = top * w + x;
idx2 = top * w + (w - x - 1);
for (y = top; y <= bottom; y += 1) {
if (data[idx1] && !left) {
left = x + 1;
if (right) { // if left and right found then stop the search
found = true;
break;
}
}
if (data[idx2] && !right) {
right = w - x - 1;
if (left) { // if left and right found then stop the search
found = true;
break;
}
}
idx1 += w;
idx2 += w;
}
}
left -= 1; // correct left
if(w === right - left + 1 && h === bottom - top + 1) { return true } // no need to crop if no change in size
w = right - left + 1;
h = bottom - top + 1;
ctx.canvas.width = w;
ctx.canvas.height = h;
ctx.putImageData(imgData, -left, -top);
return true;
}
Can an HTML canvas element be internally cropped to fit its content?
Yes, using this method (or a similar one) will give you the needed coordinates. The background don't have to be transparent, but uniform (modify code to fit background instead) for any practical use.
When the coordinates are obtained simply use drawImage() to render out that region:
Example (since no code is provided in question, adopt as needed):
// obtain region here (from linked method)
var region = {
x: x1,
y: y1,
width: x2-x1,
height: y2-y1
};
var croppedCanvas = document.createElement("canvas");
croppedCanvas.width = region.width;
croppedCanvas.height = region.height;
var cCtx = croppedCanvas.getContext("2d");
cCtx.drawImage(sourceCanvas, region.x, region.y, region.width, region.height,
0, 0, region.width, region.height);
Now croppedCanvas contains only the cropped part of the original canvas.
I'm trying to create a chess board, and place it in the middle of the screen, so far i cannot get it to be directly in the center. i don't want to hard code the position to the screen because i'm going to be dealing with different screen sizes.
var winsize = cc.director.getWinSize();
var centerpos = cc.p(winsize.width / 2, winsize.height / 2);
for (i=0; i<64; i++){
var tile = cc.Sprite.create(res.tile_png);
this.addChild(tile,0);
tile.setPosition(winsize.width+i%8*50/-10, winsize.height-Math.floor(i/8)*50);
}
But the tiles and positioning is completely off
#jumpman8947, if you're using Cocos2d js perhaps you have a similar line: cc.view.setDesignResolutionSize(480, 320, cc.ResolutionPolicy.SHOW_ALL);
In this particular case the game will scale to any sceeen, but still run in 480x320 resolution, so no matter what screen resoultion you use, the center in the cocos world would always be cc.p(240, 160) so no matter what's the window size or the screen resolution, the resolution of the game stays the same
You can read more about resolution policies here (and in official js-doc):
http://www.cocos2d-x.org/wiki/Multiple_Resolution_Policy_for_Cocos2d-JS
Also please be aware, that the Sprite position in Cocos is the position of the centre of the sprite, not bottom left corner
In your question it's not completely clear exactly what you want. However, I made some assumptions. The explanation for my solution is embedded in the comments in the code below.
// var winsize = cc.director.getWinSize();
// Here is some example hard-coded return values:
var winsize = {width: 600, height: 400};
// You can change these numbers to see how they influence
// the outcome.
// var centerpos = cc.p(winsize.width / 2, winsize.height / 2);
// This line doesn't seem relevant for the question you asked.
// Or, rather, the following calculations will result in the tiles
// being centred on the screen anyway, so this calculation here
// is unnecessary.
// Being a chess board, I assume that you want the tiles to be square,
// i.e. to have the same width and height.
// If so, first find out which is the minimum dimension
// and calculate the tile size as being 1/8 of that.
var minDimn = Math.min(winsize.width, winsize.height);
var tileSize = minDimn / 8;
// Find out how far in from the left and how far down from the top
// you need the upper left corner of the upper left tile to start.
// This assumes that you don't need any "margin" around the board.
// (If you do need such a "margin", basically subtract it twice
// from each of winsize.width and winsize.height above.)
// Start with default values of 0 for each, but then add in the
// excess for the longer dimension, but divide it by two
// because that excess will be split between either
// the top and bottom or the left and right.
var xStart = 0, yStart = 0;
if (winsize.width > winsize.height) {
xStart = (winsize.width - winsize.height) / 2;
} else if (winsize.height > winsize.width) {
yStart = (winsize.height - winsize.width) / 2;
}
// Instead of looping through all 64 positions in one loop,
// loop through all the horizontal positions in an outer loop
// and all the vertical positions in an inner loop.
for (i = 0; i < 8; i++) {
// For the horizontal dimension, calculate x for each tile
// as the starting position of the left-most tile plus
// the width of the tile multiplied by the number of tiles (0-based)
var x = xStart + i * tileSize;
// Now the inner loop
for (j = 0; j < 8; j++) {
// Same type of calculation for the y value.
var y = yStart + j * tileSize;
// You can see the values in this demo here.
document.write("<pre>(" + x + ", " + y + ")</pre>");
// The following two lines don't seem to be relevant to the question.
// var tile = cc.Sprite.create(res.tile_png);
// this.addChild(tile,0);
// Use your calculated values in your function call.
// tile.setPosition(x, y);
}
}
I am having trouble drawing a line on the HTML canvas with JavaScript. For the record, I don't want to use any pre-written line-drawing methods, I need to do it using pixel manipulation. I tried drawing a line on a 500x500 pixel canvas that I already gave data with this
function drawBackgtoundAndLine()
{
var cnvs = document.getElementById("cnvs");
var cont = cnvs.getContext("2d")
var imdt = cont.getImageData(0,0,500,500)
//Fill canvas with a color
for ( var i = 0 ; i < imdt.data.length ; i = i + 4)
{
imdt.data[i] = 200;
imdt.data[i+1] = 100;
imdt.data[i+2] = 0;
imdt.data[i+3] = 255;
}
//Draw a horizontal line
var index = 0;
for ( var c = 0 ; c < 500 ; c++)
{
index = (4*c)+488000;
imdt.data[index] = 0;
imdt.data[index+1] = 0;
imdt.data[index+2] = 0;
imdt.data[index+3] = 255;
}
cont.putImageData( imdt , 0 , 0 )
}
You can see it in action in this fiddle. My math, by the way, that gave me the second for loop to draw a line is:
I want to color the whole 245th row. So, to pass over the first 244 rows, I multiply 2000(the number of data points in each row) times 244 rows to get 488000. Then I cycle through the loop 500 times to hit each pixel in the row, and add the 488000 to get to the right row. I'd really appreciate an explanation/fix for the 245th row not turning black.
You did not set the canvas size.
Note that the CSS size is only about display, not the number of pixels in the canvas.
You need to set the real canvas size for example with:
cnvs.width = 500;
cnvs.height = 500;
Remember that when you set the height/width the canvas is cleared (even if the value is the same as the current size), also remember that to get pixel-perfect graphics you need to keep the canvas size the same size as the element on the page (i.e. cnvs.offsetWidth and cnvs.offsetHeight).
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);
I am learning ways of manipulating HTML 5 Canvas, and decided to write a simple game, scroller arcade, for better comprehension. It is still at very beginning of development, and rendering a background (a moving star field), I encountered little, yet annoying issue - some of the stars are blinking, while moving. Here's the code I used:
var c = document.getElementById('canv');
var width = c.width;
var height = c.height;
var ctx = c.getContext('2d');//context
var bgObjx = new Array;
var bgObjy = new Array;
var bgspeed = new Array;
function init(){
for (var i = 1; i < 50; i++){
bgObjx.push(Math.floor(Math.random()*height));
bgObjy.push(Math.floor(Math.random()*width));
bgspeed.push(Math.floor(Math.random()*4)+1);
}
setInterval('draw_bg();',50);
}
function draw_bg(){
var distance; //distace to star is displayed by color
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0,0,width,height);
for (var i = 0; i < bgObjx.length; i++){
distance = Math.random() * 240;
if (distance < 100) distance = 100;//Don't let it be too dark
ctx.fillStyle = "rgb("+distance+","+distance+","+distance+")";
ctx.fillRect(bgObjx[i], bgObjy[i],1,1);
bgObjx[i] -=bgspeed[i];
if (bgObjx[i] < 0){//if star has passed the border of screen, redraw it as new
bgObjx[i] += width;
bgObjy[i] = Math.floor(Math.random() * height);
bgspeed[i] = Math.floor (Math.random() * 4) + 1;
}
}
}
As you can see, there are 3 arrays, one for stars (objects) x coordinate, one for y, and one for speed variable. Color of a star changes every frame, to make it flicker. I suspected that color change is the issue, and binded object's color to speed:
for (var i = 0; i < bgObjx.length; i++){
distance = bgspeed[i]*30;
Actually, that solved the issue, but I still don't get how. Would any graphics rendering guru bother to explain this, please?
Thank you in advance.
P.S. Just in case: yes, I've drawn some solutions from existing Canvas game, including the color bind to speed. I just want to figure out the reason behind it.
In this case, the 'Blinking' of the stars is caused by a logic error in determining the stars' distance (color) value.
distance = Math.random() * 240; // This is not guaranteed to return an integer
distance = (Math.random() * 240)>>0; // This rounds down the result to nearest integer
Double buffering is usually unnecessary for canvas, as browsers will not display the drawn canvas until the drawing functions have all been completed.
Used to see a similar effect when programming direct2d games. Found a double-buffer would fix the flickering.
Not sure how you would accomplish a double(or triple?)-buffer with the canvas tag, but thats the first thing I would look into.