获取包装盒的标准方法是?

时间:2017-03-15 02:36:24

标签: javascript fabricjs

好的,如果我有以下旋转然后选中的形状,你会看到它们的边界框: enter image description here

我正在尝试编写一些代码来相互对齐对象。所以我想让每个对象都包含#34;

我知道getBoundingRect但是,对于上述形状,这给了我以下内容: enter image description here

因此,这些盒子对我没用。是否有一种标准方法可以获得我称之为"包含框"各种形状?例如,我希望能够将以下方框返回给我: enter image description here

因此,对于任何给定的形状,我希望能够获得红色边界矩形(没有旋转)。

显然,我可以在fabricJS中为每个可能的形状编写一个例程,但我宁愿不重新发明轮子!有什么想法吗?

编辑这是一个显示当前边界框(红色)的交互式代码段:



$(function () 
{
    canvas = new fabric.Canvas('c');

    canvas.add(new fabric.Triangle({
      left: 50,
      top: 50,
      fill: '#FF0000',
      width: 50,
      height: 50,
      angle : 30
    }));

    canvas.add(new fabric.Circle({
      left: 250,
      top: 50,
      fill: '#00ff00',
      radius: 50,
      angle : 30
    }));

    canvas.add(new fabric.Polygon([
      {x: 185, y: 0},
      {x: 250, y: 100},
      {x: 385, y: 170},
      {x: 0, y: 245} ], {
        left: 450,
        top: 50,
        fill: '#0000ff',
        angle : 30
      }));

    canvas.on("after:render", function(opt) 
    { 
        canvas.contextContainer.strokeStyle = '#FF0000';
        canvas.forEachObject(function(obj) 
        {
            var bound = obj.getBoundingRect();

            canvas.contextContainer.strokeRect(
                bound.left + 0.5,
                bound.top + 0.5,
                bound.width,
                bound.height
            );
        });
    });

    canvas.renderAll();
});

<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.6/fabric.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="c" width="800" height="600"></canvas><br/>
&#13;
&#13;
&#13;

2 个答案:

答案 0 :(得分:2)

因此getBoundingBox是fabricjs的Object类的方法。 没有什么可以阻止你为你能想到的每种形状重写这种方法。

我将从圆形和三角形开始,我会让你想象多边形。当形状是路径或将圆形缩放为椭圆时,它变得越来越难。

圈子是最难的。 我为所有象限采样了30,60,90度的圆圈。仍然不完美。您可能需要增加采样或找到更好的公式(可能每15度采样一次)。

Triangle更容易,因为它有3个兴趣点。

多边形来自三角形,这里没什么难的。

fabric.Circle.prototype.getBoundingRect = function() {
  var matrix = this.calcTransformMatrix();
  var points = [{x:-this.width/2, y:0}, {x:this.width/2, y:0}, {x:0, y: -this.height/2}, {x: 0, y: this.height/2}, {x: 0, y: -this.height/2}, {x: 0.433 * this.width, y: this.height/4}, {x: -0.433 * this.width, y: this.height/4}, {y: 0.433 * this.height, x: this.width/4}, {y: -0.433 * this.height, x: this.width/4}, {y: -0.433 * this.height, x: -this.width/4}, {y: 0.433 * this.height, x: -this.width/4}, {x: 0.433 * this.width, y: -this.height/4}, {x: -0.433 * this.width, y: -this.height/4}];
  points = points.map(function(p) {
     return fabric.util.transformPoint(p, matrix);
  });
  return fabric.util.makeBoundingBoxFromPoints(points);
}

fabric.Triangle.prototype.getBoundingRect = function() {
  var matrix = this.calcTransformMatrix();
  var points = [{x:-this.width/2, y:this.height/2}, {x:this.width/2, y:this.height/2}, {x:0, y: -this.height/2}, {x: 0, y: 0}];
  points = points.map(function(p) {
     return fabric.util.transformPoint(p, matrix);
  });
  return fabric.util.makeBoundingBoxFromPoints(points);
}

fabric.Polygon.prototype.getBoundingRect = function() {
  var matrix = this.calcTransformMatrix();
  var points = this.points;
  var offsetX = this.pathOffset.x;
  var offsetY = this.pathOffset.y;
      points = points.map(function(p) {
         return fabric.util.transformPoint({x: p.x - offsetX , y: p.y -
 offsetY}, matrix);
      });
      return fabric.util.makeBoundingBoxFromPoints(points);
    }

