Adding video to Javascript Canvas - javascript

Is it possible to add a background video that will loop over and over to a HTML5 Canvas? This is that work I have so far just want to add a simple video that will play to the background. I am able to add video to the HTML with the video tag but want it played on the canvas itself.
<canvas id="ex1" width="525" height="200" style="border: 5px solid black;" ></canvas>
<p id="text"> Increase/Decrease Speed</p>
<input type="button" value="+" id="btnAdd">
<input type="button" value="-" id="btnSub">
<script>
var x = 0;
var y = 15;
var speed = 10;
var isRight = true;
document.getElementById('text').innerText = speed;
document.getElementById('btnAdd').addEventListener('click', function (event) {
if (isRight) speed++;
else speed--;
document.getElementById('text').innerText = speed;
});
document.getElementById('btnSub').addEventListener('click', function (event) {
if (isRight) speed--;
else speed++;
document.getElementById('text').innerText = speed;
});
function animate() {
reqAnimFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame;
reqAnimFrame(animate);
x += speed;
if (x <= 0 || x >= 475) {
speed = -speed;
isRight = !isRight;
}
document.getElementById('text').innerText = speed;
draw();
}
function draw() {
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
context.clearRect(0, 0, 525, 200);
context.fillStyle = "#ff00ff";
context.fillRect(x, y, 40, 40);
}
animate();
</script>

A frame currently running in a video element can be copied to canvas. Using setInterval we have to regularly sample the video element and copy it to canvas. It can be used to do interesting stuff.
From Mozilla drawImage
The first parameter can be any element to draw into the context; the specification permits any image element (that is, <img>, <canvas>, and <video>).
Here is a boiler plater code sample -
var videoElement, canvasContext;
videoElement.addEventListener('play', function(){
copyCurrentFrameIntoCanvas(this);
setInterval(copyCurrentFrameIntoCanvas,10,this);
},false);
function copyCurrentFrameIntoCanvas(videoElement) {
canvasContent.drawImage(videoElement,0,0,<width>,<height>);
}
You need to initialise the videoElement and canvasContent appropriately which i think you know already. Also replace <width> and <height> placeholders appropriately.

You can create an off-screen video element so you won't have to deal with CSS or cause extra reflowing. To create an off-screen video element is simple:
/// element
var video = document.createElement('video');
/// set video elemet size
video.width = 640;
video.height = 360;
/// setup with auto preload and loop
video.preload = 'auto';
video.loop = true;
Now you can attach an event handler so you start drawing when the video is ready to be played:
video.addEventListener('canplay', start, false);
Now set the source of the video. You need to check what types the browser can play by using the video.canPlayType('<mime-type-here>') method but for simplicity we set the source directly:
video.src = 'link/to/video.ogv';
In the handler we can now update the video. As the requestAnimationFrame tries to update 60 times per second we can reduce it to half as video is rarely above 30 FPS (US, 25 in Europe):
function start() {
/// get context from canvas (canvas not shown in example)
var ctx = canvas.getContext('2d'),
toggle = true; /// this is used to reduce FPS
/// start video
video.play();
/// start loop
requestAnimationFrame(loop);
function loop() {
/// reduce frame-rate to half
toggle = !toggle;
if (toggle) {
requestAnimationFrame(loop);
return;
}
/// draw video frame
ctx.draw(video, 0, 0);
requestAnimationFrame(loop);
}
}
Disclaimer: untested but you see the main steps. The code should create the video element and auto-start it when it's ready. Then it will run in a loop and draw the video frame to canvas every 30 FPS (or so).
Note: Safari browser do not support drawImage with a video element in iOS (at the moment writing this answer).

Related

Canvas Flickering in Chrome and OBS

