Why produce the canvas rendering an empty image file? [duplicate] - javascript

This question already has answers here:
async or sync ? when we set the src property of the Image object?
(4 answers)
Closed 7 years ago.
My goal:
If the Camera button on my site is clicked > open the smartphone Camera > take a picture from a qr-code > resize it with js > load the picture on my server > read the qr-code > give me the value from the qr-code back.
My problem is that my code produce an empty image file(jpg) and gives me an error.
I tested it with uploading a existing qr-code image and it works on the second try to upload it. On the first try i get the same error like if i try to upload the taken picture from the smartphone.
my html
<form id="qr-code-upload-form" class="form-group" action="" method="post" enctype="multipart/form-data">
<label>
<span id="qr-pic" class="glyphicon glyphicon-camera"></span>
<input type="file" capture="camera" accept="image/*" id="cameraInput" name="cameraInput" style="display: none;">
</label>
<canvas id="cnvsForFormat" width="400" height="266" style="display:none;"></canvas>
</form>
my js/jquery
$(document).ready(function(){
$('body').on("change", "#cameraInput", readCode);
function readCode() {
var cnvs=document.getElementById("cnvsForFormat");
var input = document.getElementById('cameraInput').files[0];
var img = new Image();
if (input) {
var reader = new FileReader();
reader.onload = function (e) {
//Get the input image file
img.src = e.target.result;
var newHeight = img.height/2;
var newWidth = img.width/2;
//reduce the size until its lower than 600px
while (newWidth>600 || newHeight>600) {
newHeight = newHeight/2;
newWidth = newWidth/2;
}
$("#cnvsForFormat").attr("height",newHeight);
$("#cnvsForFormat").attr("width",newWidth);
//Draw the input image in a canvas with the right size for the upload
var ctx=cnvs.getContext("2d");
ctx.drawImage(img,0,0, newWidth, newHeight);
var resizedImg = cnvs.toDataURL("image/jpeg");
//Send the resized Image to my php-request-file
$.post("?cmd=ajax&param=qr_verarbeiten",{photo:resizedImg},function(e) {
alert(e);
});
};
reader.readAsDataURL(input);
}
};
});
</script>
my php to read the qr-code
if (isset($_GET['param']) && $_GET['param'] == "qr_verarbeiten" && isset($_POST['photo'])) {
$img = base64_decode(substr($_POST['photo'],23));
file_put_contents("img/upload/qr.jpg", $img);
$qrcode = new QrReader("img/upload/qr.jpg");
echo $qrcode->text();
}
I use jQuery, Bootstrap and SMARTY in that project
I really hope somebody can help me!
Thanks for taking time to read this and i apologize for my bad english.
The problem was in the js. And i've added the js/MegaPixImage Libraray to handle the big files i get from the iPhone Camera - The working code is :
$(document).ready(function(){
$('body').on("change", "#cameraInput", readCode);
function readCode() {
// var cnvs=document.getElementById("cnvsForFormat");
var input = document.getElementById('cameraInput').files[0];
if (input) {
var img = new Image();
var reader = new FileReader();
reader.onload = function (e) {
img.onload = function() {
var count=0;
while (img.width<1) {
count++;
}
var newHeight = img.height;
var newWidth = img.width;
while (newWidth>600 || newHeight>600) {
newHeight = newHeight/2;
newWidth = newWidth/2;
}
var renderImg = new Image();
var mpImg = new MegaPixImage(img);
mpImg.render(renderImg, { width: newWidth, height: newHeight });
$.post("?cmd=ajax&param=qr_verarbeiten",{photo:renderImg.src},function(e) {
alert(e);
});
};
img.src = e.target.result;
};
reader.readAsDataURL(input);
}
};
});

I got it. It was cause the browser load the img.src asynchron. So i have to add an img.onload function before i load my img.src to make sure the img is already rendered. ;-)
I've added the working code to my question.

Related

Can't draw imported image through input file

