I'm loading png and jpeg images in a canvas with drawImage, but it doesn't always work, even with the same image. There's no exception, no error, it just doesn't work.
{
debug("lbase starting");
var canvas = document.getElementById("base");
debug(canvas);
ctx = canvas.getContext("2d");
debug(ctx);
ctx.clearRect(0, 0, canvas.width, canvas.height);
var baseimg = new Image();
debug(baseimg);
baseimg.src = "baseimage.png";
debug(baseimg.src);
ctx.drawImage(baseimg, 0, 0);
debug("base loading done");
}
My debug function just write a message in a div at the bottom of the page. I also tried with:
retcode ctx.drawImage(baseimg, 0, 0);
debug(retcode)
but it returns undefined.
Everything gets executed (I get all the right debug messages), but sometimes it works, sometimes it does not. It seems to fail more in ff then chromium.
Is there anyway to verify that the drawImage has worked?
Any idea what's going on?
Thanks.
You can't add an image to the canvas until it has finished loading.
Something like this should work:
var baseimg = new Image();
baseimg.src = "baseimage.png";
baseimg.onload = function() {
ctx.drawImage(baseimg, 0, 0);
};
Related
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);
};
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) );
I'm loading an image into my albImg array.
in my loop i then do this:
for(var j = 1; j < albImg.length; j++){
if(albImg[j].complete == true && albImg[j].width > 0){
loadedimages++;
}
}
to make sure all my images are loaded.
I then call my flipImg() function like this:
if(loadedimages == albImg.length-1){
flipImg();
}
I then flip an image and
ctx2.save();
ctx2.scale(-1, 1);
for (var i = RszSpriteCount; i < sprites.length; i++) {
ctx2.drawImage(albImg[sprites[i][0]], sprites[i][1], sprites[i][2], sprites[i][3], sprites[i][4], 0 - (sprites[i][1] + sprites[i][3]), sprites[i][2], sprites[i][3], sprites[i][4]);
}
ctx2.restore();
var flipSz = albImg.length;
albImg[flipSz] = new Image();
albImg[flipSz].src = cnv2.toDataURL();
Here's where my problem begins.
The new image I created - albImg[5] - can't be displayed until it is loaded.
But it is created as if it already is loaded.
That is to say that:
albImg[5].width is already set (to 750) before I can display it.
albImg[5].complete is set to true, before I can display it.
albImg[5].onload = ctx.drawImage(albImg[5], 0, 0); will try to draw the image before it is loaded.
How can I check if my flipped image really is loaded before I display it? in Javascript?
(due to circumstances I'm not using jQuery for this)
Please help.
Your main error is in how you do set the onload event handler :
albImg[5].onload = ctx.drawImage(albImg[5], 0, 0)
will set the return value of drawImage() (undefined) to the onload listener.
What you want is
albImg[5].onload = e => ctx.drawImage(albImg[5], 0, 0);
or
albImg[5].onload = function(){ ctx.drawImage(this, 0, 0) };
For the complete and width properties set to true, it's because while the loading of an Image is always async, in your case, the image is probably already HTTP cached.
Since the HTTP loading and the javascript execution are not executed on the same thread, it is possible that the Image actually loaded before the browser returns its properties.
But even then, the onload event will fire (best to set it before the src though).
var cacher = new Image();
cacher.onload = function(){
var img = new Image();
img.onload = function(){
console.log('"onload" fires asynchronously even when cached');
};
img.src = c.toDataURL();
console.log('cached : ', img.complete, img.naturalWidth);
}
cacher.src = c.toDataURL();
console.log('before cache :', cacher.complete, cacher.naturalWidth);
<canvas id="c"></canvas>
So when dealing with an new Image (not one in the html markup), always simply listen to its onload event.
Now, with the few information you gave in your question, it would seem that you don't even need these images, nor to deal with any of their loadings (except for the sprites of course), since you can directly and synchronously call ctx.drawImage(CanvasElement, x, y).
const ctx = c.getContext('2d');
ctx.moveTo(0, 0);
ctx.lineTo(300, 75);
ctx.lineTo(0, 150);
ctx.fillStyle = 'rgba(120,120,30, .35)';
ctx.fill();
const flipped = c.cloneNode(); // create an offscreen canvas
const f_ctx = flipped.getContext('2d');
f_ctx.setTransform(-1, 0,0,1, c.width, 0);// flip it
f_ctx.drawImage(c,0,0);// draw the original image
// now draw this flipped version on the original one just like an Image.
ctx.drawImage(flipped, 0,0);
// again in 3s
setTimeout(()=>ctx.drawImage(flipped, 150,0), 3000);
<canvas id="c"></canvas>
I am working with a single canvas that allows the user to click on a window pane in a window image. The idea is to show where the user has clicked. The image will then be modified (by drawing a grill on the window) and then saved to in JPEG. I am saving the canvas image prior to the click function because I don't want the selection box to show in the final image. However, Firefox often displays a blank canvas when restoring the canvas where IE and Chrome do not. This works perfectly in Chrome and IE. Any suggestions? Does Firefox have a problem with toDataURL()? Maybe some async issue going on here? I am also aware that saving a canvas in this fashion is memory intensive and there may be a better way to do this but I'm working with what I have.
Code:
/**
* Restores canvas from drawingView.canvasRestorePoint if there are any restores saved
*/
restoreCanvas:function()
{
var inverseScale = (1/drawingView.scaleFactor);
var canvas = document.getElementById("drawPop.canvasOne");
var c = canvas.getContext("2d");
if (drawingView.canvasRestorePoint[0]!=null)
{
c.clearRect(0,0,canvas.width,canvas.height);
var img = new Image();
img.src = drawingView.canvasRestorePoint.pop();
c.scale(inverseScale,inverseScale);
c.drawImage(img, 0, 0);
c.scale(drawingView.scaleFactor, drawingView.scaleFactor);
}
},
/**
* Pushes canvas into drawingView.canvasRestorePoint
*/
saveCanvas:function()
{
var canvas = document.getElementById("drawPop.canvasOne");
var urlData = canvas.toDataURL();
drawingView.canvasRestorePoint.push(urlData);
},
EXAMPLE OF USE:
readGrillInputs:function()
{
var glassNum = ir.get("drawPop.grillGlassNum").value;
var panelNum = ir.get("drawPop.grillPanelNum").value;
drawingView.restoreCanvas();
drawEngine.drawGrill(glassNum, panelNum,null);
drawingView.saveCanvas();
},
sortClick:function(event)
{
..... //Sorts where user has clicked and generates panel/glass num
.....
drawingView.showClick(panelNum, glassNum);
},
showClick:function(panelNum, glassNum)
{
var glass = item.panels[panelNum].glasses[glassNum];
var c = drawEngine.context;
drawingView.restoreCanvas();
drawingView.saveCanvas();
c.strokeStyle = "red";
c.strokeRect(glass.x, glass.y, glass.w, glass.h);
},
By just looking at the code setting the img.src is an async action to retrieve the image, so when you try to draw it 2 lines later to the canvas, it probably hasn't been loaded yet (having it in cache will make it return fast enough that it might work).
You should instead use an img.onload function to draw the image when it has loaded.
restoreCanvas:function()
{
var inverseScale = (1/drawingView.scaleFactor);
var canvas = document.getElementById("drawPop.canvasOne");
var c = canvas.getContext("2d");
if (drawingView.canvasRestorePoint[0]!=null)
{
c.clearRect(0,0,canvas.width,canvas.height);
var img = new Image();
img.onload = function() {
c.scale(inverseScale,inverseScale);
c.drawImage(img, 0, 0);
c.scale(drawingView.scaleFactor, drawingView.scaleFactor);
};
img.src = drawingView.canvasRestorePoint.pop();
}
},
We're building a one page website and something we have always had problems with is when the one page site has lots of images. The scroll seems very jittery especially on chrome and we want to look at a much better way of getting it done. Any suggestions of libraries of techniques to use?
Thanks in advance.
Since each image is likely getting resized with CSS, then with lots of images, the browser has to resize the image when you scroll, which adds a lot of computation.
This will add to the loading/rendering time initially, but should help with laggy scrolling.
$('.body').find('.img').each(function() {
var img = this;
setTimeout(function() { //use setTimeout so as to not totally block the thread.
renderImageAsRightSize(img);
}, 0);
});
//remove loader image.
$(".site-loader").delay(100).fadeOut("slow");
function renderImageAsRightSize (imageObject)
{
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.height = canvas.width * (imageObject.height / imageObject.width);
var oc = document.createElement('canvas'),
octx = oc.getContext('2d');
oc.width = imageObject.width;
oc.height = imageObject.height;
octx.drawImage(imageObject, 0, 0, oc.width, oc.height);
octx.drawImage(oc, 0, 0, oc.width, oc.height);
//resize final image
ctx.drawImage(oc, 0, 0, oc.width, oc.height, 0, 0, canvas.width, canvas.height);
}
Essentialliy, converting full-size images being resized with CSS, to the appropriate size (whatever the size of the resized image is on the page) using the canvas.