Why is my canvas element distorting my image? - javascript

The canvas element is distorting my image and zooming in to the middle left. As seen here.
https://i.ibb.co/ZMCcM88/Screenshot-116.png
The above is what it should look like, the bottom is how its coming out.
My console.log() shows the video and canvas sizes are equal and nothing should be distorted or coming out wrong.
https://i.ibb.co/p0cHDXf/Screenshot-118.png
My code looks like this
Template
<div>
<video ref="video" class="full-width" autoplay playsinline >
<canvas ref="canvas" class="full-width" height="240" >
</div>
Script
export default defineComponent({
name: "CameraPage",
setup() {
const imageCaptured = ref(false);
const video = ref(null);
const canvas = ref(null);
const initCamera = () => {
navigator.mediaDevices
.getUserMedia({
video: true,
})
.then((stream) => {
video.value.srcObject = stream;
});
};
const captureImage = () => {
canvas.width = video.value.getBoundingClientRect().width;
canvas.height = video.value.getBoundingClientRect().height;
let context = canvas.value.getContext("2d");
context.drawImage(video.value, 0, 0, canvas.width, canvas.height);
imageCaptured.value = "True";
};
onMounted(() => {
initCamera();
});
return {
initCamera,
onMounted,
captureImage,
video,
canvas,
imageCaptured,
};
},
});
What am I doing that's distorting the image? Am I getting something in the drawImage() method thats wrong? Is it the "full-width" and "height" in the canvas html attributes?
I just want to "take a screenshot" of the streaming video and make it look like a photo basically.
Im so close but so far away.

Related

I get a blank (dark) screen when I try to launch a picture in picture video based on canvas

Im pretty new to web dev and Svelte, so apologies if this is basic. I can't seem to get picture in picture (PiP) working. It will show up but the screen is a very dark grey, the content is not being displayed. The canvas in question does display elsewhere on the page and I can see it being updated properly.
My browser allows PiP from youtube, etc. so I don't think that is the issue. The relevant code is as follows, its written using Svelte.
<script>
....
const buildPopout = () => {
writeToCanvas();
createVideo();
}
const writeToCanvas = () => {
const canvasEl = document.getElementById("canvas");
const canvasCtx = canvasEl.getContext('2d');
//canvasCtx.font = '52px serif';
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
canvasCtx.textAlign = 'center';
//Note: timeString is set elsewhere and is working, as is this update
canvasCtx.fillText(timeString, canvas.width / 2, canvas.height / 2);
}
//
async function createVideo() {
const canvasEl = document.getElementById("canvas");
console.log('canvasEL: ' + canvasEl); //TODO: Remove
const video = document.createElement('video');
video.muted = true;
video.autoplay = false;
video.controls = true;
video.srcObject = canvasEl.captureStream();
video.play();
video.addEventListener('loadedmetadata', () => {
video.requestPictureInPicture();
});
}
<div>
...
<button on:click={buildPopout}>Pop Out</button>
<canvas id="canvas" width="200" height="100"></canvas>
</div>
The expectation is this should mirror the content of the canvas in the picture in picture container and follow me between sites. It pops the container open and that does follow me. However, it doesn't contain the canvas content.
I can see the canvas and the content is changing, it just isn't showing up in the right place.

HTML5 Canvas captureStream - Problem with hi-res images [duplicate]

I've looked at a sample for Streaming from canvas to video element so I can see that the principle works but i can't get it to play/display a static image in the video.
Here is my code so far with an image borrowed from stackoverflow. How can I change my code to display the canvas as a video?
const canvas = document.getElementById('viewport');
const context = canvas.getContext('2d');
const video = document.getElementById('videoPlayBack');
make_base();
function make_base() {
base_image = new Image();
base_image.onload = function () {
context.drawImage(base_image, 0, 0);
}
base_image.src = "https://cdn.sstatic.net/Img/ico-binoculars.svg?v=d4dbaac4eec9";
}
const stream = canvas.captureStream(25);
video.srcObject = stream;
<canvas id="viewport"></canvas>
<video id="videoPlayBack" playsinline autoplay muted></video>
You are drawing a cross-origin image, this will thus taint the canvas and mute the CanvasCaptureMediaStreamTrack, resulting in no data being passed.
Keep your canvas untainted if you wish to stream it:
const canvas = document.getElementById('viewport');
const context = canvas.getContext('2d');
const video = document.getElementById('videoPlayBack');
make_base();
function make_base() {
base_image = new Image();
base_image.onload = function () {
context.drawImage(base_image, 0, 0, 400, 300);
}
base_image.crossOrigin = "anonymous";
base_image.src = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
}
const stream = canvas.captureStream(25);
video.srcObject = stream;
<canvas id="viewport" width="400" height="300"></canvas>
<video id="videoPlayBack" controls playsinline autoplay muted ></video>
You don't get to save from a canvas if you draw "not your images" on it, because of the canvas security model (the moment you draw any image to it that isn't same origin, and doesn't explicitly have a CORS header that okays your use, you no longer have access to the pixels - not through ImageData, not through toDataURL, etc. etc).
To prevent the canvas from flagging itself as "tainted", see https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for what your options might be.

