My client is experiencing a bizarre bug in Firefox 12 on Windows: a blue tinted box is appearing around the drawn image in the canvas.
The canvas is inside an iframe inside a fancybox div. You may be able to see this in action by clicking the image thumbnail under the main image on this site:
http://mattmatthias.com/a/index.php?route=product/product&path=20&product_id=80
At first, I thought it was a selection issue, although the fact that the drawn image itself rather than the whole canvas seems to refute that. I've tried again and again to blur the canvas, the container div, the iframe... everything, to no avail.
What's worse, I can't reproduce this bug. Everything functions normally in Safari, Firefox, Chrome, and Opera on my mac.
This is probably the offending code, as it's the only part of the code that draws anything:
if(imageWidth == 0) return;
context.clearRect(0, 0, canvasWidth, canvasHeight);
var x_adjust = -x-(ratio*canvasWidth -canvasWidth )/2;
var y_adjust = -y-(ratio*canvasHeight -canvasHeight )/2;
var width = scaledWidth*ratio;
var height = scaledHeight*ratio;
if(x_adjust < canvasWidth - width)
x_adjust = canvasWidth - width;
if(x_adjust > 0)
x_adjust = 0;
if(y_adjust < canvasHeight - height)
y_adjust = canvasHeight - height;
if(y_adjust > 0)
y_adjust = 0;
if(width < canvasWidth) {
x_adjust += (canvasWidth - width) / 2;
}
if(height < canvasHeight) {
y_adjust += (canvasHeight - height) / 2;
}
context.drawImage(image, 0, 0,
imageWidth, imageHeight,
x_adjust, y_adjust,
width, height);
Any ideas? As I get more details, I will post them here.
This isn't related to the canvas; just opening the image in Firefox shows the same thing. So it's a problem with reading/rendering the image.
This Mozilla support issue sounds like precisely what's happening here, and it's related to a color management issue.
And indeed, removing the color profile from the image file makes the image look correct.
Depending on the particular case, instead of just stripping the profile you may want to do some conversion instead; whether that's necessary depends on the particular image, profile, and on how big the probability is that color management even remotely has a chance of producing something meaningful on your visitors' monitors (unless you cater to graphic designers: probably not very big).
Related
I need to build a kind of map in canvas, which must be able to hold more than 10.000 elements and thus has quiet big dimensions in some cases (> 8000px width, >4000 px height). Also I need to pan and zoom the map.
After some fiddeling around with existing libraries (Paper.js) and possible other solutions (Leaflet Map) I eventually wrote an own library from scratch, because the main requirement is, that is should be really really fast (loading, mouseovers, ...) and none of the libraries I tried could offer all of the aspects.
The structure is as follows:
I have one map object with an associated Control object, which registers events and has resize methods etc.
A map is divided in mutliple even sized tiles (1024px x 1024px - customizable) because using the map with only one canvas at a size over 8000px width made it incredibly slow
Each tile is associated with a canvas
The elements (just circles) are added to one or multiple tiles (If it's on the edge) - more specifically to the tiles' canvas.
The tiles are placed within an container div which has the dimensions of the map area (when not zoomed out)
The container div is placed within a viewport div to enable the map being displayed as a "widget"
Zooming scales every tile/canvas and the container. For sake of performance I sacrificed smooth zoom and implemented a customizable amount of zoom steps, which still feels okay.
Panning set's the topand left style of the container.
Events used are window.resize, mousewheel, DOMMouseScrol, mousedown, mouseup, mousemove, touchstart,touchend,touchmove and Hammertime pinch
This alltogether runs satisfying on Desktop Browsers, and iPhones (tested with SE, 6S) but on every Android device I tested it (Samsung S4, One Plus One and another 1 year old device, and android studio emulator) it runs extremly slow. Drawing of the Map is fine in speed, but zooming and panning is near to impossible.
The code is too comprehensive to post it here, so I'm asking you if there are any known problems with canvas on android, that could explain this problem, or maybe some issues with the way I built the structure that could produce issues with android. I'm really clueless here, since it works on desktop and iPhone.
The real problem you're hitting is you're overloading the GPU. Loading that much data all and once then moving it around is going to put a toll on the GPU and likely force the browser into software rendering mode, which is a big performance hit.
Instead, I'd suggest changing your approach. Rather than having various large canvases, you should have one canvas that is, at most, the size of the users screen. Then, utilize methods of the canvas API such as scale and translate to render what you need. For an added bonus, avoid trying to render things which are off screen.
It may seem like having to redraw the scene every time you move around would be slow but it's not. The reality is that either you specify exactly what needs to be drawn or the browser has to attempt to draw all of it again when you shift it around. Here's a brief example of how you can render and move large images.
var ctx = document.querySelector('canvas').getContext('2d');
var img = new Image();
img.src = 'https://placeimg.com/1000/1000/nature';
img.onload = start;
function start() {
var xDirection = -1;
var yDirection = -1;
var xPosition = 0;
var yPosition = 0;
var prev = Date.now();
(function render() {
var now = Date.now();
var delta = (now - prev) / 1000;
xPosition += xDirection * delta * 20;
yPosition += yDirection * delta * 40;
if (xPosition > 0) {
xPosition = 0;
xDirection *= -1;
} else if (xPosition < -320) {
xPosition = -320;
xDirection *= -1;
}
if (yPosition > 0) {
yPosition = 0;
yDirection *= -1;
} else if (yPosition < -240) {
yPosition = -240;
yDirection *= -1;
}
prev = now;
ctx.save();
ctx.translate(xPosition, yPosition);
ctx.drawImage(img, 0, 0);
ctx.restore();
requestAnimationFrame(render);
})();
}
body {
background: #111;
}
canvas {
background: #FFF;
}
<canvas width="320" height="240"></canvas>
Here my full code on jsfiddle:
https://jsfiddle.net/6u7bLkwc/14/
Before exposing my problem, you can create a triangles on the image like this:
Click on the image, then drag your mouse in order to create it.
What is the problem with my code ?
When i use this script, i have many lags on my PC ... especially for larger images !
What i want ?
Try to use my script without loop to prevent lags.
Issue come from this part of code:
function draw() {
ctx.drawImage(imageObj, 0, 0);
shapes.forEach(function(shape) {
var hue = Math.floor(Math.random() * 360);
ctx.fillStyle = shape.getColor();
ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
});
if (isMouseDown) {
ctx.fillStyle = color;
ctx.fillRect(mouseDownX, mouseDownY, mouseX - mouseDownX, mouseY - mouseDownY);
}
}
Exactly this line:
ctx.drawImage(imageObj, 0, 0);
This line is used to apply a background image to canvas ... since i want use this script for bigger images (Like 500ko and up) and loop it many time !!! this will create a self DDOSING attack to my computer.
Why not use this line outside of draw function ?
Because when i use it outside, and mouse hover the created rectangle ... i see a black background ... but like this i see my photo. (Mouse hover a rectangle make it transparent).
Any fix or any idea on how to fix it ? any different approch ?
The reason you exspierncing lag i mostly due to this function;
can.addEventListener('mousemove', function(e) {
mouseX = e.pageX;
mouseY = e.pageY;
if (isMouseDown) return;
var hoveredAlready = false;
for (var i = shapes.length - 1; i > -1; i--) {
var shape = shapes[i];
if (mouseX < shape.getLeft() || mouseY < shape.getTop()
|| mouseX > shape.getRight()
|| mouseY > shape.getBottom()
|| hoveredAlready) {
console.log(shape);
shape.unHover();
} else {
shape.hover();
hoveredAlready = true;
}
}
});
By allowing the image to be hovered in your example with a single exsitsing rectangle it's fine but as soon as you have say like 4 and they overlap this function is being called about 100 times in less than a second so for some users they shall experience lag.
I managed to replicated your problem while trying to debug this:
Also removing the function while it does break the applications functionality does remove the lag.
Some things I'd consider doing to work back and eliminate lag, Sort out boundaries more efficiently so you know when the users cursor is within the boundaries and adjust for coming on and off the image or your going to get some really sideways looking rectangles, Consider how your transparency may affect multiple objects on different 'levels' and also consider minimizing the amount you write to console as that adds to the overhead.
I am attempting to use javascript to take a picture with my iphone and draw the image to the canvas. I use
<input type="file" capture="camera" accept="image/*" id="takePictureField">
mobilePictureGet);
to use the camera. Once I choose a picture
document.getElementById("takePictureField").addEventListener('change', function(e) {
if(e.target.files.length == 1 && e.target.files[0].type.indexOf("image/") == 0) {
img.src = URL.createObjectURL(e.target.files[0]);
}
}
this fires, and the image appears how I want it in the tag. (so far so good), However, I draw it to the canvas when img tag has loaded:
img.onload = function() {
ctx.drawImage(img, 0, 0, img.width,img.height);
}
and it appears rotated -90 degrees. I have tried
var x = width / 2;
var y = height / 2;
var angleInRadians = Math.PI / 2;
ctx.translate(x, y);
ctx.rotate(angleInRadians);
ctx.drawImage(img, -width / 2, -height / 2, img.width, img.height);
ctx.rotate(-angleInRadians);
ctx.translate(-x, -y);
but I can't get it to fit.
Does anyone know why the canvas would draw the image rotated? Any help to get the picture drawn correctly on the canvas is much appreciated!!
Thanks!
It probably depends on how you hold your phone. That (orientation) information is stored in the exif data of the image and you have to rotate it accordingly.
One library that can help you with this is JavaScript Load Image
I've never tried to take a picture with javascript, but I do some ios mobile development.
The reason the picture comes out sideways at all, is because apple has deemed that holding your phone with the home button facing right is the only acceptable way to take a picture. This is also known as it's 'up' state.
More information on why your iPhone takes pictures sideways can be found here: http://rotatemailer.com/sideways-pictures.html
In ios it's very easy to rotate it, so in javascript i'm not too sure how to properly do that.
I'm trying to draw a diagram with canvas and want to get crisp lines, not anti-aliased. I know about the 0.5 offset you need to use to make lines fall exactly on screen pixels, but even with that I get anti-aliased lines in Firefox, while both Chrome and IE render it fine.
Here's some example code:
JS:
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
canvas.width = 100;
canvas.height = 100;
ctx.translate(-0.5, -0.5); //To get crisp lines
ctx.lineWidth = 1;
ctx.strokeStyle = 'black';
for (var x = 20; x < 100; x += 20){
ctx.moveTo(x, 20);
ctx.lineTo(x,100);
ctx.stroke();
}
See JsFiddle: http://jsfiddle.net/einaregilsson/9yrF6/8/
This is what it looks like in Chrome and IE:
This is what it looks like in Firefox:
This is Firefox 26 on Windows 7. I've tried turning off hardware acceleration, which someone suggested but that makes no difference. Any ideas how I can get crisp lines on Firefox?
Also, is there anyone on Firefox that doesn't get anti-aliased lines when they look at the Fiddle? I'm wondering if this is a general Firefox issue, or particular to my setup.
It looks like you're slightly zoomed-in on Firefox (notice how the lines are spaced slightly further apart)
Hit Ctrl+0 to reset the zoom level. This should fix your problem.
It should be crisp in Firefox too, you probably have zoomed in.
Reset the zoom (CTRL+0)
I have a tricky question that might just have a simple solution, although I trully don't see it now.
So, I've been working around HTML5 element and, obviously, doing the interaction methodology in JavaScript.
One of the objectives of this work is to be able to use a mobile device [MD] (iOS or Android, phone or tablet) as a remote controller for an application that will be served by another machine (eg. a laptop or external display) and both will be showing the same thing on each of the screens on different scales.
So, I wanna have an event occur when the canvas is 80% filled (or in this case, "erased" (which I already have by calculating the total number of [initial] pixels) and each device has a different count since the screen sizes/resolutions are different.
This is the tricky part: How will I be able to "scale" the MD pixel count and mirror that to the bigger screen?
For concrete measures, how will I be able to implement the following example:
I draw a line on the MD that goes for 300px wide, and for simplicity, let's say that this represents 10% of the MD canvas (which on both the screens is in fullscreen).
I want the external monitor (which has a higher resolution) to mirror this event but on an appropriate scale so that those 10% on the MD represent the same (scaled) 10% of "canvas real estate"
Just in case the text is too confusing, I'll leave the code bellow:
function totalPix(x, y) {
var total = x * y;
var objective = (total * 80) / 100;
}
function canvasApp() {
//prevent from scrolling (no bouncing)
document.body.addEventListener('touchmove', function(event){
event.preventDefault();
}, false);
if(!canvasSupport()) {
alert("No canvas support on this device!");
return;
} else if(!socketSupport) {
alert("No websocket support on this device!");
} else {
//create canvas on every load (//TODO)
var elemDiv = document.getElementById("content");
var newElem = document.createElement("canvas");
newElem.setAttribute("id", "frontscreen");
elemDiv.appendChild(newElem);
drawScreen();
function drawScreen() {
//Setup canvas
var canvas = document.getElementById("frontscreen");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
totalPix(canvas.width, canvas.height);
ctx = canvas.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
//Foreach touchmove event, send position to server
canvas.addEventListener('touchmove', function(event) {
for (var i = 0; i<event.touches.length; i++) {
var touch = event.touches[i];
ctx.globalCompositeOperation = "destination-out";
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(touch.pageX, touch.pageY, 30, 0, 2*Math.PI, false);
ctx.fill();
ctx.stroke();
}
}, false);
window.onresize = function resizeCanvas() {drawScreen();};
}
}
}
If I understand you correctly, it's as simple as changing the size property on the HTML style property of your <canvas> element. For example, let's say you fill in a 300 by 300 px. square on Monitor A, which occupies 10% of the screen real estate (I know, big monitor). Then you load the same page on Monitor B, which is twice the size of Monitor A. (Really really big monitor, just bear with me here. It's an example.) Naturally, it will only occupy 5% of the screen's real estate.
If you want that 300px to always occupy the same percentage of size on all screens (but still be 300px on the canvas), you can do something like this:
var canvas = document.getElementById("mycanvas");
var heightAsPercent = 10;
var widthAsPercent = 10;
canvas.style.height = (heightAsPercent / 100) * screen.height;
canvas.style.width = (widthAsPercent / 100) * screen.width;
That way, the canvas will always occupy 10% of the screen, whether the monitor width is 3000px or 6000px. I've obviously chosen very verbose variable names for clarity, so feel free to modify them as needed.
The reason this works is that you're only modifying the CSS properties of the canvas, which affect only how it's rendered, not the actual <canvas> data. I came across this little trick by accident, and it drove me nuts until I figured out why it was doing this. Now it actually comes in handy. :)