使用响应式布局从 canvas 捕获切片图像
Capturing a sectioned image from a canvas with responsive layout
我有一个 canvas,其中包含各种 div
元素,用作表示要捕获的 canvas 区域的框。捕获发生时,它与屏幕上显示的并不完全相同。我如何跟踪并响应地捕获 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.height}px; min-height: ${profile.height}px; width: ${profile.width}px; min-width: ${profile.width}px; 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>
有几个复杂的因素使这里有点困难。
drawImage
方法从原始来源获取其图像数据。意思是,您没有绘制呈现给您的视频的一部分。您正在从原始 image/video 捕获中剪下一块。这意味着您必须计算呈现给您的视频尺寸与原始视频尺寸之间的差异。
由于视频 'overflows' 在左侧和右侧或顶部和底部超出容器,您必须考虑到这一点并在顶部或左侧添加偏移量。
最后,要获得屏幕上显示的图像,您必须通过向 drawImage
提供 profile-box
尺寸来进行拉伸。
总而言之:
<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.height}px; min-height: ${profile.height}px; width: ${profile.width}px; min-width: ${profile.width}px; 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>
我有一个 canvas,其中包含各种 div
元素,用作表示要捕获的 canvas 区域的框。捕获发生时,它与屏幕上显示的并不完全相同。我如何跟踪并响应地捕获 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.height}px; min-height: ${profile.height}px; width: ${profile.width}px; min-width: ${profile.width}px; 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>
有几个复杂的因素使这里有点困难。
drawImage
方法从原始来源获取其图像数据。意思是,您没有绘制呈现给您的视频的一部分。您正在从原始 image/video 捕获中剪下一块。这意味着您必须计算呈现给您的视频尺寸与原始视频尺寸之间的差异。
由于视频 'overflows' 在左侧和右侧或顶部和底部超出容器,您必须考虑到这一点并在顶部或左侧添加偏移量。
最后,要获得屏幕上显示的图像,您必须通过向 drawImage
提供 profile-box
尺寸来进行拉伸。
总而言之:
<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.height}px; min-height: ${profile.height}px; width: ${profile.width}px; min-width: ${profile.width}px; 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>