I am trying to load an image with an html input type="file" and load it to an with javascript without success.
Here is my code so far:
HTML:
<canvas class="imported" id="imported"></canvas>
<button class="import_button" id="import_button">Import a picture</button>
<input type="file" id="imgLoader"/>
JAVASCRIPT:
document.getElementById('import_button').onclick = function() {
document.getElementById('imgLoader').click();
};
document.getElementById('imgLoader').addEventListener('change', importPicture, false);
function importPicture()
{
alert("HERE");
var canvas = document.querySelector('#imported');
var context = canvas.getContext("2d");
var fileinput = document.getElementById('imgLoader');
var img = new Image();
var file = document.getElementById('imgLoader').files[0];
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(evt)
{
alert("THERE");
if( evt.target.readyState == FileReader.DONE)
{
alert("AGAIN");
img.src = evt.target.result;
context.drawImage(img, 200, 200);
}
}
}
The alerts are all fired up, no errors or message in the console..
How can I load it and display it? Thank you!
The logic (of your original code, not the edited version) is a bit baffling:
document.getElementById('imgLoader').addEventListener('change', importPicture, false);
adds a change event to the file input. That's fine. But then within the "importPicture" function, which runs when the file input changes, you add another change event listener (via fileinput.onchange) ...why are you doing that? I can't see how it makes sense. It means you have to change the file again before the code in there runs. And it also means that every time you change the file it adds more and more listeners, until you have lots of functions firing at once.
The other problem is that you're not waiting for the data to be loaded into the Image object before you try to draw the image on the canvas. You also need to set the dimensions of the canvas itself, and not be prescriptive about the dimensions of the image (because the user could upload anything, of any size).
Here's a working demo:
document.getElementById('import_button').onclick = function() {
document.getElementById('imgLoader').click();
};
var fileinput = document.getElementById('imgLoader').addEventListener('change', importPicture, false);
function importPicture() {
var canvas = document.querySelector('#imported');
var context = canvas.getContext("2d");
var img = new Image();
var file = this.files[0];
var reader = new FileReader();
reader.onload = function(evt) {
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
context.drawImage(img, 0, 0);
}
img.src = evt.target.result;
}
reader.readAsDataURL(file);
}
<canvas class="imported" id="imported"></canvas>
<button class="import_button" id="import_button">Import a picture</button>
<input type="file" id="imgLoader" />
Credit to this answer for the final bits.

FileReader dragging image from browser window

I have a div where users can drag and drop an image and then, using FileReader, I get the base64 of the image. Works fine.
dropper.ondrop = function (e) {
e.preventDefault();
var file = e.dataTransfer.files[0];
var reader = new FileReader();
reader.readAsDataURL(file);
};
My issue is if I have a browser window open and drag and drop an image from the browser I get the error:
Failed to execute 'readAsDataURL' on 'FileReader':
parameter 1 is not of type 'Blob'.
Now, is there any turnaround to get the base64 of an image dragged from the browser window?
Ultimately, is there is a way to get the image URL and then the actual base64 with a server side curl request?
Any idea?
You can use e.dataTransfer.getData('url') to tell whether there's a URL available. Like this:
var url = e.dataTransfer.getData('url');
if(url){
console.log('Url');
console.log(url);
}else{
console.log('File');
var file = e.dataTransfer.files[0];
var reader = new FileReader();
reader.readAsDataURL(file);
}
Then, having the URL, you can load an img element and use draw it on a canvas to grab the base64 representation. Like this:
var img = document.createElement('img');
img.crossOrigin = 'anonymous';
img.onload = function(){
var canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
var context = canvas.getContext('2d');
context.drawImage(this, 0, 0);
console.log(canvas.toDataURL());
};
img.src = url;
This would be the "final" product:
var dropper = document.querySelector('div');
dropper.ondragover = function(e) {
e.preventDefault();
}
dropper.ondrop = function (e) {
e.preventDefault();
var url = e.dataTransfer.getData('url');
if(url){
console.log('Url');
console.log(url);
var img = document.createElement('img');
img.crossOrigin = 'anonymous';
img.onload = function(){
var canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
var context = canvas.getContext('2d');
context.drawImage(this, 0, 0);
console.log(canvas.toDataURL());
};
img.src = url;
}else{
console.log('File');
var file = e.dataTransfer.files[0];
var reader = new FileReader();
reader.readAsDataURL(file);
}
};
div{
border:1px solid red;
height:240px;
width:100%;
}
<div>Drop here</div>
Also available here: https://jsfiddle.net/8u6Lprrb/1/
The first parameter comes through with the "http://" or "https://" protocol instead of the expected "file://" protocol. and thats why you cant read it with the HTML5 FILE API, so you may want to download and save the file first before trying to read the contents.
on the other hand, you can update an img tag's src attribute with the retrieved url and skip the readDataAsUrl() portion to hotlink the image.

Input onchange javascript function call not working in Safari

I have an html canvas that shows a preview of an image when the image is selected in an input. This works in Chrome, but I can't seem to get it to work in Safari. Specifically - in Safari - the onchange="previewFile()" does not seem to call the previewFile function.
<canvas id="canvas" width="0" height="0"></canvas>
<h2>Upload a photo </h2>
<input type="file" onchange="previewFile()"><br>
<script type="text/javascript">
// setup the canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// grab the photo and display in canvas
var photo = new Image();
function previewFile() {
var file    = document.querySelector('input[type=file]').files[0];
var reader  = new FileReader(); 
reader.addEventListener("load", function () {
photo.src = reader.result;
canvas.height = photo.height;
canvas.width = photo.width;
ctx.drawImage(photo,0,0);
}, false);
if (file) {
  reader.readAsDataURL(file);
}
}
</script>
Your problem is certainly due to the fact that you are not waiting for the image has loaded before you try to draw it on the canvas.
Even wen the source is a dataURI, the loading of the image is asynchronous, so you need to wrap your drawing operations in the image's onload event.
var photo = new Image();
photo.onload = function(){
canvas.height = photo.height;
...
}
...
reader.onload = function(){
// this will eventually trigger the image's onload event
photo.src = this.result;
}
But if all you need is to draw your image on the canvas, don't even use a FileReader, actually, the readAsDataURL() method should trigger a I'm doing something wrong error in your brain. Almost all you can do with a dataURI version of a Blob, you can also do it with the Blob itself, without computing it nor polluting the browser's memory.
For example, to display an image for user input, you can use URL.createObjectURL(blob) method.
// setup the canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// grab the photo and display in canvas
var photo = new Image();
// drawing operations should be in the mage's load event
photo.onload = function() {
// if you don't need to display this image elsewhere
URL.revokeObjectURL(this.src);
canvas.height = this.height;
canvas.width = this.width;
ctx.drawImage(this, 0, 0);
}
photo.onerror = function(e) {
console.warn('file format not recognised as a supported image');
}
file_input.onchange = function() {
// prefer 'this' over DOM selection
var file = this.files[0];
var url = URL.createObjectURL(file);
photo.src = url;
};
<canvas id="canvas" width="0" height="0"></canvas>
<h2>Upload a photo </h2>
<input type="file" id="file_input">
<br>
And for those who need to send this file to their server, use a FormData to send directly the Blob. If you really need a dataURI version, then convert it server-side.

