canvas adjust image to video tag proportions

Viewed 23

I developed a project that when registering, you have to take a picture. Customers asked for the camera or video tag to be at 100% screen height. However, above this camera/video goes a filter, so the video tag receives the width of this image/filter and the height of the screen every time. The video tag has an object-fit:cover to get 100%. The challenge now is to adjust this on the canvas, to make the image appear with the same size. The problem is the width because the width is the same as the filter, there is no way to actually know the width of the video.

Video component

<template>
  <div class="takePhoto__camera">
    <video ref="video" fullscreen="false" playsinline muted></video>
    <img class="takePhoto__camera__filter" src="../../assets/images/filtro.png" ref="filter" alt="Filtro">
    <button class="takePhoto__camera__btn" @click.prevent="photoTaken"></button>
  </div>
</template>

<script>
import { mapMutations } from "vuex"

export default {
  name: "ComponentCameraVideo",

  data(){
    return{
      video: null,
      localStream: null,
    }
  },

  mounted(){
    this.video = this.$refs.video;
    this.init();
  },

  methods: {
    ...mapMutations(["SET_CAMERA"]),

    init(){
      const filter = document.querySelector(".takePhoto__camera__filter");

      // Seta a classe ao body
      document.body.classList.add("takingPicture");

      filter.onload = () => {
        // Pedir permissão para acessar a câmera
        if('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices){
          navigator.mediaDevices.getUserMedia({video: true}).then((stream) => {
            this.video.srcObject = stream;
            this.localStream = stream;
            this.video.play();
            this.setWidthVideo();
          }).catch((error) => {
            console.log(error)
            alert(error);
          })
        }
        else{
          alert("Não foi possível acessar a câmera!");
        }
      }
      
    },

    photoTaken(){
      // Remover a classe do body
      document.body.classList.remove("takingPicture");

      // Setar a câmera na variável vuex
      this.SET_CAMERA(
        {
          "video" : {
            element: this.video,
            width: this.video.offsetWidth, 
            height: this.video.offsetHeight
          },
          "filter" : {
            element: this.$refs.filter,
            width: this.$refs.filter.offsetWidth, 
            height: this.$refs.filter.offsetHeight
          },
          "stream" : this.localStream,
        }
      );

      // Emitir o evento
      this.$emit("photoTaken");

      // Parar a execução da câmera
      // this.localStream.getTracks()[0].stop();
    },

    setWidthVideo(){
      setTimeout(() => {
        // Setar largura para o vídeo de acordo com o filtro
        const video = document.querySelector(".takePhoto__camera video");
        const filter = document.querySelector(".takePhoto__camera__filter");
      
        video.style.width = filter.offsetWidth + "px";
        video.style.height = innerHeight + "px";
      }, 500)
    }
  },
}
</script>

Canvas component

<template>
  <div class="takePhoto__showPhoto">
    <canvas ref="canvas"></canvas>
    <img :src="src" alt="Sua foto">
  </div>
</template>

<script>
import { mapState, mapMutations } from "vuex"

export default {
  name: "ComponentCameraCanvas",

  data() {
    return {
      canvas: null,
      src: "/",
    };
  },

  computed: {
    ...mapState(["camera"])
  },

  mounted() {
    this.canvas = this.$refs.canvas;
    this.getPhoto()
  },

  methods: {
    ...mapMutations(["SET_PHOTO"]),

    getPhoto(){
      const ratio = (window.innerHeight < window.innerWidth) ? 16 / 9 : 9 / 16;
      const ctx = this.canvas.getContext("2d");

      // Setar tamanho para o Canvas
      this.canvas.width = this.camera.filter.width;
      this.canvas.height = this.camera.filter.height;

      // Configs do canvas
      ctx.imageSmoothingEnabled = true;
      ctx.imageSmoothingQuality = "high";
      
      // Filtro
      let filter = new Image();
      filter.src = this.camera.filter.element.src

      // Inserindo as imagens no canvas
      ctx.drawImage(this.camera.video.element, 0, 0);
      ctx.drawImage(filter, 0, 0, this.camera.filter.width, this.camera.filter.height);

      // Parar a execução da câmera
      this.camera.stream.getTracks()[0].stop()

      // Salvar na variável photo - vuex
      this.SET_PHOTO(this.canvas.toDataURL("image/jpeg"));

      // Motrar na tag image
      this.src = this.canvas.toDataURL("image/jpeg");
    },
  },
}
</script>

<style lang="scss" scoped>
#picture {
  display: block;
  width: 100%;
  height: 100vh;
  box-sizing: border-box;

  canvas {
    display: block;
    width: 100%;
    height: 100%;
  }
}
</style>

I'm using vue to store the values

https://torcida.petronas.com.br/stg

1 Answers

The <canvas> tag also accepts the same object-fit and object-position as <video> and <img>.
So for your case, you set your canvas size to the intrinsic size of your video (.videoWidth & .videoHeight), then you explicitely set the CSS size and object-XXX of your canvas, amd it'll behave the same as your <video>.

Related