I have a simple demo where users can select an image file, and I want to extract the RGB values of the image file as a ImageData. Here's my attempt:
<form>
<label for="img">Select image:</label>
<div>
<input type="file" id="img" name="img" accept="image/*" onchange="loadImage(this)">
</div>
</form>
<canvas id="origCanvas" .../>
function loadImage(input) {
if (input.files && input.files[0]) {
var imageFile = input.files[0]
var reader = new FileReader();
reader.onload = function() {
var img = new Image(CANVAS_WIDTH, CANVAS_HEIGHT);
img.src = reader.result;
readRGB(img);
}
reader.readAsDataURL(imageFile);
}
}
function readRGB(img) {
var c = document.getElementById("origCanvas");
var ctx = c.getContext("2d");
ctx.drawImage(img, 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
imageData = ctx.getImageData(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// do something else
}
However, when I keep selecting different files, readRGB only extracts the correct imageData some of the times. Other times the imageData I extracted are the old imageData that's already there on the canvas.
My gut feeling says that img.src = reader.result is an async operation, and it's at the mercy of the browser when img will be loaded. Many times when I try to ctx.drawImage(img), I end up drawing an empty canvas because img hasn't loaded the actual image file yet.
Is my guess correct, and what should I do in this case?
Related
I'm working inside a lit-element component trying to draw an img element onto a canvas element:
return html`
<img></img>
<br>
<canvas width="1500" height="1500"></canvas>
<br>
<input type='file' accept='image/*' #change=${this.readImage}> </input>
`
readImage(event) {
// Get the image element
var img = this.shadowRoot.querySelector('img')
// Get the file
var file = event.target.files[0]
// File reader
var reader = new FileReader()
// Handle read
reader.onload = event=>{
img.src = event.target.result
// Get the canvas element
var canvas = this.shadowRoot.querySelector('canvas')
// Get the canvas context
var context = canvas.getContext('2d')
// Draw image on canvas
context.drawImage(img,0,0)
}
// Read the file
reader.readAsDataURL(file)
}
However for some reason the canvas element remains blank. I can't seam to find out where I'm going wrong or if I'm running into some kind of bug?
You need to use onload to make sure the image has finished loading. Is there a reason you aren't just using a new Image() object instead of using an embedded img element?
this.shadowRoot=document;
function
readImage(event) {
// Get the image element
var img = this.shadowRoot.querySelector('img')
// Get the file
var file = event.target.files[0]
// File reader
var reader = new FileReader()
// Handle read
reader.onload = event => {
img.onload = () => {
// Get the canvas element
var canvas = this.shadowRoot.querySelector('canvas')
// Get the canvas context
var context = canvas.getContext('2d')
// Draw image on canvas
context.drawImage(img, 0, 0)
}
img.src = event.target.result
}
// Read the file
reader.readAsDataURL(file)
}
<img></img>
<br>
<canvas width="1500" height="1500"></canvas>
<br>
<input type='file' accept='image/*' onchange=readImage(event)> </input>
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 upload an image using a file input:
<input type="file" id="product_images-0"/>
Then I get the file with javascript:
var element = document.getElementById('product_images-1');
console.log(element.files[0]);
I want to get the image file from memory, show it to user, simulate an image crop to get the coordinates and show the "cropped-simulated" image to the user, by using css transform/translate.
After cropping if the user doesn't like the image can remove it.
It is possible. I don't want to use Ajax because the user can remove an image before submits, and deleting this "remove" image from backend is hard to do it.
Please see simple proof-of-concept below. Once file is submitted you need to create new HTMLImageElement and append it to your preview. Then you can draw cropped image on canvas using drawImage.
Remember to validate if file you are submitting is image type!
var fileInput = document.getElementById('product_images-0');
var preview = document.querySelector('div.preview');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
fileInput.onchange = function (event) {
var curFiles = fileInput.files;
if (curFiles.length > 0) {
while (preview.firstChild) {
preview.removeChild(preview.firstChild);
}
var image = document.createElement('img');
image.src = window.URL.createObjectURL(curFiles[0]);
preview.appendChild(image);
image.onload = function () {
ctx.drawImage(image, 0.2 * image.width, 0.2 * image.height, 0.5 * image.width, 0.5 * image.height, 0, 0, canvas.width, canvas.height);
};
}
};
<input type="file" id="product_images-0"/>
<div class="preview">
<p>No files currently selected for upload</p>
</div>
<canvas id="canvas" width="200" height="200"></canvas>
I have a webpage with capture image from the Available camera feature. For the web version it simply places the video capture onto a canvas. However for a phone, I am using <input class="btn btn-info" type="file" accept="image/*" id="cameraCapture" capture="camera"> to capture a picture. It asks the user to either capture the image using the phone's camera or upload from its filesystem/gallery etc. Once the image is clicked it simply places the image's name next to the button.
Is there a way to access this image and display it in the same page.
Thanks in advance
You can do that with JS using FileReader object.
Take a look at this answer: preview-an-image-before-it-is-uploaded
I hope it helps
var input = document.querySelector('input[type=file]');
input.onchange = function () {
var file = input.files[0];
drawOnCanvas(file);
};
function drawOnCanvas(file) {
var reader = new FileReader();
reader.onload = function (e) {
var dataURL = e.target.result,
c = document.querySelector('canvas'),
ctx = c.getContext('2d'),
img = new Image();
img.onload = function() {
c.width = img.width;
c.height = img.height;
ctx.drawImage(img, 0, 0);
};
img.src = dataURL;
};
reader.readAsDataURL(file);
}
Okay, I know that there are loads of subjects that look identical to this one on SO, but none of them have fixed my issue...
I'm trying to grab an image from a file input and throw it onto a canvas so that I can later turn it into a base-64 image... But I've hit a snag in the process that I was not expecting, in drawing the image to the canvas...
Taking the following HTML:
<input type="file" id="fileIn" onchange="preview()"><br>
<img id="filePreview"><br>
<canvas id="fileCanvas"></canvas>
And the following script:
var dataurl = '';
function preview(){
document.getElementById('filePreview').src = URL.createObjectURL(document.getElementById('fileIn').files[0]);
document.getElementById('filePreview').onload = showInCanvas;
cnv = document.getElementById('fileCanvas');
ctx = cnv.getContext('2d');
}
function showInCanvas(){
cnv.style.width = document.getElementById('filePreview').naturalWidth + 'px';
cnv.width = document.getElementById('filePreview').naturalWidth;
cnv.style.height = document.getElementById('filePreview').naturalHeight + 'px';
cnv.height = document.getElementById('filePreview').naturalHeight + 'px';
ctx.clearRect(0, 0, cnv.width, cnv.height);
ctx.drawImage(document.getElementById('filePreview'), 0, 0);
dataurl = cnv.toDataURL('image/png');
}
The problem I'm having is that the image simply refuses to draw onto the canvas. Even going into the console and drawing the image to the canvas manually, after the script has run, it still refuses to draw. Because of this, the image data simply runs through as data:,
It's an https site, if that affects anything.
For clarification, here's my question:
Why is the canvas refusing to render this image? How can I fix this issue?
If the intent is to convert the image to Data-URI ("base64"), the FileReader can be used - no need for canvas (which can also alter the image/final compression):
fileIn.onchange = function(e) {
var fr = new FileReader();
fr.onload = done.bind(fr);
fr.readAsDataURL(e.target.files[0]);
}
function done() {
var dataURI = filePreview.src = this.result;
alert(dataURI.substr(0, 35) + "...")
}
<input type="file" id="fileIn"><br>
<img id="filePreview"><br>
<canvas id="fileCanvas"></canvas>