javascript canvas doesn't draw images - javascript

I'm developing hybrid javascript app for android using cordova.
In the following code I use two ways of drawing image on canvas: with setTimeout and without.
Following code (wrapped with cordova) on android device doesn't react on func1 but does react on func2. The second click on func1 finally draws image on a canvas.
Which is completely strange.
I assume it has something to do with android device performance because on my desktop PC both functions works fine.
Why this happening? How to avoid using setTimeout?
<html style="background: white;">
<head>
</head>
<body>
<button onclick="func1()">render img2 func1</button>
<button onclick="func2()">render img2 func2</button><br />
<canvas id="canv">canv</canvas>
<script>
var img = new Image();
var canvas = document.getElementById('canv');
canvas.width = 100;
canvas.height = 100;
var ctx = canvas.getContext("2d");
function setSrc() {
img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAASUlEQVRo3u3PAQ0AIAwDsIGC+TcLLkhOWgddSU6Ga5udT4iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi8cQEjUgGTmE6z3QAAAABJRU5ErkJggg=="
};
function drawImg() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};
function func1() {
setSrc();
drawImg();
};
function func2() {
setSrc();
setTimeout(function () {
drawImg();
}, 500);
};
</script>
</body>
</html>

It's not so strange as image loading is asynchronous. You are trying to draw the image before it has loaded. On the second click the image has loaded so therefor it will be drawn.
You need to use a callback mechanism in order to make this work:
function setSrc(callback) {
img.onload = callback; /// when image is loaded this will be called
img.src = 'data: ...snipped...';
};
function drawImg() {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};
Then modify your function slightly:
function func1() {
setSrc(drawImg); /// drawImg is now callback function for setSrc.
};
Note: if you need to for example draw things on top of the image you must continue your code from the callback function. For example add a callback also for the drawImg function which calls the next step in the code after the image has been drawn. This is because as mentioned, image loading is asynchronous and if you attempt to draw anything else before the image has been loaded the image will be drawn on top instead

Related

How to fix clearRect?

Just as an introduction, I'm a beginner with JS so maybe the answer to the following problem is simple but I'm just not seeing it.
I have the following code:
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d');
canvas.width = window.innerWidth
canvas.height = window.innerHeight
let img = new Image();
img.onload = function() { ctx.drawImage(img, 0, 0); };
img.src = './tree.jpg';
Which works, so no problem here.
Then I have:
ctx.clearRect(0, 0, 300, 300);
img.src = './tree.jpg';
ctx.drawImage(img, 730, 720);
And for some reason that eludes me, the clearRect doesn't want to work in that sequence.
Any suggestions are kindly appreciated.
Thanks
clearRect() does not work as intended here since the image hasn't been drawn on the canvas yet. What has happened here is the canvas gets cleared and then the image loads.
As Mozilla says here,
If you try to call drawImage() before the image has finished loading, it won't do anything (or, in older browsers, may even throw an exception). So you need to be sure to use the load event so you don't try this before the image has loaded:
Similiar to how you're using drawImage() in the onload(), clear your Rectangle after your image has loaded and been drawn.
img.onload = function() {
ctx.drawImage(img, 0, 0);
ctx.clearRect(0, 0, 300, 300);
};

How to wait for the multiple images to load in the canvas and convert to base64?

