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

Javascript 裁剪视频元素以适合织物js画布,javascript,canvas,fabricjs,crop,webcam,Javascript,Canvas,Fabricjs,Crop,Webcam,我试图让用户的相机拍摄快照,然后将最终结果渲染到方形的fabricjs画布上 我使用浏览器自带的网络摄像头功能,因此它在我的笔记本电脑上以640x480的速度运行。我向用户展示这一点的方式是在一个方形视图元素“camera view”中显示视频提要,该元素设置为“object fit:cover”,以移除字母框(然后它会放大以匹配高度)。然后将图片放置在方形画布“用户照片”上,该画布也设置为“对象适合:封面”,以包含矩形图像(隐藏侧面并匹配视频)。该图像还需要在手机摄像头上以纵向方式正确显示,这

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

我使用浏览器自带的网络摄像头功能,因此它在我的笔记本电脑上以640x480的速度运行。我向用户展示这一点的方式是在一个方形视图元素“camera view”中显示视频提要,该元素设置为“object fit:cover”,以移除字母框(然后它会放大以匹配高度)。然后将图片放置在方形画布“用户照片”上,该画布也设置为“对象适合:封面”,以包含矩形图像(隐藏侧面并匹配视频)。该图像还需要在手机摄像头上以纵向方式正确显示,这似乎是可行的

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

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

我使用了来自这里的部分:,这里和其他来自堆栈溢出的部分

以下是代码的主要部分,正在进行的全部工作如下:


好的,我现在设法解决了

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

您可以在此处看到一个运行的3步版本:

第一步显示裁剪为方形框的矩形网络摄像头提要。 第二步采用矩形视频馈送,从中包装一个图像,然后用计算出的偏移量将其绘制到新画布上,以获得一个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, 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");
}
var canvasSquare//用于我们的图像大小调整
宽度//用于我们的Resensive div上浆
var vidW,vidH//计算我们的网络视频源的宽度和高度
//拉票
const cameraView=document.querySelector(“摄像头视图”);
const userPhoto=document.querySelector(“用户照片”);
var canvasFab=newfabric.Canvas('photo-adjust',{});
//按钮的Div设置
const cameraDiv=document.querySelector(“摄像头”);
const resultDiv=document.querySelector(“result”);
const fabricDiv=document.querySelector(“结构”);
//网络摄像头的设置和使用
变量约束={
视频:{
宽度:{
理想人数:4096
},
高度:{
理想人数:4096
},
面对模式:“用户”
},
音频:错误
};
//将所有div设置为相同大小
函数设置大小(){
boxWidth=document.getElementById(“框宽”).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);
帆布布景宽度(箱宽);
帆布装配高度(箱宽);
}
设置大小();
//调整画布的大小
函数大小调整画布(){
var cvs=document.getElementsByTagName(“画布”);
对于(var c=0;cvidH)?vidH:vidW;
cameraView.width=(vidW>vidH)?vidW:vidH;
cameraView.height=(vidH>vidW)?vidH:vidW;
调整画布的大小();
}
cameraView.srcObject=流;
})
.catch(函数(错误){
console.error(“哎呀,有东西坏了。”,error);
});
cameraDiv.classList.remove(“d-none”);
结果iv.classList.add(“d-none”);
fabricDiv.classList.add(“d-none”);
}
网络摄像机设置();
函数TakeSnapshot(){
var scape=vidW>vidH;//视频是否处于横向?
var boxSize=canvasSquare;
var比率=景观?vidW/vidH:vidH/vidW;
var偏移=((boxSize*比率)-boxSize)/2;
userPhoto.getContext(“2d”).drawImage(cameraView,横向?偏移:0,横向?0:偏移,canvasSquare,canvasSquare,0,0,userPhoto.width,userPhoto.height);
cameraDiv.classList.add(“d-none”);
resultDiv.classList.remove(“d-none”);
fabricDiv.classList.add(“d-none”);
关闭网络摄像头();
}
//删除视频并停止流
功能关闭网络摄像头(){
var videoEl=document.getElementById('camera-view');
流=videoEl.src对象;
if(流!=null){
stream.getTracks().forEach(track=>track.stop());
videoEl.srcObject=null;
}
}
函数UseImage(){
const photo=document.getElementById('user-photo');
fabric.Image.fromURL(photo.toDataURL(),函数(img){
img.set({
“flipX”:对,
});
canvasFab.centerObject(img);
canvasFab.setBackgroundImage(img,canvasFab.renderAll.bind(canvasFab));
});
cameraDiv.classList.add(“d-none”);
结果iv.classList.add(“d-none”);
fabricDiv.classList.remove(“d-none”);
}
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, 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");
}