I'm using the HTML5 camera for taking picture. I have noticed that after the picture is captured, the final image width and height are stretched though I'm applying the same width and height for both video as well as canvas.
Please refer this JSFIDDLE
var video = document.querySelector("#videoElement");
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;
if (navigator.getUserMedia) {
navigator.getUserMedia({video: true}, handleVideo, videoError);
}
function handleVideo(stream) {
video.src = window.URL.createObjectURL(stream);
}
function videoError(e) {
// do something
}
var v,canvas,context,w,h;
var imgtag = document.getElementById('imgtag');
var sel = document.getElementById('fileselect');
//document.addEventListener('DOMContentLoaded', function(){
v = document.getElementById('videoElement');
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
w = canvas.width;
h = canvas.height;
//},false);
function draw(v,c,w,h) {
if(v.paused || v.ended) return false;
context.drawImage(v,0,0,w,h);
var uri = canvas.toDataURL("image/png");
imgtag.src = uri;
}
document.getElementById('save').addEventListener('click',function(e){
draw(v,context,w,h);
});
var fr;
sel.addEventListener('change',function(e){
var f = sel.files[0];
fr = new FileReader();
fr.onload = receivedText;
fr.readAsDataURL(f);
})
function receivedText() {
imgtag.src = fr.result;
}
Your code has hard-coded values for width and height, both for video and canvas elements.
Avoid setting absolute sizes in both directions as the dimension of the video element can vary. You can set the size by using just the width or just the height, or by using CSS style/rule.
For canvas you need to set the size based on the actual size of the video element. For this use the video element properties:
video.videoWidth;
video.videoHeight;
You can apply a scale to those as you want, for example:
scale = 300 / v.videoWidth;
w = v.videoWidth * scale;
h = v.videoHeight * scale;
canvas.width = w;
canvas.height = h;
context.drawImage(v, 0, 0, w, h);
Updated fiddle
Should someone still be facing this problem in 2021...
// init media navigator
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
// get the actual settings of video element,
// which includes real video width and height
const streamSettings = stream.getVideoTracks()[0].getSettings();
// set the constrains to canvas element
canvasElement.width = streamSettings.width;
canvasElement.height = streamSettings.height;
}
Related
PS: Is it not a research kind of question! I have been trying to do this from very long time.
I am trying to make web based an image editor where user can select multiple cropping area and after selection save/download all the image area. like below.
As of now I discovered two libraries
1.Cropper.JS where is only single selection feature is available.
2.Jcrop where only single selection area restrictions.
I am currently using cropper.Js but it seems impossible for me to make multiple selection cropping.
Any help is much appreciated.if any other method/library available in JavaScript, Angular or PHP or reactJS for multiple image area selection and crop and download in one go as in the image below.
As per #Keyhan Answer I am Updating my Jcrop library Code
<div style="padding:0 5%;">
<img id="target" src="https://d3o1694hluedf9.cloudfront.net/market-750.jpg">
</div>
<button id="save">Crop it!</button>
<link rel="stylesheet" href="https://unpkg.com/jcrop/dist/jcrop.css">
<script src="https://unpkg.com/jcrop"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
JavaScript
<script>
setImage();
var jcp;
var jcp;
Jcrop.load('target').then(img => {
//You can enable multiple cropping with this line:
jcp = Jcrop.attach(img, { multi: true });
});
// to fix security issue when trying to convert to Data URI
function setImage() {
document.getElementById('target').setAttribute('crossOrigin', 'anonymous');
document.getElementById('target').src = 'https://d3o1694hluedf9.cloudfront.net/market-750.jpg';
}
var link = document.getElementById('save');
link.onclick = function () {
//we check if at least one crop is available
if (jcp.active) {
var i = 0;
var fullImg = document.getElementById("target");
//we are looping cropped areas
for (area of jcp.crops) {
i++;
//creating temp canvas and drawing cropped area on it
canvas = document.createElement("canvas");
canvas.setAttribute('width', area.pos.w);
canvas.setAttribute('height', area.pos.h);
ctx = canvas.getContext("2d");
ctx.drawImage(fullImg, area.pos.x, area.pos.y, area.pos.w, area.pos.h, 0, 0, area.pos.w, area.pos.h);
//creating temp link for saving/serving new image
temp = document.createElement('a');
temp.setAttribute('download', 'area' + i + '.jpg');
temp.setAttribute('href', canvas.toDataURL("image/jpg").replace("image/jpg", "image/octet-stream"));
temp.click();
}
}
};
</script>
I tried to explain the code with comments:
var jcp;
Jcrop.load('target').then(img => {
//You can enable multiple cropping with this line:
jcp = Jcrop.attach(img,{multi:true});
});
//assuming you have a button with id="save" for exporting cropped areas
var link=document.getElementById('save');
link.onclick = function(){
//we check if at least one crop is available
if(jcp.active){
var i=0;
var fullImg = document.getElementById("target");
//we are looping cropped areas
for(area of jcp.crops){
i++;
//creating temp canvas and drawing cropped area on it
canvas = document.createElement("canvas");
canvas.setAttribute('width',area.pos.w);
canvas.setAttribute('height',area.pos.h);
ctx = canvas.getContext("2d");
ctx.drawImage(fullImg, area.pos.x, area.pos.y, area.pos.w, area.pos.h, 0, 0, area.pos.w, area.pos.h);
//creating temp link for saving/serving new image
temp = document.createElement('a');
temp.setAttribute('download', 'area'+i+'.jpg');
temp.setAttribute('href', canvas.toDataURL("image/jpg").replace("image/jpg", "image/octet-stream"));
temp.click();
}
}
};
EDIT: As you commented it would be nicer if we have local image loader, we can add a file input to our html
<img id="target" />
<br/>
<input type="file" id="imageLoader" name="imageLoader"/><!-- add this for file picker -->
<button id="save">save</button>
and a function to our js to handle it
var jcp;
var save=document.getElementById('save');
var imageLoader = document.getElementById('imageLoader');
var img = document.getElementById("target");
imageLoader.onchange=function handleImage(e){//handling our image picker <input>:
var reader = new FileReader();
reader.onload = function(event){
img.src = event.target.result;
}
reader.readAsDataURL(e.target.files[0]);
}
save.onclick = function(){
if(jcp&&jcp.active){
var i=0;
for(area of jcp.crops){
i++;
canvas = document.createElement("canvas");
canvas.setAttribute('width',area.pos.w);
canvas.setAttribute('height',area.pos.h);
ctx = canvas.getContext("2d");
ctx.drawImage(img, area.pos.x, area.pos.y, area.pos.w, area.pos.h, 0, 0, area.pos.w, area.pos.h);
temp = document.createElement('a');
temp.setAttribute('download', 'area'+i+'.jpg');
temp.setAttribute('href', canvas.toDataURL("image/jpg").replace("image/jpg", "image/octet-stream"));
temp.click();
}
}
};
Jcrop.load('target').then(img => {
jcp = Jcrop.attach(img,{multi:true});
});
Yes, #keyhan was right <input type="file"> is another question, but still, I am giving you an idea of how to implement Kayhan's code above.
<div>
<input type="file" id="image-input" accept="image/*">
<!-- img id name should be "target" as it is also using by Jcrop -->
<img id="target"></img>
</div>
and Now you can put below JavaScript Code just above setImage()
<script>
let imgInput = document.getElementById('image-input');
imgInput.addEventListener('change', function (e) {
if (e.target.files) {
let imageFile = e.target.files[0];
var reader = new FileReader();
reader.onload = function (e) {
var img = document.createElement("img");
img.onload = function (event) {
var MAX_WIDTH = 1600;
var MAX_HEIGHT = 800;
var width = img.width;
var height = img.height;
// Change the resizing logic
if (width > height) {
if (width > MAX_WIDTH) {
height = height * (MAX_WIDTH / width);
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width = width * (MAX_HEIGHT / height);
height = MAX_HEIGHT;
}
}
// Dynamically create a canvas element
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
// var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// Actual resizing
ctx.drawImage(img, 0, 0, width, height);
// Show resized image in preview element
var dataurl = canvas.toDataURL(imageFile.type);
document.getElementById("target").src = dataurl;
}
img.src = e.target.result;
}
reader.readAsDataURL(imageFile);
}
});
</script>
Well, I'm having a very weird issue that's only happening on Samsung Browser. On Chrome and other browsers, this works well.
When I take a snapshot of a current frame of a video (Currently the mobile camera) on javascript I get the image with distortion and generally bad.
The code that takes the snapshot is:
function takeSnapshot() {
// Here we're using a trick that involves a hidden canvas element.
video.pause();
var hidden_canvas = document.createElement('canvas'),
context = hidden_canvas.getContext('2d');
var width = video.videoWidth,
height = video.videoHeight;
if (width && height) {
// Setup a canvas with the same dimensions as the video.
hidden_canvas.width = width;
hidden_canvas.height = height;
// Make a copy of the current frame in the video on the canvas.
context.drawImage(video, 0, 0, width, height);
// Turn the canvas image into a dataURL that can be used as a src for our photo.
return hidden_canvas.toDataURL('image/jpeg');
}
}
Do I'm missing something else to make it work in Samsung Browser? Or I just put a message that this is not compatible with this browser?
Currently tested on Samsung Galaxy S9, Android 10.
------------- Update
I found what is causing the image to be captured badly.
I'm using custom size for the image, in this case, is a horizontal rectangle.
I do this when init the video:
var w = 2000; // This renders the video as a horizontal rectangle, this does the issue.
var h = 1200;
var userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
var isSamsungBrowser = userAgent.indexOf('SamsungBrowser') >= 0;
// Quick fix:
if(SamsungBrowser){ // If I render as vertical renctangle, the issue is gone.
w = 1200;
h = 2000;
}
navigator.getMedia(
{
video:
{
deviceId: videoSource ? { exact: videoSource } : undefined,
width: { ideal: h },
height: { ideal: w }
}
},
// Success Callback
function (stream) {
// Create an object URL for the video stream and
// set it as src of our HTLM video element.
try {
currentStream = stream;
video.srcObject = stream;
} catch (error) {
video.src = window.URL.createObjectURL(stream);
}
window.stream = stream;
// Play the video element to start the stream.
video.play();
video.onplay = function () {
showVideo();
};
}
For the most part this is working, except the uploaded image resizes back to its original size within the canvas when text is added. I'll post a couple of example images below for a visual.
Here is the JavaScript:
function upImage(imageFile) {
let imgInput = document.getElementById('imageIn');
imgInput.addEventListener('change', function(e) {
if(e.target.files) {
let imageFile = e.target.files[0]; //here we get the image file
var reader = new FileReader();
reader.readAsDataURL(imageFile);
reader.onloadend = function (e) {
gImgObj = new Image(); // Creates image object
gImgObj.src = e.target.result; // Assigns converted image to image object
gImgObj.onload = function(ev) {
var myCanvas = document.querySelector('.memeCanvas'); // Creates a canvas object
var myContext = myCanvas.getContext("2d"); // Creates a contect object
var MAX_WIDTH = 800;
var MAX_HEIGHT = 600;
var width = gImgObj.width;
var height = gImgObj.height;
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
myCanvas.width = width;
myCanvas.height = height;
myContext.drawImage(gImgObj, 0, 0, width, height);
}
}
}
});
}
Here is the HTML:
<input id="imageIn" type="file" accept=".jpg,.jpeg,.png" onclick="upImage(this)" onchange="runMemeEditor()"/>
Here is what the initial uploaded image looks like:
Here is what happens when the editor overlay changes:
The call to runMemeEditor() simply loads a few functions which displays a Meme Generator. Like I said, it technically works as designed, but the uploaded image when larger than 800x600 end up resizing back to its original size within the canvas.
Thanks for looking.
Is there a way to capture a higher resolution image than the actual width of my onscreen video element which is showing my webcam image that I intend to capture?
Currently, I have set the width in getUserMedia to 1280, but my element is constrained to 640px. I'd like to still be storing images of 1280px wide so that I'm getting higher quality images.
Code:
<div id="video-container">
<h3 id="webcam-title">Add Photos</h3>
<video id="video" autoplay playsinline></video>
<select id="videoSource"></select>
<div id="take-photo-button" onclick="takeSnapshot();">TAKE PHOTO <div class="overlay"></div></div>
<canvas id="myCanvas" style="display:none;"></canvas>
<div id="snapshot-container"></div>
<div id="approval-form-submit">SAVE ORDER</div>
</div>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script>
$(function() {
var video = document.querySelector('video');
var videoSelect = document.querySelector('select#videoSource');
var initialized = false;
//Obtain media object from any browser
navigator.getUserMedia = ( navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
var video_height, snapshot_height;
//var video_width = 1280;
var video_width = 640;
var container_width = 800;
var snapshot_margin = 10;
var snapshot_width = (container_width - snapshot_margin*6)/3;
//var snapshot_width = 1280;
function fillSelectWithDevices(deviceInfos) {
var value = videoSelect.value;
$(videoSelect).empty();
for (let i = 0; i !== deviceInfos.length; ++i) {
var deviceInfo = deviceInfos[i];
if (deviceInfo.kind === 'videoinput') {
var option = document.createElement('option');
option.value = deviceInfo.deviceId;
option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`;
videoSelect.appendChild(option);
if(!initialized && deviceInfo.label==='Back Camera'){
value = deviceInfo.deviceId;
initialized = true;
}
}
if (Array.prototype.slice.call(videoSelect.childNodes).some(n => n.value === value)) {
videoSelect.value = value;
}
}
}
function gotStream(stream) {
window.stream = stream; // make stream available to console
video.srcObject = stream;
video.addEventListener('canplay', function(ev){
video_height = video.videoHeight * (video_width/video.videoWidth);
snapshot_height = video.videoHeight * (snapshot_width/video.videoWidth);
initCanvas();
// Firefox currently has a bug where the height can't be read from
// the video, so we will make assumptions if this happens.
if (isNaN(video_height)) {
video_height = video_width * (3/4);
console.log("Can't read video height. Assuming 4:3 aspect ratio");
}
//video_width=640;
//video_height=480;
video.setAttribute('width', video_width);
video.setAttribute('height', video_height);
canvas.setAttribute('width', video_width);
canvas.setAttribute('height', video_height);
}, false);
return navigator.mediaDevices.enumerateDevices();
}
function handleError(error) {
console.log('navigator.getUserMedia error: ', error);
}
function start() {
if (window.stream) {
window.stream.getTracks().forEach(track => {
track.stop();
});
}
var videoSource = videoSelect.value;
var constraints = {
video: {deviceId: videoSource ? {exact: videoSource} : undefined,
facingMode: "environment",
width:1280},
audio: false
};
navigator.mediaDevices.getUserMedia(constraints).then(gotStream).then(fillSelectWithDevices).catch(handleError);
}
videoSelect.onchange = start;
start();
var canvas, ctx, container;
function initCanvas() {
canvas = document.getElementById("myCanvas");
ctx = canvas.getContext('2d');
container = document.getElementById("snapshot-container");
//Reconstitute snapshots from form URI after failed submit
var image_list_field = $('#image-list-field'),
URI_array = image_list_field.val().split(','),
dataURI;
for(var i = 0;i<URI_array.length;i++){
if(URI_array[i]){
dataURI = "data:image/png;base64,"+URI_array[i];
displaySnapshot(dataURI);
}
}
}
// Capture a photo by fetching the current contents of the video
// and drawing it into a canvas, then converting that to a PNG
// format data URL. By drawing it on an offscreen canvas and then
// drawing that to the screen, we can change its size and/or apply
// other changes before drawing it.
takeSnapshot = function() {
alert (video_width + " " + video_height);
ctx.drawImage(video, 0, 0, video_width, video_height);
var data = canvas.toDataURL('image/png');
displaySnapshot(data);
}
function displaySnapshot(data){
var photo = document.createElement('img'),
snapshot_div = document.createElement('div'),
delete_text = document.createElement('p');
photo.setAttribute('src', data);
$(photo).css({"width":snapshot_width+"px"});
$(photo).addClass("snapshot-img");
$(snapshot_div).css({"width":snapshot_width+"px","height":snapshot_height+25+"px"});
$(delete_text).text("Delete Photo");
$(snapshot_div).append(photo).append(delete_text);
$(delete_text).on('click',function(){$(this).closest('div').remove()})
container.append(snapshot_div);
}
$('#approval-form-submit').on('click',function(e){
var form = $('#approval-form'),
image_list_field = $('#image-list-field'),
imageURI;
image_list_field.val("");
$('.snapshot-img').each(function(i, d){
imageURI = d.src.split(',')[1]+',';
image_list_field.val(image_list_field.val()+imageURI);
});
form.submit();
})
Using javascript or Jquery I want to get the current image from the webcam feed on my page and put it in an tag.
I have a bit of script which generates tags dynamically with unique Id's so after generating one all I want to do is capture an image from the webcam at that exact moment and save the image in the generated tag. After taking the image I just want the webcam to carry until the next time it takes a picture.
I already have a webcam feed running using a library which does face tracking, however I want to extend this with this feature to create a gallery of captured images on the page.
The library I am using is ClmTracker
Creator of the library suggested calling getImageData(x,y,w,h) on the video element and I have tried this. also tried to implement tutorials I have seen on other websites but to no avail. It would seem the answer would need to be specific to my code. I have tried to use canvas instead of tags to put the image in to, but I kept getting errors due to them being created dynamically in the code.
var vid = document.getElementById('videoel');
var overlay = document.getElementById('overlay');
var overlayCC = overlay.getContext('2d');
/********** check and set up video/webcam **********/
function enablestart() {
var startbutton = document.getElementById('startbutton');
startbutton.value = "start";
startbutton.disabled = null;
}
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.msURL || window.mozURL;
// check for camerasupport
if (navigator.getUserMedia) {
// set up stream
var videoSelector = {
video: true
};
if (window.navigator.appVersion.match(/Chrome\/(.*?) /)) {
var chromeVersion = parseInt(window.navigator.appVersion.match(/Chrome\/(\d+)\./)[1], 10);
if (chromeVersion < 20) {
videoSelector = "video";
}
};
navigator.getUserMedia(videoSelector, function (stream) {
if (vid.mozCaptureStream) {
vid.mozSrcObject = stream;
} else {
vid.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
}
vid.play();
}, function () {
//insertAltVideo(vid);
alert("There was some problem trying to fetch video from your webcam. If you have a webcam, please make sure to accept when the browser asks for access to your webcam.");
});
} else {
//insertAltVideo(vid);
alert("This demo depends on getUserMedia, which your browser does not seem to support. :(");
}
vid.addEventListener('canplay', enablestart, false);
How can I capture an image from the webcam and put it in a div using the code above as a basis?
I'm not sure I can give any more details as I have not got the knowledge on how to do this.
First, draw it to a canvas:
var canvas = document.createElement('canvas');
canvas.height = video.height;
canvas.width = video.width;
canvas.getContext('2d').drawImage(video, 0, 0);
And now you can create the image:
var img = document.createElement('img');
img.src = canvas.toDataURL();
I cannot get to seem to get the screenshot porting working, but the webcam part is working fine, I'll update when complete.
JSFiddle
HTML
<div id="container">
<video autoplay="true" id="videoElement">dsasd</video>
<br>
<button onclick="snap()">Screenshot</button>
<canvas></canvas>
</div>
JS
var video = document.querySelector("#videoElement");
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;
if (navigator.getUserMedia) {
navigator.getUserMedia({
video: true
}, handleVideo, videoError);
}
function handleVideo(stream) {
video.src = window.URL.createObjectURL(stream);
}
function videoError(e) {
// do something
}
// Get handles on the video and canvas elements
var video = document.querySelector('video');
var canvas = document.querySelector('canvas');
// Get a handle on the 2d context of the canvas element
var context = canvas.getContext('2d');
// Define some vars required later
var w, h, ratio;
// Add a listener to wait for the 'loadedmetadata' state so the video's dimensions can be read
video.addEventListener('loadedmetadata', function() {
// Calculate the ratio of the video's width to height
ratio = video.videoWidth / video.videoHeight;
// Define the required width as 100 pixels smaller than the actual video's width
w = video.videoWidth - 100;
// Calculate the height based on the video's width and the ratio
h = parseInt(w / ratio, 10);
// Set the canvas width and height to the values just calculated
canvas.width = w;
canvas.height = h;
}, false);
// Takes a snapshot of the video
function snap() {
// Define the size of the rectangle that will be filled (basically the entire element)
context.fillRect(0, 0, w, h);
// Grab the image from the video
context.drawImage(video, 0, 0, w, h);
}
CSS
container {
margin: 0px auto;
width: 500px;
height: 375px;
border: 10px #333 solid;
}
videoElement, canvas {
width: 500px;
height: 375px;
background-color: #666;
}