Validating Image upload

I'm trying to validate the image before being uploaded. I restrict the size and pixels but the problem is that I can still upload the image even though it doesn't apply to the restrictions. How can I stop the user or disable the upload as this validation is done with the onchange command.
var myFile = document.getElementById('file');
myFile.addEventListener('change', function() {
image = new Image();
if(this.files[0].size > 800000)
{
alert("File size too big. Please upload a smaller image");
return false;
}
else
{
var reader = new FileReader();
//Read the contents of Image File.
reader.readAsDataURL(this.files[0]);
reader.onload = function (e)
{
//Initiate the JavaScript Image object.
var image = new Image();
//Set the Base64 string return from FileReader as source.
image.src = e.target.result;
image.onload = function () {
//Determine the Height and Width.
var height = this.height;
var width = this.width;
if (height > 500 || width > 375) {
alert("Height and Width must not exceed 500px(h) and 375px(w).");
return false;
}
else
{
alert("Uploaded image has valid Height and Width.");
return true;
}
};
}
}
});
You can reset the value of the file input element if your validation fails
var myFile = document.getElementById('file');
...
myFile.value=""
And then show validation to the user in page.
Here's what I did as an alternative. I didn't know this was possible until I came upon this. You just add this to the relevant alert/return false section and it disables the submit button.
jQuery("input[type='submit']").attr("disabled", 'disabled');

Displaying multiple Thumbnails not working

So I am making this "drop-zone" for a website.
The user can drag and drop images over this area and all jpegs that are dropped on the area are displayed as thumbnailes. I made a List of all jpegs that are dropped on the area and that is working fine. Now everytime a new jpeg is added to that list I want to display all jpegs as thumbnailes of the size 100x150px.
Here is my code for that.
And here is a like to the fiddle http://jsfiddle.net/cJkYj/2/
function displayThumbnailes( ) {
//Clear the div.
var outerDiv = document.getElementById('fileChooserDiv');
while (outerDiv.hasChildNodes()) {
outerDiv.removeChild(outerDiv.lastChild);
}
var div = document.createElement('div');
outerDiv.appendChild(div);
//Go over the Files and add Thumbnails for all jpegs.
for( var i = 0; i < files.size(); i++) {
if( files.get(i).type == "image/jpeg") {
var reader = new FileReader();
reader.onloadend = function() {
var result = reader.result;
displayThumbnail( result, div );
}
reader.readAsDataURL(files.get(i));
}
}
}
function displayThumbnail( src, div ) {
var img = new Image();
img.onload = function () {
var cv = document.createElement('canvas');
cv.style.border = "1px solid #ffffff";
cv.width = 100;
cv.height = 150;
var ctx = cv.getContext( "2d" );
ctx.drawImage(img, 0, 0, 100, 150);
div.appendChild(cv);
}
img.src = src;
}
MY PROBLEM: I believe this code would display all thumbnailes in the area of the div but I only see the last one.
This looks like I am messing up some synchronisation or something.
"files" is an Object that manages the files dragged on the "drop-zone".
I want every Thumbnail to have his own canvas so I can add an onclick event to them later.
Any help would be appriciated ^^
Your code:
reader.onloadend = function() {
var result = reader.result;
displayThumbnail( result, div );
}
reader.readAsDataURL(files.get(i));
was failing because of the async onloadend callback. Each pass through the loop updated the outer reader variable, so each event used only the last reference.
You could either change your code to wrap that in a closure:
(function(reader) {
reader.onloadend = function() {
var result = reader.result;
displayThumbnail( result, div );
}})(reader);
or (preferred) change your code to use the event specific data :
reader.onloadend = function(evt) {
var result = evt.target.result;
displayThumbnail( result, div );
};
-- or using onload --
reader.onload = function (evt) {
var result = evt.target.result;
displayThumbnail(result, div);
}
reader.readAsDataURL(files.get(i));
works: example
I should also note that I rearranged your code to completely run within an onload handler.

Categories