HTML5 Canvas image gets blurred when repeatedly copyied on itself - javascript

I have this problem with scrolling an tiled image, but only on mobile (android) browsers..
The image is "scrolled" by repainting it on itself, I had a version using an offscreen buffer but no difference. The coordinates are parsed to integers, as the touch events give floats. If this is not done, the effect is even worse. The canvas width/height attr are set equal to the css style width/height so the canvas is not scaled.
When i repeatedly scroll the canvas following touchmove events using the following code, the rendered image gets blurred more and more on mobile chrome browser - not so on desktop browsers.
See the image attached, the tiles in the middle are the affected, those at the borders get redrawn.
What am I doing wrong? Is there another way of scrolling a canvas?
worldRenderer.prototype.offset = function(dx,dy) {
dx = parseInt(dx);
dy = parseInt(dy)
this.ctx.drawImage(this.canvas, dx, dy);
var obounds = this.calcVisible();
this.options.offsetx += dx;
this.options.offsety += dy;
var bounds = this.calcVisible();
var x0 = bounds[0];
var x1 = bounds[2];
if (dx>0) {
this.paintRect(this.ctx, [bounds[0],bounds[1],obounds[0],bounds[3]]);
x0=obounds[0];
} else if (dx<0) {
this.paintRect(this.ctx, [obounds[2],bounds[1],bounds[2],bounds[3]]);
x1=obounds[2];
}
if (dy>0) {
this.paintRect(this.ctx, [x0,bounds[1],x1,obounds[1]] );
} else if (dy<0) {
this.paintRect(this.ctx, [x0,obounds[3],x1,bounds[3]]) ;
}
}

Related

Unexpected border pattern in a grid with rectangles in p5.js

I am trying to generate a square grid like pattern in p5js that covers as much of the browser window as possible.I am using p5js in instance mode as I using this with react and I am using chrome in Win10.
Here is my code:-
var size = 15;
var height = window.innerHeight;
var width = window.innerWidth;
Sketch = (p) => {
p.setup = () => {
p.createCanvas(width,height)
p.frameRate(60);
p.noLoop();
}
p.draw = () => {
p.background(250);
p.stroke(0);
p.noFill();
for(let i =0;i*size +size <width;i++) {
for(let j=0;j*size +size<height;j++) {
p.rect(i*size,j*size,size,size);
}
}
}
p.mouseDragged = (e) => {
p.stroke(0);
let x = Math.floor(e.clientY/size);
let y = Math.floor(e.clientX/size);
p.fill(220);
p.rect(y*size,x*size,size,size);
}
}
I call p.noLoop() so it doesnt refreshes everytime and I also have a button that calls p.redraw() to change everything to default. Here is the grid and behaviour I get:
The borders of grids are of varying sizes, first they decrease then increase then decrease and so on. Also, the area around which I drag my mouse has even more weird borders(This gets resolved when I click somewhere else so is this a GPU Aliasing rendering issue?). How do I create grid with same borders throughout my screen?
Edit: When I render even a single box, it has issues. The left and upper border are fine. However the right and down borders have an extra pixel of grayish borders which seems to be the problem. How do I fix this?
Also, How does strokeWeight and rect work in p5js? If I do strokeWeight(10) and rect(3,2,50,50), does that create a 50 by 50 rectangle with 10 pixels borders all around or the borders are included in the rectangle size?

Transparent HTML5 canvas todataurl only rendering transparent background on mobile devices

