html - 从具有响应式布局的 Canvas 中捕获切片图像

标签 html typescript vue.js canvas html5-video

我有一张 Canvas ,上面有各种 div元素用作表示要捕获的 Canvas 区域的框。当捕获发生时,它并不完全像屏幕上显示的那样。我如何跟踪和响应捕获 div出现的部分?
例如, try catch 大的紫色部分:
enter image description here
enter image description here
这是来自组件的代码:

<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方法从原始来源获取其图像数据。意思是,您没有绘制呈现给您的视频的一部分。您正在从原始图像/视频捕获中剪下一块。这意味着您必须计算呈现给您的视频尺寸与原始视频尺寸之间的差异。
由于视频在容器的左侧和右侧或顶部和底部“溢出”,因此您必须考虑到这一点并在顶部或左侧添加偏移量。
最后,要获得屏幕上显示的图像,您必须通过提供 profile-box 进行拉伸(stretch)。尺寸为 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.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>

关于html - 从具有响应式布局的 Canvas 中捕获切片图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67206298/

相关文章:

vue.js - 我如何从 Vuex 的另一个商店调用 setter/getter ?

javascript - 关于 import 如何知道去 package.json 的解释

php - 使用 PHP 基于 URL 参数选择多个下拉菜单

android - 更改 html 文件但 apk 未在 cordova 中更新

javascript - 在 div 中添加嵌套的 if else 语句

Angular 2 : Dynamic load component Error

angular - 如何在 ionic 2 中创建叠加页面?

javascript - VueJS : Google Maps loads before data is ready - how to make it wait?(Nuxt)

flask - 如何将Vue.js与Flask结合使用?

html - 使用属性 : Title, rel, target 创建 anchor 链接的标准方法是什么?