裁剪视频元素以适合织物 js 画布

Posted

技术标签:

【中文标题】裁剪视频元素以适合织物 js 画布【英文标题】:crop video element to fit a fabric js canvas 【发布时间】:2021-01-24 01:24:51 【问题描述】:

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

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

我在尝试将这个“用户照片”复制到织物 js 画布时出现问题。我希望它只复制它所做的画布的正方形副本。然而,它总是来自偏心。我不想硬编码任何值,因为画布和视频框的大小可能会改变,或者视频的分辨率可能不同。我很确定在绘制新画布时我刚刚进行了一些计算,或者“object-fit:cover”约束可能导致了这种行为。

过去两天我一直在寻找和尝试如何正确地实现这一目标并且已经接近但它仍然不能完全按照它应该的方式工作。

我使用了这里的部分: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, 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, 0);
cameraView.classList.add('d-none');

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

【问题讨论】:

【参考方案1】:

好的,所以我现在设法解决了。

我的问题的主要原因是不同的相机提供了不同的分辨率和宽高比,我在绘制画布时没有正确使用。我现在精通在 getContext("2d").drawImage() 中使用最大数量的参数。哈。

您可以在此处查看正常运行的 3 步版本: JS Fiddle

第一步将矩形网络摄像头 Feed 裁剪为一个方框。 第二步获取矩形视频源,从中创建图像,然后将其绘制到具有计算偏移量的新画布上,以获得正方形 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(`$widthx$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, canvasSquare, 0, 0, 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");

【讨论】:

以上是关于裁剪视频元素以适合织物 js 画布的主要内容,如果未能解决你的问题,请参考以下文章

居中、裁剪、适合视频背景(在 iframe 中)到父元素

Fabric JS clipPath:裁剪后如何使图像适合画布?

libVLC 视频裁剪

使用 FabricJS 裁剪图像

如何在织物画布中使用背景图像获取绘图路径中的图像?

如何在视频元素中播放画布?