使用响应式布局从画布中捕获分段图像
Posted
技术标签:
【中文标题】使用响应式布局从画布中捕获分段图像【英文标题】:Capturing a sectioned image from a canvas with responsive layout 【发布时间】:2021-07-16 06:22:08 【问题描述】:我有一个带有各种 div
元素的画布,这些元素用作表示要捕获的画布区域的框。当捕获发生时,它与屏幕上显示的不完全一样。如何在出现时跟踪和响应捕获 div
部分?
例如,尝试捕获大的紫色部分:
这是组件中的代码:
<template>
<div>
<div class="video-wrapper">
<canvas ref="the-canvas" class="canvas">
</canvas>
<video class="video" ref="video" autoplay />
<div :class="profileCenter.name" :style="profileToStyle(profileCenter)" />
<div :class="profileTopLeft.name" :style="profileToStyle(profileTopLeft)" />
<div :class="profileTopMiddle.name" :style="profileToStyle(profileTopMiddle)" />
<div :class="profileTopRight.name" :style="profileToStyle(profileTopRight)" />
</div>
</div>
</template>
<script lang="ts">
import Vue, Component, Watch from 'vue-property-decorator'
import Header from '@/components/Header.vue'
@Component( components: Header )
export default class Test extends Vue
private userMedia: MediaStream | null = null
private winHeight: number = window.innerHeight
private winWidth: number = window.innerWidth
private profileCenter: any = name: 'profile-box', height: 350, width: 250, top: 40, left: 50, transform: [-50, -50]
private profileTopLeft: any = name: 'profile-box', height: 100, width: 100, top: 20, left: 20, transform: [-50, -50]
private profileTopMiddle: any = name: 'profile-box', height: 100, width: 100, top: 20, left: 50, transform: [-50, -50]
private profileTopRight: any = name: 'profile-box', height: 100, width: 100, top: 20, left: 80, transform: [-50, -50]
async mounted ()
this.$nextTick(() =>
window.addEventListener('resize', () =>
this.updateSizes()
)
)
this.userMedia = await navigator.mediaDevices.getUserMedia( video: true, audio: false )
setTimeout(() =>
const video: htmlVideoElement = document.getElementsByClassName('video')[0] as HTMLVideoElement
const canvas: HTMLCanvasElement = document.getElementsByClassName('canvas')[0] as HTMLCanvasElement
const targetSquare: HTMLDivElement = document.getElementsByClassName('profile-box')[0] as HTMLDivElement
const targetDims = targetSquare.getBoundingClientRect()
canvas.height = targetDims.height
canvas.width = targetDims.width
console.log(canvas.width, canvas.height)
console.log(video.videoWidth, video.videoHeight)
const ctx = canvas.getContext('2d')
ctx!.drawImage(video, targetDims.left, targetDims.top, targetDims.width, targetDims.height, 0, 0, targetDims.width, targetDims.height)
window.open(canvas.toDataURL())
, 3000)
private updateSizes (): void
this.winHeight = window.innerHeight
this.winWidth = window.innerWidth
private profileToStyle (profile: height: number, width: number, top: number, left: number, transform: number[] ): string
return `height: $profile.heightpx; min-height: $profile.heightpx; width: $profile.widthpx; min-width: $profile.widthpx; top: $profile.top%; left: $profile.left%; transform: translate($profile.transform[0]%, $profile.transform[1]%)`
@Watch('userMedia')
private userMediaWatcher ()
this.$refs.video.srcObject = this.userMedia
</script>
<style>
.container--fluid
height: 100%;
overflow: hidden;
.video-wrapper
position: absolute;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
overflow: hidden;
.video-wrapper video
min-width: 100%;
min-height: 100%;
width: auto;
height: auto;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
.canvas
position: absolute;
/* width: 480px; */
/* height: 640px; */
.profile-box
position: absolute;
border: 2px solid purple;
</style>
【问题讨论】:
您的代码的哪一部分进行了捕获?如果您的 sn-p 中缺少它,请添加它... canvas 与各种 div 元素充当框...这可能是你的问题开始了,你为什么不在画布上绘制矩形:w3schools.com/tags/canvas_rect.asp 这样你就知道你有正确的区域后来被提取...是的,就像@biberman指出的示例代码sn-p似乎不完整 图像被绘制并显示在 window.open() 行上。代码不完整。 为什么不使用getImageData
?
ctx!.drawImage
这个语法是什么?
【参考方案1】:
有几个复杂的因素使它在这里有点困难。
drawImage
方法从原始来源获取图像数据。这意味着,您没有绘制呈现给您的视频的一部分。您正在从原始图像/视频捕获中剪下一块。这意味着您必须计算呈现给您的视频尺寸与原始视频尺寸之间的差异。
由于视频在左侧和右侧或顶部和底部“溢出”到容器外,因此您必须考虑到这一点并在顶部或左侧添加偏移量。
最后,要获得屏幕上显示的图像,您必须通过将profile-box
尺寸提供给drawImage
来进行拉伸。
把它们放在一起:
<template>
<section class="home">
<div>
<div class="video-wrapper">
<canvas ref="the-canvas" class="canvas">
</canvas>
<video class="video" ref="video" autoplay/>
<div :class="profileCenter.name" :style="profileToStyle(profileCenter)" />
<div :class="profileTopLeft.name" :style="profileToStyle(profileTopLeft)" />
<div :class="profileTopMiddle.name" :style="profileToStyle(profileTopMiddle)" />
<div :class="profileTopRight.name" :style="profileToStyle(profileTopRight)" />
</div>
</div>
</section>
</template>
<script lang="ts">
import Vue, Component, Watch from 'vue-property-decorator'
import Header from '@/pages/Header.vue'
@Component(components: Header)
export default class Test extends Vue
$refs!:
video: HTMLVideoElement
private userMedia: MediaStream | null = null
private winHeight: number = window.innerHeight
private winWidth: number = window.innerWidth
private profileCenter: any = name: 'profile-box', height: 350, width: 250, top: 40, left: 50, transform: [-50, -50]
private profileTopLeft: any = name: 'profile-box', height: 100, width: 100, top: 20, left: 20, transform: [-50, -50]
private profileTopMiddle: any = name: 'profile-box', height: 100, width: 100, top: 20, left: 50, transform: [-50, -50]
private profileTopRight: any = name: 'profile-box', height: 100, width: 100, top: 20, left: 80, transform: [-50, -50]
async mounted ()
this.$nextTick(() =>
window.addEventListener('resize', () =>
this.updateSizes()
)
)
this.userMedia = await navigator.mediaDevices.getUserMedia( video: true, audio: false )
setTimeout(() =>
const video: HTMLVideoElement = document.getElementsByClassName('video')[0] as HTMLVideoElement
const canvas: HTMLCanvasElement = document.getElementsByClassName('canvas')[0] as HTMLCanvasElement
const targetSquare: HTMLDivElement = document.getElementsByClassName('profile-box')[0] as HTMLDivElement
const targetDims = targetSquare.getBoundingClientRect()
canvas.height = targetDims.height
canvas.width = targetDims.width
const ctx = canvas.getContext('2d')
const originalVideoWidth = video.videoWidth;
const originalVideoHeigth = video.videoHeight;
const videoActualWidth: number = video.getBoundingClientRect().width;
const videoActualHeight: number = video.getBoundingClientRect().height;
const videoContainer: HTMLDivElement = document.getElementsByClassName('video-wrapper')[0] as HTMLDivElement;
const containerWidth: number = videoContainer.getBoundingClientRect().width;
const containerHeight: number = videoContainer.getBoundingClientRect().height;
let videoOffsetLeft = 0;
let videoOffsetTop = 0;
if(containerWidth === videoActualWidth)
videoOffsetTop = ((videoActualHeight - containerHeight) / 2) * (originalVideoHeigth / videoActualHeight)
else
videoOffsetLeft = ((videoActualWidth - containerWidth) / 2) * (originalVideoWidth / videoActualWidth)
const left = videoOffsetLeft + ((originalVideoWidth / videoActualWidth) * targetDims.left);
const top = videoOffsetTop + ((originalVideoHeigth / videoActualHeight) * targetDims.top);
const width = (originalVideoWidth / videoActualWidth ) * targetDims.width;
const height = (originalVideoHeigth / videoActualHeight ) * targetDims.height;
ctx!.drawImage(video,
left, top, width, height, 0, 0, targetDims.width,targetDims.height)
//window.open(canvas.toDataURL())
, 3000)
private updateSizes (): void
this.winHeight = window.innerHeight
this.winWidth = window.innerWidth
private profileToStyle (profile: height: number, width: number, top: number, left: number, transform: number[] ): string
return `height: $profile.heightpx; min-height: $profile.heightpx; width: $profile.widthpx; min-width: $profile.widthpx; top: $profile.top%; left: $profile.left%; transform: translate($profile.transform[0]%, $profile.transform[1]%)`
@Watch('userMedia')
private userMediaWatcher ()
this.$refs.video.srcObject = this.userMedia
</script>
<style>
.container--fluid
height: 100%;
overflow: hidden;
.video-wrapper
position: absolute;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
overflow: hidden;
.video-wrapper video
min-width: 100%;
min-height: 100%;
width: auto;
height: auto;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
img
height: 100%;
width: 100%;
.canvas
position: absolute;
/* width: 480px; */
/* height: 640px; */
.profile-box
position: absolute;
border: 2px solid purple;
</style>
【讨论】:
这会产生白色图像。如果可能,请提供一个演示。此代码无法按预期工作。 我更新了答案以包含整个文件。它应该工作。 (我不知道怎么把它变成代码sn-p,也许你能弄清楚。)以上是关于使用响应式布局从画布中捕获分段图像的主要内容,如果未能解决你的问题,请参考以下文章