$(function () 
{
    fabric.util.makeBoundingBoxFromPoints = function(points) {
      var minX = fabric.util.array.min(points, 'x'),
          maxX = fabric.util.array.max(points, 'x'),
          width = Math.abs(minX - maxX),
          minY = fabric.util.array.min(points, 'y'),
          maxY = fabric.util.array.max(points, 'y'),
          height = Math.abs(minY - maxY);

      return {
        left: minX,
        top: minY,
        width: width,
        height: height
      };
    };

    fabric.Circle.prototype.getBoundingRect = function() {
      var matrix = this.calcTransformMatrix();
      var points = [{x:-this.width/2, y:0}, {x:this.width/2, y:0}, {x:0, y: -this.height/2}, {x: 0, y: this.height/2}, {x: 0, y: -this.height/2}, {x: 0.433 * this.width, y: this.height/4}, {x: -0.433 * this.width, y: this.height/4}, {y: 0.433 * this.height, x: this.width/4}, {y: -0.433 * this.height, x: this.width/4}, {y: -0.433 * this.height, x: -this.width/4}, {y: 0.433 * this.height, x: -this.width/4}, {x: 0.433 * this.width, y: -this.height/4}, {x: -0.433 * this.width, y: -this.height/4}];
      points = points.map(function(p) {
         return fabric.util.transformPoint(p, matrix);
      });
      return fabric.util.makeBoundingBoxFromPoints(points);
    }
    
    fabric.Triangle.prototype.getBoundingRect = function() {
      var matrix = this.calcTransformMatrix();
      var points = [{x:-this.width/2, y:this.height/2}, {x:this.width/2, y:this.height/2}, {x:0, y: -this.height/2}, {x: 0, y: 0}];
      points = points.map(function(p) {
         return fabric.util.transformPoint(p, matrix);
      });
      return fabric.util.makeBoundingBoxFromPoints(points);
    }

    fabric.Polygon.prototype.getBoundingRect = function() {
      var matrix = this.calcTransformMatrix();
      var points = this.points;
      var offsetX = this.pathOffset.x;
      var offsetY = this.pathOffset.y;
      points = points.map(function(p) {
         return fabric.util.transformPoint({x: p.x - offsetX , y: p.y -
 offsetY}, matrix);
      });
      return fabric.util.makeBoundingBoxFromPoints(points);
    }

    canvas = new fabric.Canvas('c');

    canvas.add(new fabric.Triangle({
      left: 50,
      top: 50,
      fill: '#FF0000',
      width: 50,
      height: 50,
      angle : 30
    }));

    canvas.add(new fabric.Circle({
      left: 250,
      top: 50,
      fill: '#00ff00',
      radius: 50,
      angle : 30
    }));

    canvas.add(new fabric.Polygon([
      {x: 185, y: 0},
      {x: 250, y: 100},
      {x: 385, y: 170},
      {x: 0, y: 245} ], {
        left: 450,
        top: 50,
        fill: '#0000ff',
        angle : 30
      }));

    canvas.on("after:render", function(opt) 
    { 
        canvas.contextContainer.strokeStyle = '#FF0000';
        canvas.forEachObject(function(obj) 
        {
            var bound = obj.getBoundingRect();
            if(bound)
            {
                canvas.contextContainer.strokeRect(
                    bound.left + 0.5,
                    bound.top + 0.5,
                    bound.width,
                    bound.height
                );
            }                
        });
    });

    canvas.renderAll();
});
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.6/fabric.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="c" width="800" height="600"></canvas><br/>

答案 1 :(得分:1)

我遇到了同样的问题,并且找到了解决方法。我从活动对象创建了一个临时SVG,并将其放置在视口之外。然后,我使用本地getBBox()函数测量了实际的边界框。

更新

显然,以上解决方案仅在Firefox(76)中有效,因此我想出了另一种解决方案。由于找不到合适的本机函数来获取形状的真实边界框,因此我决定扫描像素并从中检索边界。

小提琴: https://jsfiddle.net/divpusher/2m7c61gw/118/

工作方式:

  • 导出选定的结构对象toDataURL()
  • 将其放在隐藏的画布中,并使用getImageData()
  • 获取像素
  • 扫描像素以检索形状的x1,x2,y1,y2边缘坐标

下面的演示

// ---------------------------
// the magic

var tempCanv, ctx, w, h;

