I hava JS script that takes an image and draws it to a canvas. The code works perfectly fine in all browsers except Chrome. I have included the relevant
this.tile = document.createElement('canvas');
var ctx = this.tile.getContext("2d");
this.tile.width = ((size.cm_size) * (((this.image.width) / this.app.dpi.active) * 2.54)).toFixed(2);
this.tile.height = ((size.cm_size) * (((this.image.height) / this.app.dpi.active) * 2.54)).toFixed(2);
if (this.scale > 0) {
// These 2 lines break it in Chrome.
this.tile.width = this.tile.width * this.scale;
this.tile.height = this.tile.height * this.scale;
}
ctx.drawImage(this.image, 0, 0, this.image.width, this.image.height, 0, 0, this.image.width, this.image.height);
I have commented the 2 lines that break things in Chrome. Removing these lines makes things work.
With these lines in place, nothing is displayed until I increase the 'this.app.dpi.active' variable to 350 (it starts at 150). As soon as this happens it displays how it does in Firefox.
There are no error messages, the tile is just empty.
Here's a list of values for 'this.app.dpi.active' and the resulting tile width and height. The tile doesn't display until the value DPI hits 350.
150 DPI = 9444 x 9444
200 DPI = 7084 x 7084
250 DPI = 5668 x 5668
300 DPI = 4720 x 4720
350 DPI = 4048 x 4048
I have checked these vales in Chrome and Firefox and they are identical but things work in Firefox. The this.scale value = 4 and when the width and height aren't multipled by this.scale, it works fine in FF and Chrome.
Please let me know if you need any more code or have any questions.
I don't think you can use the Element constructor. I've always used document.createElement('canvas'). In my FF console, using the Element constructor throws an error.
Okay, I figured this one out myself but thank you everyone for your input!
I noticed that once an image got to a certain size, Chrome display it correctly. This led me to believe that Chrome doesn't allow canvas elements over a certain size (probably for memory/efficiency reasons). To fix this I re-designed my system slightly so that canvas elements never get too big and therefore always displayed correctly. This fixed the issue in Chrome and made my system a fair bit quicker.
Related
EDIT: originally I checked only desktop browsers - but with mobile browsers, the picture is even more complicated.
I came across a strange issue with some browsers and its text rendering capabilities and I am not sure if I can do anything to avoid this.
It seems WebKit and (less consistent) Firefox on Android are creating slightly larger text using the 2D Canvas library. I would like to ignore the visual appearance for now, but instead focus on the text measurements, as those can be easily compared.
I have used the two common methods to calculate the text width:
Canvas 2D API and measure text
DOM method
as outlined in this question: Calculate text width with JavaScript
however, both yield to more or less the same result (across all browsers).
function getTextWidth(text, font) {
// if given, use cached canvas for better performance
// else, create new canvas
var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
var context = canvas.getContext("2d");
context.font = font;
var metrics = context.measureText(text);
return metrics.width;
};
function getTextWidthDOM(text, font) {
var f = font || '12px arial',
o = $('<span>' + text + '</span>')
.css({'font': f, 'float': 'left', 'white-space': 'nowrap'})
.css({'visibility': 'hidden'})
.appendTo($('body')),
w = o.width();
return w;
}
I modified the fiddle a little using Google fonts which allows to perform text measurements for a set of sample fonts (please wait for the webfonts to be loaded first before clicking the measure button):
http://jsfiddle.net/aj7v5e4L/15/
(updated to force font-weight and style)
Running this on various browsers shows the problem I am having (using the string 'S'):
The differences across all desktop browsers are minor - only Safari stands out like that - it is in the range of around 1% and 4% what I've seen, depending on the font. So it is not big - but throws off my calculations.
UPDATE: Tested a few mobile browsers too - and on iOS all are on the same level as Safari (using WebKit under the hood, so no suprise) - and Firefox on Android is very on and off.
I've read that subpixel accuracy isn't really supported across all browsers (older IE's for example) - but even rounding doesn't help - as I then can end up having different width.
Using no webfont but just the standard font the context comes with returns the exact same measurements between Chrome and Safari - so I think it is related to webfonts only.
I am a bit puzzled of what I might be able to do now - as I think I just do something wrong as I haven't found anything on the net around this - but the fiddle is as simple as it can get. I have spent the entire day on this really - so you guys are my only hope now.
I have a few ugly workarounds in my head (e.g. rendering the text on affected browsers 4% smaller) - which I would really like to avoid.
It seems that Safari (and a few others) does support getting at sub-pixel level, but not drawing...
When you set your font-size to 9.5pt, this value gets converted to 12.6666...px.
Even though Safari does return an high precision value for this:
console.log(getComputedStyle(document.body)['font-size']);
// on Safari returns 12.666666984558105px oO
body{font-size:9.5pt}
it is unable to correctly draw at non-integer font-sizes, and not only on a canvas:
console.log(getRangeWidth("S", '12.3px serif'));
// safari: 6.673828125 | FF 6.8333282470703125
console.log(getRangeWidth("S", '12.4px serif'));
// safari: 6.673828125 | FF 6.883331298828125
console.log(getRangeWidth("S", '12.5px serif'));
// safari 7.22998046875 | FF 6.95001220703125
console.log(getRangeWidth("S", '12.6px serif'));
// safari 7.22998046875 | FF 7
// High precision DOM based measurement
function getRangeWidth(text, font) {
var f = font || '12px arial',
o = $('<span>' + text + '</span>')
.css({'font': f, 'white-space': 'nowrap'})
.appendTo($('body')),
r = document.createRange();
r.selectNode(o[0]);
var w = r.getBoundingClientRect().width;
o.remove();
return w;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
So in order to avoid these quirks,
Try to always use px unit with integer values.
I found below solution from MDN more helpful for scenarios where fonts are slanted/italic which was for me the case with some google fonts
copying the snippet from here - https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Measuring_text_width
const computetextWidth = (text, font) => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.font = font;
const { actualBoundingBoxLeft, actualBoundingBoxRight } = context.measureText(text);
return Math.ceil(Math.abs(actualBoundingBoxLeft) + Math.abs(actualBoundingBoxRight));
}
i have a pretty simple but very annoying problem. I try to read the width of a string in a canvas. I know how to do this, and it works. But the results differ from browser to browser.
ctx.font = "10px Arial";
var txt = "This is a text demonstration. Why is the width of this text different in every browser??";
ctx.fillText("width:" + ctx.measureText(txt).width, 10, 50);
ctx.fillText(txt, 10, 100);
Here a little fiddle: http://jsfiddle.net/83v7c4jv/
Chrome: 390px, IE: 375px, Firefox: 394px. Only IE is accurate, since C# gives me the same result if i try this there. Does anybody know why and how i can get Chrome and Firefox to render and calculate the same values like IE?
you need to read this answer
it worked for me in some project , i used this code to get height of the text because it not exist it will work the same as text width
obj.lineHeight = function(){
var testDiv = document.createElement('div'); // creating div to measure text in
testDiv.style.padding= "0px";
testDiv.style.margin = "0px";
testDiv.style.backgroundColor = "white";
testDiv.textContent = "Hello World !";
testDiv.style.fontSize = obj.size+"px";
testDiv.style.fontFamily = obj.fontFamily;
testDiv.style.clear = "both";
testDiv.style.visibility="hidden";
document.body.appendChild(testDiv);
var __height__ = testDiv.clientHeight;
testDiv.style.display = "none";
document.body.removeChild(testDiv);
return __height__ ;
};
First of all, the same font may be rendered different in different browsers. Pay attention on the following picture. I just putted together screenshots of your JSFIDDLE running on Chrome (the first) and IE (the second). As you can see, the text width actually is not the same, and the numbers that ctx.measureText returns are correct in the both cases.
C# gives you the same number as IE because they use the same text rendering algorithm, but it has no meaning when your page runs on other browser.
You can found some tricks and hacks in this thread, but in fact you cannot really control the browser rendering mechanism. If you want to ensure your text to be shown exactly the same on all the browsers and only way is to turn it into an image.
I've searched exhaustively for an answer, and I'm hoping that stackoverflow will come to the rescue once again.
If we set the font size of a context, it seems to allow arbitrary precision:
fontSize = 11.654321;
context.font = 'bold ' + fontSize + 'px sans-serif ';
Inspecting the context object will show that font size has been set with extreme precision. I have noticed, however, that the measureText() method of the context object always returns an integer value (presumably a ceiling value). Does anyone know the actual precision used in displaying text (when using pixel-based font sizes)?
A link to documentation containing this information would be sufficient (as long as it actually informs about precision).
In case anyone asks, I'm trying to adjust the font to fit text to a given width by doing something like this:
var fontSize = 12;
context.font = 'bold ' + fontSize + 'px sans-serif ';
var text = "whatever";
var maxWidth = 200;
var currentWidth = context.measureText(text).width;
var adjustment = maxWidth / currentWidth;
fontSize *= adjustment;
context.font = 'bold ' + fontSize " 'px sans-serif ';
For Chromium and Firefox, it appears that the effective precision is at the integer level (I have not tested Internet Explorer). I've created this jsFiddle to research the issue, and it appears from my research that in Chromium and Firefox, font sizes specified with sub-integer precision are effectively rounded to the nearest integer.
The tests I've done assume that the measureText() method is functioning correctly (which may be a false assumption). The tests have shown some odd behavior for this method (more obvious in Chromium than in Firefox).
Using a fixed length string of a repeated character ('m'), I have graphed the measured size of the string as a function of its font size. When using 0.1 for the font size input step size, the output string width remains constant for step sizes of 1.0. These "steps" are centered around the integer font size, and jumps occur at midpoints between integers.
The odd behavior observed in Chromium is that at times, the input font size can be varied as much as 2 pixels, with no change in the measured width of the string. This behavior is unexpected, but occurs at regular intervals (offset from zero). The following intervals of input values resulted in no change in measured width: [3.5, 5.5), [12.5, 14.5), [21.5, 23.5), [30.5, 32.5), and so on. The centers of these "big flat steps" seem to be 9 pixels apart (4.5, 13.5, 22.5, 31.5). The first one having a center around half the spacing of the interval between the rest maybe a clue if this is indeed a bug.
In Firefox, the odd behavior is much more subtle. If you change the input step size to 1, the graph is nearly linear (as expected).. but periodically the graph gets slightly steeper for an interval of 1. For example, this occurs at [11.5, 12.5) and [35.5, 36.5).
If I am overlooking something, or if there is some error in my methods, please comment and let me know. I am not too concerned about the Firefox behavior, but as of now, I consider the behavior in Chromium to be a bug. I may file a bug report, and if so, I will update my answer to include a link.
I'm trying to draw a huge canvas rectangle on top of the page (some kind of lightbox background), the code is quite straightforward:
var el = document.createElement('canvas');
el.style.position = 'absolute';
el.style.top = 0;
el.style.left = 0;
el.style.zIndex = 1000;
el.width = window.innerWidth + window.scrollMaxX;
el.height = window.innerHeight + window.scrollMaxY;
...
document.body.appendChild(el);
// and later
var ctx = el.getContext("2d");
ctx.fillStyle = "rgba(0, 0, 0, 0.4)";
ctx.fillRect(0, 0, el.width, el.height);
And sometimes (not always) the last line throws:
Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIDOMCanvasRenderingContext2D.fillRect]
I've been guessing if that happens because of image size or because of the content type beneath the canvas (e.g. embeded video playing), but apparently not.
So I'm looking for any ideas on how to isolate and/or solve this issue.
Looking at the nsIDOMCanvasRenderingContext2D.fillRect() implementation (and going through the functions it calls) - there aren't all too many conditions that will return NS_ERROR_FAILURE. It can only happen if either EnsureSurface() or mThebes->CopyPath() fail. And the following two lines in EnsureSurface() are most likely the source of your issue:
// Check that the dimensions are sane
if (gfxASurface::CheckSurfaceSize(gfxIntSize(mWidth, mHeight), 0xffff)) {
What's being checked here:
Neither the width nor the height of the canvas can exceed 65535 pixels.
The height cannot exceed 32767 pixels on Mac OS X (platform limitation).
The size of canvas data (width * height * 4) cannot exceed 2 GB.
If any of these conditions is violated EnsureSurface() will return false and consequently produce the exception you've seen. Note that the above are implementation details that can change at any time, you shouldn't rely on them. But they might give you an idea which particular limit your code violates.
You could apply a try-catch logic. Firefox seems to be the only browser which behaves this a bit odd way.
el.width = window.innerWidth + window.scrollMaxX;
el.height = window.innerHeight + window.scrollMaxY;
// try first to draw something to check that the size is ok
try
{
var ctx = el.getContext("2d");
ctx.fillRect(0, 0, 1, 1);
}
// if it fails, decrease the canvas size
catch(err)
{
el.width = el.width - 1000;
el.height = el.height - 1000;
}
I haven't found any variable that tells what is the maximum canvas size. It varies from browser to browser and device to device.
The only cross browser method to detect the maximum canvas size seems to be a loop that decreases the canvas size eg. by 100px until it doesn't produce the error. I tested a loop, and it is rather fast way to detect the maximum canvas size. Because other browsers doesn't throw an error when trying to draw on an over sized canvas, it is better to try to draw eg. red rect and read a pixel and check if it is red.
To maximize detection performance:
- While looping, the canvas should be out of the DOM to maximize speed
- Set the starting size to a well known maximum which seems to be 32,767px (SO: Maximum size of a <canvas> element)
- You can make a more intelligent loop which forks the maximum size: something like using first bigger decrease step (eg.1000px) and when an accepted size is reached, tries to increase the size by 500px. If this is accepted size, then increase by 250px and so on. This way the maximum should be found in least amount of trials.
It seems certain tiles will not get drawn. I have a tileset split-up into 32x32 squares and uses a 2D 100x100 array to draw the map onto the canvas.
Then it sets the "viewport" for the player. Since it's one big map, the player is always centered on the edge, unless they run near the edge.
However, this has caused problems drawing the map. Red block is "player"
Somethings I found out was that, a higher viewport (15x10) will give the ability to draw SOME previously not-drawn tiles.
Here's the code. You can download the tileset to test on localhost or below on jsFiddle http://mystikrpg.com/images/all_tiles.png
Everything below is well commented.
Even if change viewport I do see some tiles get drawn, not all.
http://pastebin.com/cBTum1aQ
Here is jsFiddle: http://jsfiddle.net/weHXU/
Working Demo
Basically I took a different approach to drawing the tiles. Using putImageData and getImageData can cause performance issues, also by pre-allocating all the image data in the beginning you are incurring a memory penalty to start out with. You already have the data in the form of the image you might as well just reference it directly instead, and it should actually be faster.
Heres the method I use
for (y = 0; y <= vHeight; y++) {
for (x = 0; x <= vWidth; x++) {
theX = x * 32;
theY = y * 32;
var tile = (board[y+vY][x+vX]-1),
tileY = Math.floor(tile/tilesX),
tileX = Math.floor(tile%tilesX);
context.drawImage(imageObj, tileX*tileWidth, tileY*tileHeight, tileWidth, tileHeight, theX, theY, tileWidth, tileHeight);
}
}
Its almost the same, but instead of looking at an array of the pre saved data, I just reference the area of the already loaded image.
Explanation on getting the referenced tile
Say I have a grid thats 4x4, and I need to get tile number 7, to get the y I would do 7/4, then to get the x I would use the remainder of 7/4 (7 mod 4).
As for the original issue.. I really have no idea what was causing the missing tiles, I just changed it to my method so I could test from there but it worked right away for me.