裁剪视频元素以适合面料js画布

问题描述

我正在尝试让用户的相机拍摄快照,然后将最终结果渲染到方形fabricjs画布上。

我使用浏览器的本机网络摄像头功能,因此在笔记本电脑上分辨率为640x480。我向用户显示的方式是在设置为“ object-fit:cover”的正方形视图元素“ camera-view”中显示视频提要,以删除信箱(然后放大以匹配高度)。然后将图片放置在方形画布“用户照片”上,该画布也被设置为“对象适合:封面”以包含矩形图像(隐藏侧面并与视频匹配)。图像还需要以人像的方式正确显示在手机摄像头上,这似乎可以正常工作。

尝试将此“用户照片”复制到Fabric JS画布时出现我的问题。我希望它只对它做一个正方形的画布。但是,它始终会偏离中心。我不想硬编码任何值,因为画布和视频框的大小可能会更改,或者视频的分辨率不同。我很确定在绘制新画布时我已经进行了一些计算,否则“对象适合:覆盖”约束可能会导致这种行为。

最近两天,我一直在寻找并尝试如何正确地实现此目标,并且已经接近了,但是它仍然无法正常工作。

在这里使用过fabric.js - create Image object from ImageData object of canvas API ,这里Crop Functionality using FabricJs以及在Stack Overflow附近使用的其他部件。

这是代码的主要部分,完整的工作在这里JS Fiddle

const cameraview = document.querySelector("#camera-view");
const userPhoto = document.querySelector("#user-photo");

var canWidth = 400;     //Will be calculate dynamically
var canHeight = 400;

var st = 'width:' + canWidth.toString() + 'px; height:' + canHeight.toString() + 'px';
cameraview.setAttribute("style",st);
userPhoto.setAttribute("style",st);

var canvas = new fabric.Canvas('photo-adjust',{
  width: canWidth,height: canHeight
});

function BuildFabricCanvas() {
const can = document.getElementById('user-photo');
var ctx = can.getContext('2d');

var landscape = can.width > can.height; //true if in landscape orientation
var leftTrim = landscape ? (can.width - can.height) : 0;    //Not sure if works on portrait
var topTrim = landscape ? (can.height - canHeight)/2 : (can.height-can.width); //Not sure if  works on portrait

var data = ctx.getimageData(leftTrim,topTrim,canWidth,canHeight); //Probably have these wrong

var c = document.createElement('canvas');
c.setAttribute('id','_temp_canvas');
c.width = canWidth;
c.height = canHeight;
c.getContext('2d').putimageData(data,0);

fabric.Image.fromURL(c.toDataURL(),function(img) {
  img.set({
  'flipX': true,});
  img.left = 0;
  img.top = 0;
  canvas.add(img);
  canvas.centerObject(img);
  img.bringToFront();
  c = null;
  $('#_temp_canvas').remove();
  canvas.renderAll();
  });
}

function WebcamSetup() {
navigator.mediaDevices
.getUserMedia({
  video: {
    facingMode: "user"
  },audio: false
})
.then(function(stream) {
  track = stream.getTracks()[0];
  cameraview.srcObject = stream;
})
.catch(function(error) {
  console.error("Oops. Something is broken.",error);
});

cameraview.classList.remove('d-none');
userPhoto.classList.add("d-none");
disableButton('next');
document.getElementById('retake-photo').style.visibility = 'hidden';
document.getElementById('take-photo').style.visibility = 'visible';
}

WebcamSetup();

function TakeSnapshot() {
userPhoto.classList.remove("d-none");
userPhoto.width = cameraview.videoWidth;
userPhoto.height = cameraview.videoHeight;
userPhoto.getContext("2d").drawImage(cameraview,0);
cameraview.classList.add('d-none');

EnableButton('next');
TurnOffWebcam();
document.getElementById('take-photo').style.visibility = 'hidden';
document.getElementById('retake-photo').style.visibility = 'visible';
}

解决方法

好,所以我现在设法将其整理出来。

我遇到问题的主要原因是,不同的相机提供了不同的分辨率和纵横比,而我在绘制画布时没有正确使用它们。我现在非常精通在getContext(“ 2d”)。drawImage()中使用最大数量的参数。哈。

您可以在此处查看有效的3步版本: JS Fiddle

