使用响应式布局从画布中捕获分段图像

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,也许你能弄清楚。)

以上是关于使用响应式布局从画布中捕获分段图像的主要内容,如果未能解决你的问题,请参考以下文章

Power Apps 创建响应式布局

Power Apps 创建响应式布局

Power Apps 创建响应式布局

一行 CSS 代码实现响应式布局 – 使用 Grid 实现的响应式布局

grid实现响应式布局

具有浮动和全宽布局的响应式图像