【问题标题】:HTML5: crazy canvas drawImage sizingHTML5:疯狂的画布绘制图像大小
【发布时间】:2015-10-20 14:22:50
【问题描述】:

我有下一个 html 模板:

<div className="page">
    <video id="video" autoPlay/>
    <canvas id="canvas"/>
    <div class="btn-wrapper">
        <button onClick={this._takePhoto}>Snap Photo</button>
    </div>
</div>

这是我的应用程序的屏幕截图。在顶部您可以看到&lt;video&gt;,在底部可以看到&lt;canvas/&gt;(关注开发工具)。

问题:&lt;video/&gt;图片绘制到&lt;canvas/&gt;我应该使用这个奇怪的系数:

    const canvas = document.getElementById('canvas'),
          context = canvas.getContext("2d"),
          video = document.getElementById('video');
    //QUESTION: why?!
    const widthCoef = 2.1,
          heigthCoef = 3;
    context.drawImage(video, 
                      0, 
                      0, 
                      video.clientWidth * widthCoef, 
                      video.clientHeight * heightCoef, 
                      0, 
                      0, 
                      canvas.clientWidth, 
                      canvas.clientHeight);

问题: 如果&lt;video/&gt;&lt;canvas/&gt; 具有相同的宽度/高度,为什么我应该使用系数?


如果我做同样的事情但没有系数,比如

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

我得到一张放大的图片:

更新

我的新调整大小版本:

_resizeCanvas() {
    const canvas = document.getElementById('canvas'),
        video = document.getElementById('video');
    const width = +window.getComputedStyle(video, null).getPropertyValue('width').replace('px', ''),
          height = +window.getComputedStyle(video, null).getPropertyValue('height').replace('px', '');
    video.width = width;
    video.height = height;
    canvas.width = width;
    canvas.height = height;
},

更新 2 - 添加小提琴示例

Here is my fiddle example

【问题讨论】:

  • 如何设置画布的宽度/高度?我敢打赌,您弄错了画布的像素宽度/高度(通过 canvas.width/canvas.height 访问)和画布的显示大小(通过 canvas.style.width / height 访问的 ccs 属性)。显示尺寸拉伸画布。除非您有充分的理由这样做(高 dpi 屏幕支持/像素化效果),否则不要更改画布 css w/h,只更改画布 w/h。
  • @GameAlchemist 请查看我在问题底部的更新部分。你的意思是一样的吗?
  • @GameAlchemist 在将width/height 直接设置为画布/视频后,他们停止工作
  • @OrestHera 该代码也不起作用
  • @OrestHera 我正在我的 PC 上的 Chrome 中进行测试

标签: javascript css html html5-canvas html5-video


【解决方案1】:

这些魔法系数对应于video对象大小与画布大小之间的比率。

假设,您的video 大小是(400 x 300),并且您想在画布上以(200 x 150) 的大小显示它。它可以通过以下方式完成:

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

它将调整整个 video 的大小以适应画布。

但是,您使用的是drawImage() 的剪辑版本。前四个坐标参数描述了裁剪参数。例如在以下情况下,它需要四分之一的完整video

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

根据更新的问题进行编辑

图像被剪裁,因为属性canvas.clientWidthcanvas.clientHeight 大于canvas.widthcanvas.height。这是因为 CSS display: flex;。要显示完整的图像使用:

context.drawImage(video, 0, 0, canvas.width, canvas.height);

【讨论】:

  • 不,视频和画布是完全一样的。非剪裁版本也出错了
  • @Volodymyr 我猜 CSS 和相机也存在一些问题。我可以在没有相机的情况下测试它:jsfiddle.net/27ad0etm 和最后一个 CSS 版本jsfiddle.net/ytLgebvg/35
  • 我认为问题在于 css 以错误的方式调整画布大小,但我也无法使用 canvas.width = ... 调整其大小
【解决方案2】:

这里是我的问题的工作小提琴。我还没有时间调查为什么它在我的应用程序上不起作用。近期会更新。

为了让它们都在 JSFiddle 上工作,我添加了 HTML5-video - autoplay 选项,不知道如何在 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>

反应

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>

【讨论】:

    【解决方案3】:

    我对此进行了一些测试,似乎第 3 和第 4 个参数,即您要从源获取的区域的宽度和高度,是相对于媒体的原始大小,而不是大小显示媒体的元素。因此,它基于视频的原始尺寸,而不是视频元素的宽度和高度。

    我很惊讶非剪辑版本不起作用。如果您这样做,似乎应该这样做:

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

    这应该获取完整的视频图像并将其显示在画布的左上角以及画布的整个宽度和高度。

    【讨论】:

    • 它也不起作用,但缩放变得更好了一点
    • 好吧,那么我想我会建议您坚持使用您的工作解决方案。您遇到的行为非常有趣,但如果您有一些行之有效的方法,那么这可能就是要走的路。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-09
    • 1970-01-01
    • 2014-07-24
    • 2012-05-05
    相关资源
    最近更新 更多