I think this is a chrome issue as it only seems to Chrome, Edge, and OBS. However, that is a problem because that is most of my intended audience. There is no flicker in Firefox so I believe I just need a work around for Chromium based browsers.
The flickering only seems to last for 1 rotation through the animation, however, it will occasionally come back at random times. It also comes back whenever we change the sprite animation
Prior to the below code running we have already imported the image as a Javascript Object. Each image has multiple states we can cycle between. Each state has 10 frames which are loaded as DataURLs and switched between so the flickering would not be caused by loading the images.
Currently we are testing with very few images so the total object size is only a few Kb.
//Set up Canvas Javascript Elements
const canvas = document.getElementById('mainCanvas');
const bufferCanvas = document.getElementById('bufferCanvas');
//Get Canavas 2d context
const ctx = canvas.getContext('2d');
const bctx = bufferCanvas.getContext('2d');
//Set Canvas Size - Broken up into multiple lines to more easily enable and disable elements during testing
const CANVAS_WIDTH = 364;
const CANVAS_HEIGHT = 444;
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
bufferCanvas.width = CANVAS_WIDTH;
bufferCanvas.height = CANVAS_HEIGHT;
//Sets current animation, initial frame, and cycle count
var currentAnimation = 'idle'
var currentFrame = 0;
var cycle = 0;
function animate(){
//Only incrementing frames when the cycle mod 10 is 0 slows it down
if(cycle % 10 == 0){
currentFrame++;
if(currentFrame >= currentImage[currentAnimation].length){
currentFrame = 0;
}
playerImage.src = currentImage[currentAnimation][currentFrame];
}
//Draws the buffer canvas prior to clearing the main canvas
bctx.drawImage(playerImage, 0, 0);
//Clears main Canvas
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
//Draws new image to main Canvas
ctx.drawImage(bufferCanvas, 0, 0);
//Clears buffer canvas once main canvas has been drawn
bctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
//Resets cycle to maintain a manageable number
cycle++;
if(cycle >= 10000){
cycle = 0;
}
//Calls itself to loop
requestAnimationFrame(animate);
}

Keep canvas image after resizing [duplicate]

I have created a basic shape in HTML canvas element which works fine.
The problem occurs when I resize the canvas, all the drawing in the canvas disappears. Is this the normal behavior? or is there a function that can be used to stop this?
One way to fix this could be to call drawing function again on canvas resize however this may not be very efficient if there is huge content to be drawn.
What's the best way?
Here is the link to sample code https://gist.github.com/2983915
You need to redraw the scene when you resize.
setting the width or height of a canvas, even if you are setting it to the same value as before, not only clears the canvas but resets the entire canvas context. Any set properties (fillStyle, lineWidth, the clipping region, etc) will also be reset.
If you do not have the ability to redraw the scene from whatever data structures you might have representing the canvas, you can always save the entire canvas itself by drawing it to an in-memory canvas, setting the original width, and drawing the in-memory canvas back to the original canvas.
Here's a really quick example of saving the canvas bitmap and putting it back after a resize:
http://jsfiddle.net/simonsarris/weMbr/
Everytime you resize the canvas it will reset itself to transparant black, as defined in the spec.
You will either have to:
redraw when you resize the canvas, or,
don't resize the canvas
One another way is to use the debounce if you are concerned with the performance.
It doesnt resize or redraw every position you are dragging. But it will resize only when the it is resized.
// Assume canvas is in scope
addEventListener.("resize", debouncedResize );
// debounce timeout handle
var debounceTimeoutHandle;
// The debounce time in ms (1/1000th second)
const DEBOUNCE_TIME = 100;
// Resize function
function debouncedResize () {
clearTimeout(debounceTimeoutHandle); // Clears any pending debounce events
// Schedule a canvas resize
debounceTimeoutHandle = setTimeout(resizeCanvas, DEBOUNCE_TIME);
}
// canvas resize function
function resizeCanvas () { ... resize and redraw ... }
I had the same problem. Try following code
var wrapper = document.getElementById("signature-pad");
var canvas = wrapper.querySelector("canvas");
var ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
It keeps the drawing as it is
One thing that worked for me was to use requestAnimationFrame().
let height = window.innerHeight;
let width = window.innerWidth;
function handleWindowResize() {
height = window.innerHeight;
width = window.innerWidth;
}
function render() {
// Draw your fun shapes here
// ...
// Keep this on the bottom
requestAnimationFrame(render);
}
// Canvas being defined at the top of the file.
function init() {
ctx = canvas.getContext("2d");
render();
}
I had the same problem when I had to resize the canvas to adjust it to the screen.
But I solved it with this code:
var c = document.getElementById('canvas');
ctx = c.getContext('2d');
ctx.fillRect(0,0,20,20);
// Save canvas settings
ctx.save();
// Save canvas context
var dataURL = c.toDataURL('image/jpeg');
// Resize canvas
c.width = 50;
c.height = 50;
// Restore canvas context
var img = document.createElement('img');
img.src = dataURL;
img.onload=function(){
ctx.drawImage(img,20,20);
}
// Restote canvas settings
ctx.restore();
<canvas id=canvas width=40 height=40></canvas>
I also met this problem.but after a experiment, I found that Resizing the canvas element will automatically clear all drawings off the canvas!
just try the code below
<canvas id = 'canvas'></canvas>
<script>
var canvas1 = document.getElementById('canvas')
console.log('canvas size',canvas1.width, canvas1.height)
var ctx = canvas1.getContext('2d')
ctx.font = 'Bold 48px Arial'
var f = ctx.font
canvas1.width = 480
var f1 = ctx.font
alert(f === f1) //false
</script>

