HTML5 Mirroring webcam canvas
Asked Answered
D

3

6

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);
Deprive answered 19/8, 2015 at 20:22 Comment(2)
There was a similar question about flipping context vertically, hopefully it can help.Dexter
Also, there's a nice tutorial on mirroring canvas.Dexter
D
7

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

Disinclination answered 20/8, 2015 at 3:9 Comment(1)
Thanks Kalido - this is exactly what I needed! :) Also, thank you for the explanation - its given me a better understanding of canvas.Deprive
I
1

For flipping horizontally you can use context.scale(-1, 1);.

From http://www.html5canvastutorials.com/advanced/html5-canvas-mirror-transform-tutorial/

EDIT

As a last resort, this CSS could be used

#canvas {
        -moz-transform: scaleX(-1);
        -o-transform: scaleX(-1);
        -webkit-transform: scaleX(-1);
        transform: scaleX(-1);
        filter: FlipH;
        -ms-filter: "FlipH";
}

If required, this could all be applied dynamically with javascript. Untested but should hopefully work!

Inartificial answered 19/8, 2015 at 20:26 Comment(11)
Hi Sam, Yep - I read about that... the problem I have is that I'm already using scale...Deprive
Woops didn't see this. In which case just use context.scale(-3.8,3.8); instead of the original call.Inartificial
Hi Sam, I tried that - and it didnt work... so I read I had to multiply the width by -1... but that caused it to render off screenDeprive
@Deprive ofcourse it renders off screen because scale multiplies each point x,y values by the values given (x * -1, y * 1). in your case seems that you can solve this by translating by image width context.scale(-1, 1);context.translate(-720,0)Stop
@user2786485 - thanks. I tried that, and now get half the frame of video - and its kinda squishedDeprive
where are you doing the scale/translate? inside the function or outside?Stop
Outside - I'll update my question with the revised codeDeprive
You could try moving it inside the function and then at the end of each function call context.restore() - worth a try, perhaps.Inartificial
Hi Mahout - thanks, unfortunately, I need to capture the canvas later on - so a css transform won't work here.Deprive
I think you should try to adjust the drawImage parameters to fit your needs. notice in the canvas parameters, height is greater than width, but in drawImage it is the opposite.Stop
also note that context.drawImage(video, 450, 0, 1080, 1920, 0, 0, 720, 1280); means: clipX = 450; clipY = 0; clipWidth = 1080; clipHeight = 1920; x = 0; y = 0; width=720; height=1280; maybe clipX is cutting your imageStop
C
0

Quick Answer (Tried and Tested):

let context = document.getElementById("canvas").getContext("2d");
context.translate(width, 0)
context.scale(-1, 1)
context.drawImage(image, 0, 0, width, height)
Chromosome answered 8/10, 2022 at 17:55 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.