I'm trying to get a base64 version of a canvas in HTML5.
Currently, the base64 image that I get from the canvas is blank.
I have found similar questions for example this one:
HTML Canvas image to Base64 problem
However, the issue that I have is that because I am adding 2 images in the canvas, I cannot use the example provided in those answers as most of them are using single image.
I understand that I have to wait until the canvas image is loaded properly before trying to get the base64 image. But I don't know how in my case.
This is my code:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.canvas.width = 1000;
context.canvas.height = 1000;
var imageObj1 = new Image();
imageObj1.src = "http://www.offthegridnews.com/wp-content/uploads/2017/01/selfie-psuDOTedu.jpg";
imageObj1.onload = function() {
context.drawImage(imageObj1, 0, 180, canvas.width, canvas.height);
};
var imageObj2 = new Image();
imageObj2.src = "http://www.publicdomainpictures.net/pictures/150000/velka/banner-header-tapete-145002399028x.jpg"
imageObj2.onload = function() {
context.drawImage(imageObj2, 0, 0, canvas.width, 180);
};
// get png data url
//var pngUrl = canvas.toDataURL();
var pngUrl = canvas.toDataURL('image/png');
// get jpeg data url
var jpegUrl = canvas.toDataURL('image/jpeg');
$('#base').val(pngUrl);
<div class="contents" style="overflow-x:hidden; overflow-y:scroll;">
<div style="width:100%; height:90%;">
<canvas id="myCanvas" class="snap" style="width:100%; height:100%;" onclick="takephoto()"></canvas>
</div>
</div>
<p>
This is the base64 image
</p>
<textarea id="base">
</textarea>
and this is a working FIDDLE:
https://jsfiddle.net/3p3e6Ldu/1/
Can someone please advice on this issue?
Thanks in advance.
EDIT:
As suggested in the comments bellow, i tried to use a counter and when the counter reaches a specific number, I convert the canvas to base64.
Like so:https://jsfiddle.net/3p3e6Ldu/4/
In both your examples (the one from the question and the one from the comments), the order of commands does not really respect the async nature of the task at hand.
In your later example, you should put the if( count == 2 ) block inside the onload callbacks to make it work.
However, even then you will run into the next problem: You are loading the images from different domains. You can still draw them (either into the canvas or using an <img> tag), but you are not able to access their contents directly. Not even with the detour of using the <canvas> element.
I changed to code so it would work, if the images are hosted on the same domain. I also used a function to load the image and promises to handle the callbacks. The direct way of using callbacks and a counting variable, seem error-prone to me. If you check out the respective fiddle, you will notice the SecurityError shown. This is the result of the aforementioned problem with the Same-Origin-Policy I mentioned.
A previous question of mine in a similar direction was about how to detect, if I can still read the contents of a <canvas> after adding some images.
const canvas = document.getElementById('myCanvas');
const context = canvas.getContext('2d');
context.canvas.width = 1000;
context.canvas.height = 1000;
// function to retrieve an image
function loadImage(url) {
return new Promise((fulfill, reject) => {
let imageObj = new Image();
imageObj.onload = () => fulfill(imageObj);
imageObj.src = url;
});
}
// get images
Promise.all([
loadImage("http://www.offthegridnews.com/wp-content/uploads/2017/01/selfie-psuDOTedu.jpg"),
loadImage("http://www.publicdomainpictures.net/pictures/150000/velka/banner-header-tapete-145002399028x.jpg"),
])
.then((images) => {
// draw images to canvas
context.drawImage(images[0], 0, 180, canvas.width, canvas.height);
context.drawImage(images[1], 0, 0, canvas.width, 180);
// export to png/jpg
const pngUrl = canvas.toDataURL('image/png');
const jpegUrl = canvas.toDataURL('image/jpeg');
// show in textarea
$('#base').val(pngUrl);
})
.catch( (e) => alert(e) );

Android Cordova - How to draw photo taken on canvas?