Why is this dynamic HTML5 video thumbnail generation failing in IE11?

I have a Javascript function setup to dynamically generate thumbnails from a given embedded HTML5 video once it loads. This works fine on other browsers. The problem arises with IE11. For some reason, it's just not outputting anything despite working perfectly on Firefox and Chrome.
I have a section of the site which has several HTML5 videos and they need to generate their respective thumbnails based on the first frame. I've done a good bit of researching around and I can't seem to find any IE11 compatibility issues with my code.
Here is the code I have now:
var vids = document.querySelectorAll('[id^=video-]');
for(var i = 0; i < vids.length; i++){
if(vids[i].tagName == 'VIDEO'){
$("#video-" + (i+1)).on("loadeddata", generate_handler(i+1));
}
...
}
generate_handler calls the shoot function (had to do it this way because of scoping issues in the loop),
function generate_handler(j) {
return function(event) {
shoot(j);
};
}
and the shoot function goes as follows:
function shoot(num){
var video = document.getElementById('video-' + num);
var output = document.getElementById('videothumbnail-' + num);
if(output.childNodes.length == 1){
var canvas = capture(video);
output.appendChild(canvas);
}
}
and finally, the capture function is as follows:
function capture(video) {
var canvas = document.createElement('canvas');
var w = 48;
var h = 40;
canvas.width = w;
canvas.height = h;
canvas.style.marginTop = "4px";
canvas.style.width = w + "px";
canvas.style.height = h + "px";
canvas.style.zIndex = "-999";
var ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, w, h);
return canvas;
}
Essentially, as a TL;DR: When the data of the video is loaded, it calls this function to shoot a screengrab and add it to the thumbnail div. In order to do that, it draws the video to a canvas. This is the order the functions are called:
on('loadeddata', generate_handler()) -> shoot() -> capture()
What's strange is that after some simple tests with console.log, it actually is reaching the inside of capture(), which inclines me to believe it's a compatibility issue with something in there, or with appendChild().
What this should do (and does do on Firefox/Chrome) is draw a 48x44px thumbnail of the HTML5 video being loaded on the page. Instead, on IE11 it displays nothing.

Update canvas drawing on click with Javascript