EDITED, see end of question.
In my application I have two canvas elements. One shows layered, transparent pngs, the other one gets an image from a file input and masks it. The chosen image is transparent where it is not masked. This image is then converted to a dataUrl, transformed to fit into the first canvas and added as the top layer of the first canvas.
Everything works as expected on desktop browsers: Chrome OSX, Safari OSX. I only add it in on load, so I made sure no race conditions can occur.
On Android Chrome and Safari iOS the canvas converted todataURL is rendered transparent. If I add a non-transparent image to the second canvas, the rendered image will show even on mobile devices.
To check I added the supposedly transparent canvas to the body. It shows correctly on desktop, but is transparent on mobile Browsers. Here the simplified JS. I am using fabric.js for convenience, but the problem is the same without the lib. I even once added a background color. Then only the color will show. Any ideas why todataurl on mobile browsers renders only transparent pixels?
<body>
<canvas id="canv"></canvas>
<script src="fabric.js"></script>
<script>
// main canvas
var c = new fabric.Canvas('canv');
c.setWidth(200);
c.setHeight(200);
var i = document.createElement('img');
i.src = 'dummy.jpg';
// i.src = 'dummy1.png';
i.onload = function(e) {
//document.body.appendChild(i);
scale = 1; // resizes the image
var ci = new fabric.Image(i);
ci.set({
left: 0,
top: 0,
scaleX: scale,
scaleY: scale,
originX: 'left',
originY: 'top'
}).setCoords();
// temporary canvas, will be converted to dataurl, contains transformed image
var tmpCanvas = new fabric.Canvas();
tmpCanvas.setWidth(100);
tmpCanvas.setHeight(100);
ci.scaleToWidth(100);
tmpCanvas.add(ci);
tmpCanvas.renderAll();
// create image from temporary canvas
var customImage = new fabric.Image.fromURL(tmpCanvas.toDataURL({ format: 'png' }), function (cImg) {
// add it to original canvas
c.clear();
c.add(cImg);
c.renderAll();
data = c.toDataURL({ format: 'png' });
// resized image
var newc = new fabric.StaticCanvas().setWidth(300).setHeight(300);
var newImg = new fabric.Image.fromURL(data, function (c1Img) {
newc.add(c1Img);
newc.renderAll();
// append to body to check if canvas is rendered correctly
document.body.appendChild(newc.lowerCanvasEl);
});
});
}
</script>
EDIT: I solved the problem, but could not find the problem on the Javascript side.
The problem was that I copied a temporary canvas onto another canvas. The scale and position of the added canvas was computed by finding the bounding box of non transparent pixels in a png, which was generated exactly for this purpose. A mask in short.
The bounding box was calculated in another temporary canvas at the start of the app (based on this answer). Although all sizes of the mask and its canvas were set correctly and the canvas was never added to the DOM, when loaded on a small screen the results of the bounding box differed from from the full screen results. After much testing i found this was true on Desktop too.
Because I already spent so much time on the problem, I decided to try to calculate the bounds in PHP and put it into a data attribute. Which worked great!
For those interested in the PHP solution:
function get_bounding_box($imgPath) {
$img = imagecreatefrompng($imgPath);
$w = imagesx($img);
$h = imagesy($img);
$bounds = [
'left' => $w,
'right' => 0,
'top' => $h,
'bottom' => 0
];
// get alpha of every pixel, if it is not fully transparent, write it to bounds
for ($yPos = 0; $yPos < $h; $yPos++) {
for ($xPos = 0; $xPos < $w; $xPos++) {
// Check, ob Pixel nicht vollständig transparent ist
$rgb = imagecolorat($img, $xPos, $yPos);
if (imagecolorsforindex($img, $rgb)['alpha'] < 127) {
if ($xPos < $bounds['left']) {
$bounds['left'] = $xPos;
}
if ($xPos > $bounds['right']) {
$bounds['right'] = $xPos;
}
if ($yPos < $bounds['top']) {
$bounds['top'] = $yPos;
}
if ($yPos > $bounds['bottom']) {
$bounds['bottom'] = $yPos;
}
}
}
}
return $bounds;
}
The problem was that I copied a temporary canvas onto another canvas. The scale and position of the added canvas was computed by finding the bounding box of non transparent pixels in a png, which was generated exactly for this purpose. A mask in short.
The bounding box was calculated in another temporary canvas at the start of the app (based on this answer). Although all sizes of the mask and its canvas were set correctly and the canvas was never added to the DOM, when loaded on a small screen the results of the bounding box differed from from the full screen results. After much testing i found this was true on Desktop too.
Because I already spent so much time on the problem, I decided to try to calculate the bounds in PHP and put it into a data attribute. Which worked great!
For those interested in the PHP solution:
function get_bounding_box($imgPath) {
$img = imagecreatefrompng($imgPath);
$w = imagesx($img);
$h = imagesy($img);
$bounds = [
'left' => $w,
'right' => 0,
'top' => $h,
'bottom' => 0
];
// get alpha of every pixel, if it is not fully transparent, write it to bounds
for ($yPos = 0; $yPos < $h; $yPos++) {
for ($xPos = 0; $xPos < $w; $xPos++) {
// Check, ob Pixel nicht vollständig transparent ist
$rgb = imagecolorat($img, $xPos, $yPos);
if (imagecolorsforindex($img, $rgb)['alpha'] < 127) {
if ($xPos < $bounds['left']) {
$bounds['left'] = $xPos;
}
if ($xPos > $bounds['right']) {
$bounds['right'] = $xPos;
}
if ($yPos < $bounds['top']) {
$bounds['top'] = $yPos;
}
if ($yPos > $bounds['bottom']) {
$bounds['bottom'] = $yPos;
}
}
}
}
return $bounds;
}