set canvas data to an input

I want to take a picture from the user and send it to the server without ajax.
I searched and found this code that takes the photo from video and draws it on canvas. now I want to set that image to an input on form but I don't know how.
this is HTML code:
<video id="player" controls autoplay></video>
<button id="capture" onclick="$('#loader').hide()">Capture</button>
<canvas id="canvas" width=320 height=240></canvas>
<input id="image-input">
and this is javascript code :
const player = document.getElementById('player');
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const captureButton = document.getElementById('capture');
const constraints = {
video: true,
};
captureButton.addEventListener('click', () => {
// Draw the video frame to the canvas.
context.drawImage(player, 0, 0, canvas.width, canvas.height);
player.srcObject.getVideoTracks().forEach(track => track.stop());
var Pic = document.getElementById("canvas").toDataURL("image/png");
Pic = Pic.replace(/^data:image\/(png|jpg);base64,/, "")
});
// Attach the video stream to the video element and autoplay.
navigator.mediaDevices.getUserMedia(constraints)
.then((stream) => {
player.srcObject = stream;
});
now I want to set this image as the image of input.
how can I do that?

How to use canvas for parsing imageData from video without appending canvas to the page?

I have Webcam class, that uses for capturing data from webcam. This class streames data to video tag and also draw it on canvas., Also I have QRScanner class, that uses for parsing QR code from imageData of canvas.
class Webcam {
constructor(videoTag, canvasTag) {
// using for real-time video capture
this.videoTag = videoTag;
// qr scanner parses imageData from this element
this.canvasTag = canvasTag;
// waiting for qr code here
this.captureArea = {
x: 100,
y: 60,
width: 120,
height: 120
}
// getting access to webcam
navigator.mediaDevices
.getUserMedia({
video: true
})
.then(stream => this.videoTag.srcObject = stream)
.catch(console.log);
}
capture() {
setInterval(() => {
let ctx = this.canvasTag.getContext('2d');
ctx.drawImage(this.videoTag, 0, 0, 320, 240);
// drawing capture area
ctx.strokeStyle = 'red';
// this arguments also should be passed into qr scanner
ctx.strokeRect(
this.captureArea.x,
this.captureArea.y,
this.captureArea.width,
this.captureArea.height
);
}, 100);
}
}
class QRScanner {
constructor(canvas, router, captureArea) {
this.canvas = canvas;
this.router = router;
this.captureArea = captureArea;
this.qrCode = null;
}
scan() {
setInterval(() => {
let imageData = this.canvas
.getContext('2d')
.getImageData(
this.captureArea.x,
this.captureArea.y,
this.captureArea.width,
this.captureArea.height
).data;
// parsing qr code from canvas
this.qrCode = jsQR(imageData, this.captureArea.width, this.captureArea.height);
if (this.qrCode) {
router.redirect(Router.pages.result, this.qrCode);
let resultPage = document.querySelector('#result .qr-code-data');
resultPage.innerHTML = this.qrCode.data;
}
}, 100);
}
}
<div id="scan">
<video id="video" width="320" height="240" autoplay title="Real-time video stream from webcam"></video>
<canvas id="preview" width="320" height="240" title="Capture area for QR code"></canvas>
</div>
This works as expected, but when I removes canvas element from page and trying to use temporary canvas (using document.createElement('canvas') without appending to the page) - this solution do not work. Does it possible to using temporary canvas for getting video imageData without appending this canvas to the page?
P.S. I'm using https://github.com/cozmo/jsQR
Canvas elements have a default width height when they aren't explicitly set. Since you never set those for you created canvas it is going to default to 300 x 150, at least for Chrome might be different for other browsers implementations.
var canvas = document.createElement("canvas");
console.log(canvas.width,canvas.height)
And since this default size is different than the size that you are trying to draw the image/video to there is going to be some cropping going on and therefore effecting your QR library from properly reading the image.
Just set the width and height to what you need
var canvas = document.createElement('canvas')
canvas.width = 320;
canvas.height = 240;

Create thumbnail from video file via file input