function getImageData(dataUrl) { 
  // we need to use a temp canvas to get imagedata
  if (tempCanv == null) {
    tempCanv = document.createElement('canvas');
    tempCanv.style.border = '1px solid blue';
    tempCanv.style.visibility = 'hidden';
    ctx = tempCanv.getContext('2d');
  	document.body.appendChild(tempCanv);
	}
    
	return new Promise(function(resolve, reject) {
  	if (dataUrl == null) return reject();    
    
    var image = new Image();
    image.addEventListener('load', function() {
      w = image.width;
      h = image.height;
      tempCanv.width = w;
      tempCanv.height = h;
      ctx.drawImage(image, 0, 0, w, h);
      var imageData = ctx.getImageData(0, 0, w, h).data.buffer;         
			resolve(imageData, false);      
    });
    image.src = dataUrl;
	});
  
}

function scanPixels(imageData) {
	var data = new Uint32Array(imageData),
      len = data.length,
      x, y, y1, y2, x1 = w, x2 = 0;
  
  // y1
  for(y = 0; y < h; y++) {
    for(x = 0; x < w; x++) {
      if (data[y * w + x] & 0xff000000) {
        y1 = y;
        y = h;
        break;
      }
    }
  }
  
  // y2
  for(y = h - 1; y > y1; y--) {
    for(x = 0; x < w; x++) {
      if (data[y * w + x] & 0xff000000) {
        y2 = y;
        y = 0;
        break;
      }
    }
  }

  // x1
  for(y = y1; y < y2; y++) {
    for(x = 0; x < w; x++) {
      if (x < x1 && data[y * w + x] & 0xff000000) {
        x1 = x;
        break;
      }
    }
  }

  // x2
  for(y = y1; y < y2; y++) {
    for(x = w - 1; x > x1; x--) {
      if (x > x2 && data[y * w + x] & 0xff000000) {
        x2 = x;
        break;
      }
    }
  }
  
  return {
  	x1: x1,
    x2: x2,
    y1: y1,
    y2: y2
  }
}



// ---------------------------
// align buttons

function alignLeft(){
	var obj = canvas.getActiveObject();
  obj.set('left', 0);
  obj.setCoords();
  canvas.renderAll();
}


function alignLeftbyBoundRect(){
	var obj = canvas.getActiveObject();
  var bound = obj.getBoundingRect();
  obj.set('left', (obj.left - bound.left));
  obj.setCoords();
  canvas.renderAll();
}

function alignRealLeft(){
	var obj = canvas.getActiveObject();
  getImageData(obj.toDataURL())
  	.then(function(data) {    
    	var bound = obj.getBoundingRect();
    	var realBound = scanPixels(data);  
      obj.set('left', (obj.left - bound.left - realBound.x1));      
      obj.setCoords();
      canvas.renderAll(); 
    });
}


// ---------------------------
// set up canvas

var canvas = new fabric.Canvas('c');

var path = new fabric.Path('M 0 0 L 150 50 L 120 150 z');
path.set({
  left: 170,
  top: 30,
  fill: 'rgba(0, 128, 0, 0.5)',
  stroke: '#000',
  strokeWidth: 4,
  strokeLineCap: 'square',
  angle: 65
});
canvas.add(path);
canvas.setActiveObject(path);

var circle = new fabric.Circle({
  left: 370,
  top: 30,
  radius: 45,
  fill: 'blue',
  scaleX: 1.5,
  angle: 30
});
canvas.add(circle);


canvas.forEachObject(function(obj) {
  var setCoords = obj.setCoords.bind(obj);
  obj.on({
    moving: setCoords,
    scaling: setCoords,
    rotating: setCoords
  });
});


canvas.on('after:render', function() {
	canvas.contextContainer.strokeStyle = 'red';
  
	canvas.forEachObject(function(obj) {
      
		getImageData(obj.toDataURL())
  		.then(function(data) {    	
        var boundRect = obj.getBoundingRect();
        var realBound = scanPixels(data);                
        canvas.contextContainer.strokeRect(
					boundRect.left + realBound.x1, 
        	boundRect.top + realBound.y1, 
        	realBound.x2 - realBound.x1, 
        	realBound.y2 - realBound.y1
        );    
    });           

	});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.min.js"></script>

<p>&nbsp;</p>
<button onclick="alignLeft()">align left (default)</button>&nbsp;&nbsp;&nbsp;
<button onclick="alignLeftbyBoundRect()">align left (by bounding rect)</button>&nbsp;&nbsp;&nbsp;
<button onclick="alignRealLeft()">align REAL left (by pixel)</button>&nbsp;&nbsp;&nbsp;
<p></p>
<canvas id="c" width="600" height="250" style="border: 1px solid rgb(204, 204, 204); touch-action: none; user-select: none;" class="lower-canvas"></canvas>
<p></p>