Move a HTML5 Canvas Element

I'm making a small jigsaw like puzzle in html5. Each jigsaw piece is it's own canvas. I need to move the canvas element using the mouse position. I've managed to get the canvas that is clicked, I just need to move it. I tried manipulating the top and left style attributes but the canvas didn't move. Can this be done or am I trying something impossible.
Thanks!
function MouseDown(can, e)
{
MovingCanvas = can;
clicked = true;
}
function MouseMove(e)
{
if(clicked)
{
var mx = e.clientX;
var my = e.clientY;
MovingCanvas.style.top = my;
MovingCanvas.style.left = mx;
}
}
e.clientX and e.clientY are integers.
Styles expect a string of the form {NUMBER}{UNIT}.
You are missing a unit, therefore it won't work.
MovingCanvas.style.top = my+"px";
MovingCanvas.style.left = mx+"px";

jQuery and Canvas loading behaviour

After being a long time lurker, this is my first post here! I've been RTFMing and searching everywhere for an answer to this question to no avail. I will try to be as informative as I can, hope you could help me.
This code is for my personal webpage.
I am trying to implement some sort of a modern click-map using HTML5 and jQuery.
In the website you would see the main image and a hidden canvas with the same size at the same coordinates with this picture drawn into it.
When the mouse hovers the main picture, it read the mouse pixel data (array of r,g,b,alpha) from the image drawn onto the canvas. When it sees the pixel color is black (in my case I only check the RED value, which in a black pixel would be 0) it knows the activate the relevant button.
(Originally, I got the idea from this article)
The reason I chose this method, is for the page to be responsive and dynamically change to fit different monitors and mobile devices. To achieve this, I call the DrawCanvas function every time the screen is re-sized, to redraw the canvas with the new dimensions.
Generally, this works OK. The thing is ,there seems to be an inconsistent behavior in Chrome and IE(9). When I initially open the page, I sometimes get no pixel data (0,0,0,0), until i re-size the browser. At first I figured there's some loading issues that are making this happen so I tried to hack it with setTimeout, it still doesn't work. I also tried to trigger the re-size event and call the drawCanvas function at document.ready, still didn't work.
What's bothering me is most, are the inconsistencies. Sometimes it works, sometimes is doesn't. Generally, it is more stable in chrome than in IE(9).
Here is the deprecated code:
<script type="text/javascript">
$(document).ready(function(){setTimeout(function() {
// Get main image object
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
// Create a hidden canvas the same size as the main image and append it to main div
var canvas = document.createElement('canvas');
canvas.height = mapWrapper.clientHeight;
canvas.width = mapWrapper.clientWidth;
canvas.fillStyle = 'rgb(255,255,255)';
canvas.style.display = 'none';
canvas.id = 'hiddencvs';
$('#map_wrapper').append(canvas);
// Draw the buttons image into the canvas
drawCanvas(null);
$("#map_wrapper").mousemove(function(e){
var canvas = document.getElementById('hiddencvs');
var context = canvas.getContext('2d');
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
// Get pixel information array (red, green, blue, alpha)
var pixel = context.getImageData(x,y,1,1).data;
var red = pixel[0];
var main_img = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
if (red == 0)
{
...
}
else {
...
}
});
},3000);}); // End DOM Ready
function drawCanvas(e)
{
// Get context of hidden convas and set size according to main image
var cvs = document.getElementById('hiddencvs');
var ctx = cvs.getContext('2d');
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
cvs.width = mapWrapper.clientWidth;
cvs.height = mapWrapper.clientHeight;
// Create img element for buttons image
var img = document.createElement("img");
img.src = "img/main-page-buttons.png";
// Draw buttons image inside hidden canvas, strech it to canvas size
ctx.drawImage(img, 0, 0,cvs.width,cvs.height);
}
$(window).resize(function(e){
drawCanvas(e);
}
);
function findPos(obj)
{
...
}
</script>
I'd appreciate any help!
Thanks!
Ron.
You don't wait for the image to be loaded so, depending on the cache, you may draw an image or not in the canvas.
You should do this :
$(function(){
var img = document.createElement("img");
img.onload = function() {
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
...
// your whole code here !
...
}
img.src = "img/main-page-buttons.png";
});