I'm trying to increase the radius of a circle drawn in canvas using JavaScript functions.
There were many topics with similar issues but couldn't find an answer that would fix this one, I've tried using built-in methods that were suggested like setInterval, setTimeout, window.requestAnimationFrame and clearing the canvas to redraw the circle with the updated variable.
So far the setInterval method displayed the update but kept the previous iterations in the canvas, the clear method doesn't work.
Here's the example :
//Define globals
var radiusIncrement = 5;
var ballRadius = 20;
//Helper functions
function increaseRadius() {
ballRadius += radiusIncrement;
}
function decreaseRadius() {
if (ballRadius > 0) {
ballRadius -= radiusIncrement;
}
}
//Draw handler
function draw() {
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(canvas.height/2,canvas.width/2,ballRadius,0,2*Math.PI);
ctx.stroke();
}
//Event handler
setInterval(function() {
draw();
ctx.clearRect(0,0,canvas.width,canvas.height);
}, 100);
<!--Create frame and assign callbacks to event handlers-->
<button type="button" onclick="increaseRadius()">Increase Radius</button>
<button type="button" onclick="decreaseRadius()">Decrease Radius</button>
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #000000;"></canvas>
Rather than using setInterval, is there a way to streamline the code using and use an event handler to refresh the canvas on every onlick ?
Thanks for your help.
Cheers.
If I understand you correctly, you only want to redraw on the button click, i.e. on a radius change. You don't need any timing functions for this, you can call the draw function whenever a radius change happens:
//Define globals
var radiusIncrement = 5;
var ballRadius = 20;
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
//initialize
draw();
//Helper functions
function increaseRadius() {
ballRadius += radiusIncrement;
draw();
}
function decreaseRadius() {
if (ballRadius > 0) {
ballRadius -= radiusIncrement;
draw();
}
}
//Draw handler
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.beginPath();
ctx.arc(canvas.height/2,canvas.width/2,ballRadius,0,2*Math.PI);
ctx.stroke();
}
As you can see I also extracted the variable definition for canvas and ctx out of the draw function, since you don't need to re-assign these on every draw call.

Implementing Pan/Zoom on a HTML5 Canvas with multiple images

I'm working on a site in which a user can upload an image and then apply different effects on it(masks, texsts etc.). This is how the canvas element is called in HTML:
<div>
<div onload="draw();">
<canvas id="canvas" data-girar="0">
<div style="display:none;">
<img onload="draw()" id="imgDisp" src="">
<img id="maskDisp" src="">
</div>
</canvas>
</div>
<div id="text_content"></div>
</div>
The two images are on top of each other and so is the "text_content" div. I want to implement a simple pan/zoom function so they all move together. Is there a way to do that? Thanks in advance. Also, here is the draw(); function:
function draw() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var newImg = document.getElementById('imgDisp');
var newMsk = document.getElementById('maskDisp');
var viewpanel = document.getElementById('view_panel');
var textcontent = document.getElementById('text_content');
document.getElementById("text_content").innerHTML = "";
canvas.width = 700;
canvas.height = 700;
if (newImg.height > 750 && newImg.width > 750){
viewpanel.width = newImg.width/2;
viewpanel.height = newImg.height/2;
textcontent.style.height = "700px";
textcontent.style.width = "700px";
// Draw image
ctx.drawImage(document.getElementById('imgDisp'),0,0,newImg.width/1.3,newImg.height/1.74);
// Draw frame
ctx.drawImage(document.getElementById('maskDisp'),0,0,newMsk.width/1.3,newMsk.height/1.74);
text_size('1')
}
else if (newImg.height < 500 && newImg.width < 500){{}
viewpanel.width = newImg.width;
viewpanel.height = newImg.height;
canvas.height = 240;
canvas.width = 240;
textcontent.style.height = "240px";
textcontent.style.width = "240px";
// Draw image
ctx.drawImage(document.getElementById('imgDisp'),0,0,newImg.width*2,newImg.height*2);
// Draw frame
ctx.drawImage(document.getElementById('maskDisp'),0,0,newMsk.width*2,newMsk.height*2);
text_size('2')
}
}
Here's a copy/paste of part of a paint app I made. The user sets the zoom level (input id of 'zoom') and it's used on all calculations. Whatever the zoom percentage is you want to inverse it (1/n) and since mine is labeled % I use 100/n. My app's pan capabilities come from the outer div being overflow: auto. Also I'm not placing any elements inside the canvases; the canvas has a background image, and the drawings are overlays that can be saved as separate png images. I'm not sure what effect if any this will have on your situation.
$('canvas').mousemove(function(e) {
// x and y are globals, x inits to null, mousedown sets x, mouseup returns x to null
if (x==null) return;
x = (100/$('#zoom').val())*(e.pageX - $(this).offset().left);
y = (100/$('#zoom').val())*(e.pageY - $(this).offset().top);
});

Categories