I am attempting to create a thumbnail preview from a video file (mp4,3gp) from a form input type='file'. Many have said that this can be done server side only. I find this hard to believe since I just recently came across this Fiddle using HTML5 Canvas and Javascript.
Thumbnail Fiddle
The only problem is this requires the video to be present and the user to click play before they click a button to capture the thumbnail. I am wondering if there is a way to get the same results without the player being present and user clicking the button. For example: User click on file upload and selects video file and then thumbnail is generated. Any help/thoughts are welcome!
Canvas.drawImage must be based on html content.
source
here is a simplier jsfiddle
//and code
function capture(){
var canvas = document.getElementById('canvas');
var video = document.getElementById('video');
canvas.getContext('2d').drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
}
The advantage of this solution is that you can select the thumbnail you want based on the time of the video.
Recently needed this so I wrote a function, to take in a video file and a desired timestamp, and return an image blob at that time of the video.
Sample Usage:
try {
// get the frame at 1.5 seconds of the video file
const cover = await getVideoCover(file, 1.5);
// print out the result image blob
console.log(cover);
} catch (ex) {
console.log("ERROR: ", ex);
}
Function:
function getVideoCover(file, seekTo = 0.0) {
console.log("getting video cover for file: ", file);
return new Promise((resolve, reject) => {
// load the file to a video player
const videoPlayer = document.createElement('video');
videoPlayer.setAttribute('src', URL.createObjectURL(file));
videoPlayer.load();
videoPlayer.addEventListener('error', (ex) => {
reject("error when loading video file", ex);
});
// load metadata of the video to get video duration and dimensions
videoPlayer.addEventListener('loadedmetadata', () => {
// seek to user defined timestamp (in seconds) if possible
if (videoPlayer.duration < seekTo) {
reject("video is too short.");
return;
}
// delay seeking or else 'seeked' event won't fire on Safari
setTimeout(() => {
videoPlayer.currentTime = seekTo;
}, 200);
// extract video thumbnail once seeking is complete
videoPlayer.addEventListener('seeked', () => {
console.log('video is now paused at %ss.', seekTo);
// define a canvas to have the same dimension as the video
const canvas = document.createElement("canvas");
canvas.width = videoPlayer.videoWidth;
canvas.height = videoPlayer.videoHeight;
// draw the video frame to canvas
const ctx = canvas.getContext("2d");
ctx.drawImage(videoPlayer, 0, 0, canvas.width, canvas.height);
// return the canvas image as a blob
ctx.canvas.toBlob(
blob => {
resolve(blob);
},
"image/jpeg",
0.75 /* quality */
);
});
});
});
}
Recently needed this and did quite some testing and boiling it down to the bare minimum, see https://codepen.io/aertmann/pen/mAVaPx
There are some limitations where it works, but fairly good browser support currently: Chrome, Firefox, Safari, Opera, IE10, IE11, Android (Chrome), iOS Safari (10+).
video.preload = 'metadata';
video.src = url;
// Load video in Safari / IE11
video.muted = true;
video.playsInline = true;
video.play();
You can use this function that I've written. You just need to pass the video file to it as an argument. It will return the dataURL of the thumbnail(i.e image preview) of that video. You can modify the return type according to your need.
const generateVideoThumbnail = (file: File) => {
return new Promise((resolve) => {
const canvas = document.createElement("canvas");
const video = document.createElement("video");
// this is important
video.autoplay = true;
video.muted = true;
video.src = URL.createObjectURL(file);
video.onloadeddata = () => {
let ctx = canvas.getContext("2d");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
video.pause();
return resolve(canvas.toDataURL("image/png"));
};
});
};
Please keep in mind that this is a async function. So make sure to use it accordingly.
For instance:
const handleFileUpload = async (e) => {
const thumbnail = await generateVideoThumbnail(e.target.files[0]);
console.log(thumbnail)
}
The easiest way to display a thumbnail is using the <video> tag itself.
<video src="http://www.w3schools.com/html/mov_bbb.mp4"></video>
Use #t in the URL, if you want the thumbnail of x seconds.
E.g.:
<video src="http://www.w3schools.com/html/mov_bbb.mp4#t=5"></video>
Make sure that it does not include any attributes like autoplay or controls and it should not have a source tag as a child element.
With a little bit of JavaScript, you may also be able to play the video, when the thumbnail has been clicked.
document.querySelector('video').addEventListener('click', (e) => {
if (!e.target.controls) { // Proceed, if there are no controls
e.target.src = e.target.src.replace(/#t=\d+/g, ''); // Remove the time, which is set in the URL
e.target.play(); // Play the video
e.target.controls = true; // Enable controls
}
});
<video src="http://www.w3schools.com/html/mov_bbb.mp4#t=5"></video>
With jQuery Lib you can use my code here. $video is a Video element.This function will return a string
function createPoster($video) {
//here you can set anytime you want
$video.currentTime = 5;
var canvas = document.createElement("canvas");
canvas.width = 350;
canvas.height = 200;
canvas.getContext("2d").drawImage($video, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL("image/jpeg");;
}
Example usage:
$video.setAttribute("poster", createPoster($video));
I recently stumbled on the same issue and here is how I got around it.
firstly it will be easier if you have the video as an HTML element, so you either have it in the HTML like this
<video src="http://www.w3schools.com/html/mov_bbb.mp4"></video>
or you take from the input and create an HTML element with it.
The trick is to set the start time in the video tag to the part you want to seek and have as your thumbnail, you can do this by adding #t=1.5 to the end of the video source.
<video src="http://www.w3schools.com/html/mov_bbb.mp4#t=1.5"></video>
where 1.5 is the time you want to seek and get a thumbnail of.
This, however, makes the video start playing from that section of the video so to avoid that we add an event listener on the video's play button(s) and have the video start from the beginning by setting video.currentTime = 0
const video = document.querySelector('video');
video.addEventListener('click', (e)=> {
video.currentTime = 0 ;
video.play();
})

Categories