iPad HTML5 canvas not refreshing

Edit: Demo Code is found in the "parallelogram explorer" tab here: http://atmdev01.procloud.net/geometry_tools9/
So I'm calling the following javascript function at document load to draw the perimeter of a parallelogram, and it is working just fine to do that. The issue is when I call the function from the touchmove handler to allow an iPad user to adjust the size of the parallelogram, the canvas is not properly redrawing the shape. In fact it is not responding at all. I've run some alerts to verify that this function is actually being run and it is.
Could it be an issue with the way I'm clearing the canvas (the "canvas.width = canvas.width + 0" method) or simply with refresh rates on the iPad?
The interesting part is that it's working perfectly in a desktop browser using mousemove, but not on an iPad using touchmove. Please advise...
(the "corners" in the code are the areas where the user can touch and drag to resize the parallelogram)
this.drawSides = function() {
var order = [1, 2, 4, 3];
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var firstCorner = this.getCornerObj(1);
var secondCorner = this.getCornerObj(2);
var firstCornerOpp = this.getCornerObj(firstCorner.opp);
var secondCornerOpp = this.getCornerObj(secondCorner.opp);
/* Clear the canvas and draw a parallelogram with the provided corners */
canvas.width = canvas.width + 0; //clears the canvas faster than clearRect
ctx.beginPath();
for (i in order) {
if (i < order.length) {
var cornerObj = this.getCornerObj(order[i]);
if (order[i] > 1) {
var prevObj = this.getCornerObj(order[i-1]);
ctx.moveTo(prevObj.x + (prevObj.width)/2, prevObj.y + (prevObj.height)/2);
ctx.lineTo(cornerObj.x + (cornerObj.width)/2, cornerObj.y + (cornerObj.height)/2);
}
}
}
ctx.lineTo(firstCorner.x + (firstCorner.width)/2, firstCorner.y + (firstCorner.height)/2);
ctx.strokeStyle = "#300";
ctx.stroke();
}
The canvas isn't cleared properly with canvas.width = canvas.width; in safari.
Keep in mind that it's less computationally expensive to clear the canvas using context.clearRect() than it is to reset the canvas size. This is really important for mobile devices and tablets.
Reference: http://www.html5rocks.com/en/tutorials/canvas/performance/#toc-clear-canvas
I've resolved this issue. Turns out the issue was that I wasn't properly updating my cornerObjects' x and y coordinates on touchmove. (the code excerpt above has no issue)
Also, for future reference, canvas.width = canvas.width + 0; works just fine on Safari and the iPad.

Categories

Resources