HTML5: crazy canvas drawImage sizing

前端 未结 3 631
故里飘歌
故里飘歌 2021-01-03 10:19

I have the next html template:

3条回答
  • 2021-01-03 10:53

    Those magic coefficients correspond to the ratio between the video object size and the size of canvas.

    Suppose, your video size is (400 x 300) and you want to show it on the canvas with size (200 x 150). It can be done just by:

    context.drawImage(video,0,0,200,150);
    

    It will resize full video to fit the canvas.

    However, you are using clipping version of drawImage(). The first four coordinate arguments describe clipping parameters. For example in the following case it takes quarter of full video:

    context.drawImage(video,0,0,200,150,0,0,200,150);
    

    Edit according to updated question

    The image is clipped, since properties canvas.clientWidth and canvas.clientHeight are larger than canvas.width and canvas.height. That happens because of CSS display: flex;. To show full image use:

    context.drawImage(video, 0, 0, canvas.width, canvas.height);
    
    0 讨论(0)
  • 2021-01-03 10:58

    Here are working fiddles for my problem. I haven't time yet to investigate why it didn't work on my app. Will update in near future.

    To make them both work on JSFiddle I added HTML5-video - autoplay option, don't know how to do the same on StackOverflow

    1. JS + JQuery https://jsfiddle.net/ytLgebvg/42/
    2. React.js https://jsfiddle.net/69z2wepo/18812/

    JS + JQuery

    function redraw(canvas) {
        var context = canvas.getContext("2d");
        context.strokeStyle = 'blue';
        context.lineWidth = '5';
        context.strokeRect(0, 0, window.innerWidth, window.innerHeight);
    }
    		
    function resizeCanvas(canvas, width, height) {
        var canvasClWidthBefore = +window.getComputedStyle(canvas, null).getPropertyValue('width').replace('px', ''),
            canvasClHeightBefore = +window.getComputedStyle(canvas, null).getPropertyValue('height').replace('px', '');
        alert('canvas cl before: ' + canvasClWidthBefore + 'x' + canvasClHeightBefore);
        
        canvas.width = width;
        canvas.height = height;
        
        var canvasClWidthAfter = +window.getComputedStyle(canvas, null).getPropertyValue('width').replace('px', ''),
            canvasClHeightAfter = +window.getComputedStyle(canvas, null).getPropertyValue('height').replace('px', '');
        alert('canvas cl before: ' + canvasClWidthAfter + 'x' + canvasClHeightAfter);
        
        redraw(canvas);
    }
    
    $(document).ready(function() {
    	// Grab elements, create settings, etc.
    	var canvas = document.getElementById("canvas"),
    	    context = canvas.getContext("2d"),
    	    video = document.getElementById("video"),
    	    videoObj = { "video": true };
    	navigator.webkitGetUserMedia(videoObj, function(stream){
    	    video.src = window.URL.createObjectURL(stream);
    	    video.play();
    	}, function() {});
        
        $('#snap-photo-btn').click(function() {
            const canvas = document.getElementById("canvas"),
                  context = canvas.getContext("2d"),
                  video = document.getElementById("video");
            $('#canvas').removeClass('photoMode');
            $('#canvas').addClass('editMode');
            $('#video').removeClass('photoMode');
            $('#video').addClass('editMode');
            var videoClWidth = +window.getComputedStyle(video, null).getPropertyValue('width').replace('px', ''),
                videoClHeight = +window.getComputedStyle(video, null).getPropertyValue('height').replace('px', '');       
            
            resizeCanvas(canvas, videoClWidth, videoClHeight);
            
    		var canvasWidth = canvas.width,
                canvasHeight = canvas.height;     
    
    
                
            alert('canvas: ' + canvasWidth + 'x' + canvasHeight);
            alert('video cl: ' + videoClWidth + 'x' + videoClHeight);
            context.drawImage(video, 0, 0, canvasWidth, canvasHeight);
        });
    });
    .main {
        position: relative;
    }
    
    video {
        position: absolute;
        top: 0;
        left: 0;
        min-width: 100%;
        min-height: 100%;
        height: auto;
        width: auto;
    }
    video.photoMode { z-index: 1000; }
    video.editMode { z-index: -1000; }
    
    canvas {
        /* position: absolute;
        top: 0;
        left: 0;
        min-width: 100%;
        min-height: 100%;
        height: auto;
        width: auto; */
    }
    canvas.photoMode { z-index: -1000; }
    canvas.editMode { z-index: 1000; }
    
    .btn-wrapper {
        position: absolute;
        width: 100%;
        top: 0;
        left: 0;
        display: flex;
        flex-direction: row;
        justify-content: center;
        z-index: 2000;    
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <div class="main">
        <video class="photoMode" id="video" autoPlay></video>
        <canvas class="photoMode" id="canvas"></canvas>
        <div class="btn-wrapper">
            <button id="snap-photo-btn">Snap Photo</button>
        </div>
    </div>

    React

    var Hello = React.createClass({
        
        getInitialState() {
            return { photoMode: true };
        },
    
        render: function() {
            const clazz = this.state.photoMode ? 'photoMode' : 'editMode';
            return (
            <div className="main">
        		<video className={clazz} id="video" autoPlay></video>
        		<canvas className={clazz} id="canvas"></canvas>
        		<div className="btn-wrapper">
            		<button id="snap-photo-btn" onClick={this._snapPhoto}>Snap Photo</button>
        		</div>
    		</div>
            );
        },
        
        componentDidMount() {
        	var canvas = document.getElementById("canvas"),
    	    context = canvas.getContext("2d"),
    	    video = document.getElementById("video"),
    	    videoObj = { "video": true };
            navigator.webkitGetUserMedia(videoObj, function(stream){
                video.src = window.URL.createObjectURL(stream);
                video.play();
            }, function() {});
        },
        
        _snapPhoto() {
        	const canvas = document.getElementById("canvas"),
                  context = canvas.getContext("2d"),
                  video = document.getElementById("video");
            var videoClWidth = +window.getComputedStyle(video, null).getPropertyValue('width').replace('px', ''),
                videoClHeight = +window.getComputedStyle(video, null).getPropertyValue('height').replace('px', '');       
            
            this._resizeCanvas(canvas, videoClWidth, videoClHeight);
    		var canvasWidth = canvas.width,
                canvasHeight = canvas.height;     
                
            alert('canvas: ' + canvasWidth + 'x' + canvasHeight);
            alert('video cl: ' + videoClWidth + 'x' + videoClHeight);
            context.drawImage(video, 0, 0, canvasWidth, canvasHeight);
            this.setState({photoMode: false});
        },
        
        _resizeCanvas(canvas, width, height) {
            var canvasClWidthBefore = +window.getComputedStyle(canvas, null).getPropertyValue('width').replace('px', ''),
                canvasClHeightBefore = +window.getComputedStyle(canvas, null).getPropertyValue('height').replace('px', '');
            alert('canvas cl before: ' + canvasClWidthBefore + 'x' + canvasClHeightBefore);
    
            canvas.width = width;
            canvas.height = height;
    
            var canvasClWidthAfter = +window.getComputedStyle(canvas, null).getPropertyValue('width').replace('px', ''),
                canvasClHeightAfter = +window.getComputedStyle(canvas, null).getPropertyValue('height').replace('px', '');
            alert('canvas cl after: ' + canvasClWidthAfter + 'x' + canvasClHeightAfter);
    
            this._redraw(canvas);
        },
        
        _redraw(canvas) {
            var context = canvas.getContext("2d");
            context.strokeStyle = 'blue';
            context.lineWidth = '5';
            context.strokeRect(0, 0, window.innerWidth, window.innerHeight);
        }
    });
     
    ReactDOM.render(
    	<Hello />,
        document.getElementById('container')
    );
    .main {
        position: relative;
    }
    
    video {
        position: absolute;
        top: 0;
        left: 0;
        min-width: 100%;
        min-height: 100%;
        height: auto;
        width: auto;
    }
    video.photoMode { z-index: 1000; }
    video.editMode { z-index: -1000; }
    
    canvas {}
    canvas.photoMode { z-index: -1000; }
    canvas.editMode { z-index: 1000; }
    
    .btn-wrapper {
        position: absolute;
        width: 100%;
        top: 0;
        left: 0;
        display: flex;
        flex-direction: row;
        justify-content: center;
        z-index: 2000;    
    }
    <script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
    
    <div id="container">
        <!-- This element's contents will be replaced with your component. -->
    </div>

    0 讨论(0)
  • 2021-01-03 11:13

    I did some testing on this, and it seems like the 3rd and 4th parameters, which are the width and height of the area you want to take from the source, are relative to the original size of the media, NOT the size of the element the media is displayed in. So, it's based on the original dimensions of your video, and not the width and height of your video element.

    I'm surprised the non-clipping version doesn't work though. It seems like it should if you do:

    context.drawImage(video, 0, 0, canvas.clientWidth, canvas.clientHeight);
    

    This should take the full video image and display it at the top left corner of the canvas and over the full width and height of the canvas.

    0 讨论(0)
提交回复
热议问题