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);
isherwood
  • 58,414
  • 16
  • 114
  • 157
Rob
  • 1,576
  • 3
  • 22
  • 52
  • 1
    There was a similar [question about flipping context vertically](http://stackoverflow.com/questions/2634456/flip-canvas-rotate-180deg-after-being-published-on-page), hopefully it can help. – rishat Aug 19 '15 at 20:25
  • 1
    Also, there's [a nice tutorial on mirroring canvas](http://www.html5canvastutorials.com/advanced/html5-canvas-mirror-transform-tutorial/). – rishat Aug 19 '15 at 20:26

3 Answers3

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

Kostas Minaidis
  • 4,681
  • 3
  • 17
  • 25
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thanks Kalido - this is exactly what I needed! :) Also, thank you for the explanation - its given me a better understanding of canvas. – Rob Aug 20 '15 at 04:02
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!

Mahout
  • 414
  • 1
  • 3
  • 12
  • Hi Sam, Yep - I read about that... the problem I have is that I'm already using scale... – Rob Aug 19 '15 at 20:28
  • Woops didn't see this. In which case just use `context.scale(-3.8,3.8);` instead of the original call. – Mahout Aug 19 '15 at 20:31
  • 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 screen – Rob Aug 19 '15 at 20:34
  • 1
    @Rob 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)` – GuiDocs Aug 19 '15 at 20:42
  • @user2786485 - thanks. I tried that, and now get half the frame of video - and its kinda squished – Rob Aug 19 '15 at 20:54
  • where are you doing the scale/translate? inside the function or outside? – GuiDocs Aug 19 '15 at 20:57
  • Outside - I'll update my question with the revised code – Rob Aug 19 '15 at 21:03
  • You could try moving it inside the function and then at the end of each function call `context.restore()` - worth a try, perhaps. – Mahout Aug 19 '15 at 21:06
  • Hi Mahout - thanks, unfortunately, I need to capture the canvas later on - so a css transform won't work here. – Rob Aug 19 '15 at 21:39
  • 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. – GuiDocs Aug 19 '15 at 21:51
  • 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 image – GuiDocs Aug 19 '15 at 22:07
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)