Determine bounds of shape / graphics drawn into a Canvas
Asked Answered
H

4

7

I have a simple HTML5 Canvas example that lets the user draw paths onto a canvas. Is there any way to determine the rectangular bounds of the path / shape that was drawn? (i.e., what is the width, height of the rectangular region surrounding the path).

I realize I could do the math while the shape is being drawn to figure out the bounds, but I wanted to see if there was an easier / built in way.

Hartebeest answered 7/1, 2011 at 19:19 Comment(2)
Using fabric.js would allow you to access drawn shape as a single object, including coordinates of its boundaries.Annabelle
If you are using quadratic or cubic Bézier curves, you can split them into lines using De Casteljau algorithm, then calculate bounds of those lines. You can approximate bounds by bounds of control points, since whole bezier curve lies in convex hull of it's control points.Humidifier
S
7

I assume you are using lineTos the only way I could think of would be to keep a min/max stored for the height and width as the user is drawing paths. Other than that the only way to pull back info from the canvas would be to use getImageData which will only give you raw pixel information.

Quick example showing this

var ctx = document.getElementById("canvas").getContext("2d");
var xMin, xMax, yMin, yMax;

// These are set to where the path starts, i start them at 10,10
xMin = xMax = 10;
yMin = yMax = 10;

ctx.beginPath();
ctx.moveTo(10,10);

for(var i = 0; i <10; i++){
    var x = Math.floor(Math.random()*150),
        y = Math.floor(Math.random()*150);
        
    ctx.lineTo(x,y);
    if(x < xMin){
     xMin = x;   
    }
    if(x > xMax){
     xMax = x;   
    }
    
    if(y < yMin){
     yMin = y;   
    }
    if(y > yMax){
     yMax = y;   
    }
}
ctx.strokeStyle = "rgb(0,0,0)";
ctx.stroke();
ctx.closePath();      

ctx.strokeStyle = "rgb(255,0,0)";
ctx.strokeRect(xMin,yMin,xMax - xMin,yMax - yMin);  
#canvas{
    width: 300px;
    height: 300px;
}
<canvas id="canvas"></canvas>

note I just create a bunch of random points. The main thing to remember is set the min/max vals to the coords of the first path a user creates.

I guess you knew that though, so the real answer is no there is unfortunately no built in way currently to do it..

Sachsse answered 7/1, 2011 at 19:31 Comment(0)
I
5

Although you have to track it yourself, I would suggest wrapping it up in reusable functionality. Here's a minimal example, tracking it only for moveTo and lineTo. See the live example here: http://phrogz.net/tmp/canvas_bounding_box.html

function trackBBox( ctx ){
  var begin = ctx.beginPath;
  ctx.beginPath = function(){
    this.minX = this.minY = 99999999999;
    this.maxX = this.maxY = -99999999999;
    return begin.call(this);
  };
  ctx.updateMinMax = function(x,y){
    if (x<this.minX) this.minX = x;
    if (x>this.maxX) this.maxX = x;
    if (y<this.minY) this.minY = y;
    if (y>this.maxY) this.maxY = y;
  };
  var m2 = ctx.moveTo;
  ctx.moveTo = function(x,y){
    this.updateMinMax(x,y);
    return m2.call(this,x,y);
  };
  var l2 = ctx.lineTo
  ctx.lineTo = function(x,y){
    this.updateMinMax(x,y);
    return l2.call(this,x,y);
  };
  ctx.getBBox = function(){
    return {
      minX:this.minX,
      maxX:this.maxX,
      minY:this.minY,
      maxY:this.maxY,
      width:this.maxX-this.minX,
      height:this.maxY-this.minY
    };
  };
}

...

var ctx = myCanvas.getContext("2d");

// Cause the canvas to track its own bounding box for each path
trackBBox(ctx);
ctx.beginPath();
ctx.moveTo(40,40);
for(var i=0; i<10; i++) ctx.lineTo(Math.random()*600,Math.random()*400);

// Find the bounding box of the current path
var bbox = ctx.getBBox();
ctx.strokeRect(bbox.minX,bbox.minY,bbox.width,bbox.height);  
Ingunna answered 8/1, 2011 at 16:17 Comment(0)
F
0

Inspired by @Phrogz's answer, the answer from Calculate bounding box of arbitrary pixel-based drawing, and his two slightly different demos http://phrogz.net/tmp/canvas_bounding_box.html and http://phrogz.net/tmp/canvas_bounding_box2.html, here is a version not using the alpha channel (it did not work in my case) but just using a comparison to white color.

function contextBoundingBox(ctx){
    var w=ctx.canvas.width,h=ctx.canvas.height;
    var data = ctx.getImageData(0,0,w,h).data;
    var x,y,minX,minY,maxY,maxY;
    o1: for (y=h;y--;)        for (x=w;x--;)           if ((data[(w*y+x)*4] != 255) && (data[(w*y+x)*4+1] != 255) && (data[(w*y+x)*4+2] != 255)) { maxY=y; break o1 }
    o2: for (x=w;x--;)        for (y=maxY+1;y--;)      if ((data[(w*y+x)*4] != 255) && (data[(w*y+x)*4+1] != 255) && (data[(w*y+x)*4+2] != 255)) { maxX=x; break o2 }
    o3: for (x=0;x<=maxX;++x) for (y=maxY+1;y--;)      if ((data[(w*y+x)*4] != 255) && (data[(w*y+x)*4+1] != 255) && (data[(w*y+x)*4+2] != 255)) { minX=x; break o3 }
    o4: for (y=0;y<=maxY;++y) for (x=minX;x<=maxX;++x) if ((data[(w*y+x)*4] != 255) && (data[(w*y+x)*4+1] != 255) && (data[(w*y+x)*4+2] != 255)) { minY=y; break o4 }
    return {x:minX,y:minY,maxX:maxX,maxY:maxY,w:maxX-minX,h:maxY-minY};
}
Foghorn answered 2/6, 2020 at 16:41 Comment(0)
A
0

We can learn the _calcBoundsFromPath method from the fabric.js library.This is done based on mathematical calculations.

https://github.com/fabricjs/fabric.js/blob/master/src/shapes/Path.ts

  _calcBoundsFromPath(): TBBox {}
Agostino answered 2/8, 2023 at 4:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.