HTML5 Mirroring webcam canvas

Rob picture Rob · Aug 19, 2015 · Viewed 10.7k times · Source

I'm trying to take a webcam feed - (landscape format), cut out the middle bit (portrait format) and have it render to a canvas so that it fills the screen portrait 1080px by 1920px (for this I scale the bit I cut out by 3.8). I then need to flip this canvas so the image is mirrored. I've succeeded in cutting out the middle bit, and rendering this to the canvas... I just cant figure out how to flip it.

Edit

Thank you to all the people who have pointed me at context.scale(-1, 1) - my problem is, I'm already using scale... I think my issues are to do with saving the context, but everything I try has failed to work?

<script>
       // Put event listeners into place
        window.addEventListener("DOMContentLoaded", function() {
            // Grab elements, create settings, etc.
            var canvas = document.getElementById("canvas"),
                context = canvas.getContext("2d"),
                video = document.getElementById("video"),
                videoObj = { 
                    video: {
                        mandatory: {
                            minWidth: 1280,
                            minHeight: 720,
                            /*Added by Chad*/
                            maxWidth: 1280,
                            maxHeight: 720
                        }
                    }
                },
                errBack = function(error) {
                    console.log("Video capture error: ", error.code); 
                };

            // Put video listeners into place
            if(navigator.getUserMedia) { // Standard
                navigator.getUserMedia(videoObj, function(stream) {
                    video.src = stream;
                    video.play();
                }, errBack);
            } else if(navigator.webkitGetUserMedia) { // WebKit-prefixed
                navigator.webkitGetUserMedia(videoObj, function(stream){
                    video.src = window.URL.createObjectURL(stream);
                    video.play();
                }, errBack);
            } else if(navigator.mozGetUserMedia) { // WebKit-prefixed
                navigator.mozGetUserMedia(videoObj, function(stream){
                    video.src = window.URL.createObjectURL(stream);
                    video.play();
                }, errBack);
            }

            /*
                video : video source tag
                320,0 : the shift coords
                320,180 : the canvas size
                0,0 : no shift in the canvas
                640,360 : important ! it’s the native resolution of video source
            */
            context.scale(3.8,3.8);

            function loop(){
               context.drawImage(video, 450, 0, 1080, 1920, 0, 0, 720, 1280);
               setTimeout(loop, 1000 / 30);
            }

            loop();


        }, false);
</script> 


    <video id="video" height="1080" width="1920" autoplay></video>
    <canvas id="canvas" height="1920" width="1080"></canvas>


// Put event listeners into place
window.addEventListener("DOMContentLoaded", function() {
    // Grab elements, create settings, etc.
    var canvas = document.getElementById("canvas"),
        context = canvas.getContext("2d"),
        video = document.getElementById("video"),
        videoObj = { 
            video: {
                mandatory: {
                    minWidth: 1280,
                    minHeight: 720,
                    /*Added by Chad*/
                    maxWidth: 1280,
                    maxHeight: 720
                }
            }
        },
        errBack = function(error) {
            console.log("Video capture error: ", error.code); 
        };

    // Put video listeners into place
    if(navigator.getUserMedia) { // Standard
        navigator.getUserMedia(videoObj, function(stream) {
            video.src = stream;
            video.play();
        }, errBack);
    } else if(navigator.webkitGetUserMedia) { // WebKit-prefixed
        navigator.webkitGetUserMedia(videoObj, function(stream){
            video.src = window.URL.createObjectURL(stream);
            video.play();
        }, errBack);
    } else if(navigator.mozGetUserMedia) { // WebKit-prefixed
        navigator.mozGetUserMedia(videoObj, function(stream){
            video.src = window.URL.createObjectURL(stream);
            video.play();
        }, errBack);
    }

    /*
        video : video source tag
        320,0 : the shift coords
        320,180 : the canvas size
        0,0 : no shift in the canvas
        640,360 : important ! it’s the native resolution of video source
    */




    context.scale(-3.8,3.8);
    context.translate(-720,0);
    function loop(){
       context.drawImage(video, 450, 0, 1080, 1920, 0, 0, 720, 1280);
       setTimeout(loop, 1000 / 30);
    }

    loop();


}, false);

Answer

Kaiido picture Kaiido · Aug 20, 2015

You shouldn't do the crop using the ctx.scale and ctx.translate methods.

Instead, on load of your video, calculate the cropping positions and then in the call of your drawing loop, apply those calculated positions.

When done, it's easy to apply the context.scale(-1, 1); as proposed by @Mahout.
Note that you'll also need to context.translate(canvas.width, 0); before applying the scale().

I refactored your code because the way you were requesting the video mandatories is out of date (as is chrome about it).

I changed your loop too, in order to call it only when the video has loaded, no need to try to draw anything that doesn't exists yet, and I changed your setTimeout with the requestAnimationFrame method, which fires approximatly at 30fps.

// Put event listeners into place
window.addEventListener("DOMContentLoaded", function() {
    // Grab elements, create settings, etc.
    var canvas = document.getElementById("canvas"),
        context = canvas.getContext("2d"),
        // we don't need to append the video to the document
        video = document.createElement("video"),
        videoObj = 
        navigator.getUserMedia || navigator.mozGetUserMedia ? // our browser is up to date with specs ?
        { 
        video: {
            width: { min: 1280,  max: 1280 },
            height: { min: 720,  max: 720 },
            require: ['width', 'height']
            }
        }:
        {
        video: {
            mandatory: {
                minWidth: 1280,
                minHeight: 720,
                maxWidth: 1280,
                maxHeight: 720
            }
        }
    },
    errBack = function(error) {
        console.log("Video capture error: ", error.code); 
    };
    // create a crop object that will be calculated on load of the video
    var crop;
    // create a variable that will enable us to stop the loop.
    var raf;
    
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
    // Put video listeners into place
        navigator.getUserMedia(videoObj, function(stream) {
            video.srcObject = stream;
            video.onplaying = function(){
                var croppedWidth = ( Math.min(video.videoHeight, canvas.height) / Math.max(video.videoHeight,canvas.height)) * Math.min(video.videoWidth, canvas.width),
                croppedX = ( video.videoWidth - croppedWidth) / 2;
                crop = {w:croppedWidth, h:video.videoHeight, x:croppedX, y:0};
                // call our loop only when the video is playing
                raf = requestAnimationFrame(loop);
                };
            video.onpause = function(){
                // stop the loop
                cancelAnimationFrame(raf);
                }
            video.play();
        }, errBack);

    function loop(){
       context.drawImage(video, crop.x, crop.y, crop.w, crop.h, 0, 0, canvas.width, canvas.height);
       raf = requestAnimationFrame(loop);
    }
// now that our video is drawn correctly, we can do...
context.translate(canvas.width, 0);
context.scale(-1,1);

}, false);
body,html{margin:0}
canvas{ border:1px solid;}
<canvas id="canvas" height="1920" width="1080"></canvas>

jsfiddle for chrome