在使用 fabric.js 缩放 canvas 时对图像进行像素完美缩放

Pixel perfect scaling of images while zooming the canvas with fabric.js

我的软件是一种带画笔的像素艺术绘画程序,fabric.js 进行了大量修改,以便对 fabric.js 对象进行像素圆角平移(位置始终为整数),然而,调整图像大小并使它们的像素以 1:1 比例适合 canvas 仍然是一个问题。

现在,fabric.js zoomToPoint 功能用于可以用鼠标滚轮缩放 canvas ,当图像未缩放时,它们的像素适合canvas 但是当我缩小(或放大)任何对象时,像素不再适合 canvas 因此对象的像素实际上小于或大于 canvas 像素。

缩放级别为8的问题截图:

原始图像尺寸(没问题,每个像素适合 canvas 像素)

已调整图像大小,图像像素不再适合 canvas 像素

图像的像素如何在缩放时以 1:1 的比例适合 canvas 像素?

从本质上讲,调整大小涉及对原始图像和 interpolating/convoluting 现有像素重新采样到调整大小后的 space。生成的图像在其新尺寸下具有视觉吸引力。但是对于调整大小为 X 的图像,结果像素肯定不再具有 X:1 关系。

要放大,您需要 "projection" 将每个 1x1 像素扩展为 2x2 像素组(或 3x3 像素组等)。

要缩小,您需要一个 "projection",其中每个 2x2 像素组被压缩成一个 1x1 像素集。

要缩小,源(缩放前)必须由像素组组成。这意味着您不能在不使用重新采样的情况下将图像缩小到其原始大小以下。没有可用于缩小到 1 倍以下的投影。解决方法是让用户以 2X(或 3X 或 4X)投影绘制,以便您可以缩小。

FabricJS 本身不进行投影...您必须使用临时 canvas 元素进行投影。

下面是使用投影进行缩放的示例代码:

var zoom=2;
var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
//img.src="sun.png";
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/sun.png";
function start(){

  var iw=img.width;
  var ih=img.height;   

  img.width=iw;
  img.height=ih;
  // set the "current zoom factor" on the original image to 1x
  img.zoom=1;

  // test: resize by 4X
  var zoom4x=resize(img,4,'canvas4x');
  document.body.appendChild(zoom4x);

  // test: resize the 4X back down to 2X
  var zoom2x=resize(zoom4x,0.50,'canvas2x');
  document.body.appendChild(zoom2x);

  // test: resize the 2X back down to 1X
  var zoom1x=resize(zoom2x,0.50,'canvas1x');
  document.body.appendChild(zoom1x);

  // test: resize the 1X down to half-size
  var zoomHx=resize(zoom1x,0.50,'canvas1x');
  if(zoomHx){document.body.appendChild(zoomHx)};

  // display the original image
  document.body.appendChild(img);


}



var resize = function(img,scale,id){

  var zoom=parseInt(img.zoom*scale);
  if(zoom<1){
    console.log('Cannot recale image to less than original size');
    return;
  }

  // get the pixels from the original img using a canvas
  var c1=document.createElement('canvas');
  var cw1=c1.width=img.width;
  var ch1=c1.height=img.height;
  var ctx1=c1.getContext('2d');
  ctx1.drawImage(img,0,0);
  var imgData1=ctx1.getImageData(0,0,cw1,ch1);
  var data1=imgData1.data;

  // create a canvas to hold the resized pixels
  var c2=document.createElement('canvas');
  c2.id=id;
  c2.zoom=zoom;
  var cw2=c2.width=cw1*scale;
  var ch2=c2.height=ch1*scale;
  var ctx2=c2.getContext('2d');
  var imgData2=ctx2.getImageData(0,0,cw2,ch2);
  var data2=imgData2.data;

  // copy each source pixel from c1's data1 into the c2's data2
  for(var y=0; y<ch2; y++) {
    for(var x=0; x<cw2; x++) {
      var i1=(Math.floor(y/scale)*cw1+Math.floor(x/scale))*4;
      var i2 =(y*cw2+x)*4;            
      data2[i2]   = data1[i1];
      data2[i2+1] = data1[i1+1];
      data2[i2+2] = data1[i1+2];
      data2[i2+3] = data1[i1+3];
    }
  }

  // put the modified pixels back onto c2
  ctx2.putImageData(imgData2,0,0);

  // return the canvas with the zoomed pixels
  return(c2);
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<h4>Left: 4X, 2X, 1X projections, Right:Original Image</h4>