第一步显示将矩形网络摄像头裁剪为一个方形框。 第二步获取矩形视频源,从中创建图像,然后将其绘制到具有计算的偏移量的新画布上,以得到1:1正方形图像。 第三步将画布重新绘制到fabricjs画布上作为背景层。

第二步和第三步可能可以合并为一个步骤,但是出于我的目的,我想要一个普通的画布,然后是一个fabricjs画布。

这也是JavaScript代码:

var canvasSquare; //Used for our image sizing
var boxWidth; //Used for our resonsive div sizing
var vidW,vidH; //Calculate our webcame feeds width and height
//Canvases
const cameraView = document.querySelector("#camera-view");
const userPhoto = document.querySelector("#user-photo");
var canvasFab = new fabric.Canvas('photo-adjust',{});
//Div setup for buttons
const cameraDiv = document.querySelector("#camera");
const resultDiv = document.querySelector("#result");
const fabricDiv = document.querySelector("#fabric");

//Webcam Setup and usage
var constraints = {
  video: {
    width: {
      ideal: 4096
    },height: {
      ideal: 4096
    },facingMode: "user"
  },audio: false
};

//Sets up all the divs to be the same size 
function SetupSizes() {
  boxWidth = document.getElementById("box-width").offsetWidth;
  var st = 'width:' + boxWidth.toString() + 'px; height:' + boxWidth.toString() + 'px';
  document.getElementById('camera-view').setAttribute("style",st);
  document.getElementById('user-photo').setAttribute("style",st);
  document.getElementById('photo-adjust').setAttribute("style",st);
  canvasFab.setWidth(boxWidth);
  canvasFab.setHeight(boxWidth);
}
SetupSizes();

//Resizes the canvases
function ResizeCanvases() {
  var cvs = document.getElementsByTagName("canvas");
  for (var c = 0; c < cvs.length; c++) {
    cvs[c].height = canvasSquare;
    cvs[c].width = canvasSquare;
  }
  canvasFab.width = canvasSquare;
  canvasFab.height = canvasSquare;
}

function WebcamSetup() {
  navigator.mediaDevices
    .getUserMedia(constraints)
    .then(function(stream) {
      let track = stream.getTracks()[0];
      if (track.getSettings) {
        let {
          width,height
        } = track.getSettings();
        vidW = width;
        vidH = height;
        console.log(`${width}x${height}`);
        canvasSquare = (vidW > vidH) ? vidH : vidW;
        cameraView.width = (vidW > vidH) ? vidW : vidH;
        cameraView.height = (vidH > vidW) ? vidH : vidW;
        ResizeCanvases();
      }
      cameraView.srcObject = stream;
    })
    .catch(function(error) {
      console.error("Oops. Something is broken.",error);
    });

  cameraDiv.classList.remove("d-none");
  resultDiv.classList.add("d-none");
  fabricDiv.classList.add("d-none");
}
WebcamSetup();

function TakeSnapshot() {

  var landscape = vidW > vidH; //Is the video in landscape?
  var boxSize = canvasSquare;
  var ratio = landscape ? vidW / vidH : vidH / vidW;
  var offset = ((boxSize * ratio) - boxSize) / 2;
  userPhoto.getContext("2d").drawImage(cameraView,landscape ? offset : 0,landscape ? 0 : offset,canvasSquare,userPhoto.width,userPhoto.height);

  cameraDiv.classList.add("d-none");
  resultDiv.classList.remove("d-none");
  fabricDiv.classList.add("d-none");

  TurnOffWebcam();
}

//Removes the video and stops the stream
function TurnOffWebcam() {
  var videoEl = document.getElementById('camera-view');
  stream = videoEl.srcObject;
  if (stream != null) {
    stream.getTracks().forEach(track => track.stop());
    videoEl.srcObject = null;
  }
}

function UseImage() {

  const photo = document.getElementById('user-photo');

  fabric.Image.fromURL(photo.toDataURL(),function(img) {
    img.set({
      'flipX': true,});
    canvasFab.centerObject(img);
    canvasFab.setBackgroundImage(img,canvasFab.renderAll.bind(canvasFab));
  });

  cameraDiv.classList.add("d-none");
  resultDiv.classList.add("d-none");
  fabricDiv.classList.remove("d-none");
}