i'm working with an hybrid Android app and I need to take a picture from camera, process the image and then show the results obtained on another window that should contain a small preview of the picture taken and some text with the results.
Here is the relevant code that is not currently working as I need:
From script of first window:
.
.
.
CameraPreview.takePicture(function(imgData){
var mImage = new Image();
mImage.onload = function(){
var canvas = document.createElement('canvas');
canvas.width = mImage.width;
canvas.height = mImage.height;
var context = canvas.getContext('2d');
context.drawImage(mImage, 0, 0);
var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
.
.(Process imgData[])
.
localStorage.setItem("resultImage", mImage.src);
window.open('result.html','_blank','location=no');
};
mImage.src = 'data:image/jpeg;base64,'+imgData;
});
Then, "result.html" pops up, which has the following html content:
...
<body>
...
<canvas id="resultCanvas" style="position: absolute; top: 0%; left: 0%;"></canvas>
...
</body>
...
And the following javascript code:
...
var mImage = new Image();
mImage.onload = function(){
var canvas = document.getElementById('resultCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight*0.3; // 1/3 of phone screen size
var context = canvas.getContext('2d');
context.drawImage(mImage);
navigator.notification.alert('Height = '+mImage.height+'\n'+'Width ='+mImage.width, function(){},'Image','Done');
};
mImage.src = localStorage.getItem("resultImage");
...
The alert window shows the 640x480 of the image, but the canvas is transparent where it should contain the picture taken.
If I replace the "context.drawImage()" statement with "context.fillrect()" I can see the black rectangle drawn on screen, so it looks that the canvas is there, but somehow I can not make it draw the desired image.
Hope I explained myself correctly.
I really appreciate any help. Thanks!
Well, finally I solved it, first I tryied using an "img" element instead of a canvas, and that works fine, but the problem is I need to make some drawings over the picture, so insisting on code I posted before, I realized the problem was on the use of drawImage() method in the second script, it is necessary to specify position, so replacing:
context.drawImage(mImage);
with
context.drawImage(mImage,0,0);
solved everything.

canvas element unable to draw image

I wrote a few lines of code to draw an image but seems like it keeps on loading, I tried some W3 School code using the basic implementation but that works fine.
What is happening here is it looks like its stuck in some invisible loop and unable to draw an image.
<script>
var canvas=null;
var context=null;
setup=function(){
canvas=document.getElementById("my_canvas");
context=canvas.getContext('2d');
canvas.width=1200;
canvas.height=720;
img=new Image();
img.onload=onImageLoad;
img.src="http://imgge.bg.ac.rs/images/logo1.jpg";
context.drawImage(img,192,192);
};
onImageLoad=function(){
document.write("done !!");
context.drawImage(img,155,10);
};
setup();
</script>
I replaced your document.write() with a console.log() and it works fine:
var canvas = null;
var context = null;
var setup = function() {
canvas = document.getElementById("my_canvas");
context = canvas.getContext('2d');
canvas.width = 1200;
canvas.height = 720;
img = new Image();
img.onload = onImageLoad;
img.src = "http://imgge.bg.ac.rs/images/logo1.jpg";
context.drawImage(img, 192, 192);
};
onImageLoad = function() {
console.log("done !!");
context.drawImage(img, 155, 10);
};
setup();
<canvas id="my_canvas"></canvas>
For the reason why document.write() causes the canvas to not be displayed, read document.write() overwriting the document?
You might notice a difference between the first and consecutive runs due to img being cached by your browser. A cached image is immediately available for drawing within the setup() function.
First you don't need to write context.drawImage(img,192,192); two times only write inside the image.onload() function. when you write anything using using document.write("Done !"); , it will write the content on this document(HTML) page and may create problems. Instead of that write message on <p> tag instead of on whole document.
here is a snippet for your code:
var canvas=null;
var context=null;
setup=function(){
canvas=document.getElementById("my_canvas");
context=canvas.getContext('2d');
canvas.width=1200;
canvas.height=720;
img=new Image();
img.onload=onImageLoad;
img.src="http://imgge.bg.ac.rs/images/logo1.jpg";
//context.drawImage(img,192,192);
};
onImageLoad=function(){
context.drawImage(img,155,10);
document.getElementById("report").innerHTML="Done !";
};
setup();
<canvas id="my_canvas"></canvas>
<p id="report"></p>

Scaling HTML5 Canvas Image

Building a web page on which I am trying to set an image as the background of the main canvas. The actual image is 1600x805 and I am trying to code the application so that it will scale the image either up or down, according to the dimensions of the user's screen. In Prime.js I have an object that sets the properties of the application's canvas element located in index.html. Here is the code for that object:
function Prime(w,h){
if(!(function(){
return Modernizr.canvas;
})){ alert('Error'); return false; };
this.context = null;
this.self = this;
this.globalCanvasMain.w = w;
this.globalCanvasMain.h = h;
this.globalCanvasMain.set(this.self);
this.background.setBg();
}
Prime.prototype = {
constructor: Prime,
self: this,
globalCanvasMain: {
x: 0,
y: 0,
set: function(ref){
ref.context = document.getElementById('mainCanvas').getContext('2d');
$("#mainCanvas").parent().css('position', 'relative');
$("#mainCanvas").css({left: this.x, top: this.y, position: 'absolute'});
$("#mainCanvas").width(this.w).height(this.h);
}
},
background: {
bg: null,
setBg: function(){
this.bg = new Image();
this.bg.src = 'res/background.jpg';
}
},
drawAll: function(){
this.context.drawImage(this.background.bg, 0,0, this.background.bg.width,this.background.bg.height,
this.globalCanvasMain.x,this.globalCanvasMain.y, this.globalCanvasMain.w,this.globalCanvasMain.h);
}
};
The primary interface through which external objects like this one will interact with the elements in index.html is home.js. Here's what happens there:
$(document).ready(function(){
var prime = new Prime(window.innerWidth,window.innerHeight);
setInterval(prime.drawAll(), 25);
});
For some reason, my call to the context's drawImage function clips only the top left corner from the image and scales it up to the size of the user's screen. Why can I not see the rest of the image?
The problem is that the image has probably not finished loading by the time you call setInterval. If the image is not properly loaded and decoded then drawImage will abort its operation:
If the image isn't yet fully decoded, then nothing is drawn
You need to make sure the image has loaded before attempting to draw it. Do this using the image's onload handler. This operation is asynchronous so it means you also need to deal with either a callback (or a promise):
In the background object you need to supply a callback for the image loading, for example:
...
background: {
bg: null,
setBg: function(callback) {
this.bg = new Image();
this.bg.onload = callback; // supply onload handler before src
this.bg.src = 'res/background.jpg';
}
},
...
Now when the background is set wait for the callback before continue to drawAll() (though, you never seem to set a background which means drawImage tries to draw null):
$(document).ready(function(){
var prime = new Prime(window.innerWidth, window.innerHeight);
// supply a callback function reference:
prime.background.setBg(callbackImageSet);
// image has loaded, now you can draw the background:
function callbackImageSet() {
setInterval(function() {
prime.drawAll();
}, 25);
};
If you want to draw the complete image scaled to fit the canvas you can simplify the call, just supply the new size (and this.globalCanvasMain.x/y doesn't seem to be defined? ):
drawAll: function(){
this.context.drawImage(this.background.bg, 0,0,
this.globalCanvasMain.w,
this.globalCanvasMain.h);
}
I would recommend you to use requestAnimationFrame to draw the image as this will sync with the monitor update.
Also remember to provide callbacks for onerror/onabort on the image object.
There is a problem with the setInterval function. You are not providing proper function reference. The code
setInterval(prime.drawAll(), 25);
execute prime.drawAll only once, and as the result only little part of the image which is being loaded at this moment, is rendered.
Correct code should be:
$(document).ready(function(){
var prime = new Prime(window.innerWidth, window.innerHeight);
setInterval(function() {
prime.drawAll();
}, 25);
});

Categories

Resources