enxaneta
enxaneta

Reputation: 33072

Get the bounding box of the intersection of 2 or more paths

In the following example I have in gold the intersection of 3 shapes (in this case I'm using circles but those 3 shapes can be anything) The golden intersection is the result of clipping with clip-path.

I would like to use the intersection as a symbol and for this I would need to know the bounding box of the intersection, i.e the red stroked rectangle.

If I'm using intersection.getBBox() I'm getting the bounding box before clipping.

How can I get the bounding box of the intersection?

console.log(intersection.getBBox())
svg{border:solid}

.circles{fill:none;stroke:black}
<svg id="svg" viewBox="-150 -150 300 300" width="300">
  <defs>
  <circle id="c1" cx="0" cy="-50" r="80"></circle>
  <circle id="c2" cx="43.3" cy="25" r="80"></circle>
  <circle id="c3" cx="-43.3" cy="25" r="80"></circle>
  
  <clipPath id="clipC2"><use xlink:href="#c2"/></clipPath>
  <clipPath id="clipC3"><use xlink:href="#c3"/></clipPath>
  </defs>
  
  <g class="circles">
  <use xlink:href="#c1"/>
  <use xlink:href="#c2"/>
  <use xlink:href="#c3"/>
  </g>
  
  
<g id="intersection">
  <g clip-path="url(#clipC3)">
  <use fill="gold" xlink:href="#c1" clip-path="url(#clipC2)"/>
  </g>
</g>
  
  <rect x="-38" y="-42" width="75" height="74" stroke="red" fill="none"/>
</svg>

Upvotes: 2

Views: 409

Answers (2)

enxaneta
enxaneta

Reputation: 33072

The main idea is this:

  1. I'm taking the svg element, make it base64 and use it as the src attribute of an image.
  2. I'm painting the svg element on a canvas with the same size as the svg element.
  3. I get the image data from the canvas
  4. loop through the image data and get:
  • the smallest x value of a black pixel
  • the smallest y value of a black pixel
  • the biggest x value of a black pixel
  • the biggest y value of a black pixel
  1. I'm using using those values to build the new viewBox value for the intersection.

//the svg's viewBox
let vB = { x: -100, y: -100, w: 200, h: 200 };

//canvas
let ctx = c.getContext("2d");
//set the size of the canvas equal to the size of the svg element
c.width = vB.w;
c.height = vB.h;

// draw the svg element on the canvas
let xml = new XMLSerializer().serializeToString(svg);
// make it base64 and use it as the src attribute of the image
let img=new Image()
img.src = "data:image/svg+xml;base64," + btoa(xml);
img.onload = function() {
  //paint the image on the canvas
    ctx.drawImage(this, 0, 0);
  
//get the image data from the canvas
let imgData = ctx.getImageData(0, 0, vB.w, vB.h).data;

// x the smallest x value of a black pixel
// y the smallest y value of a black pixel
// X the biggest x value of a black pixel
// Y the biggest y value of a black pixel
let x = vB.w,
  y = vB.h,
  X = 0,
  Y = 0;
let n = 0;

for (let i = 0; i < imgData.length; i += 4) {
  n++
  if (imgData[i + 3] != 0) {
    //if the alpha (i+3) value of the pixel is not 0
    let _y = Math.ceil(i / (4 * vB.w));
    let _x = (i / 4) % vB.w;
    if (_x < x) { x = _x; }
    if (_y < y) { y = _y; }
    if (_x > X) { X = _x; }
    if (_y > Y) { Y = _y; }
  } 
  
  if(n==imgData.length/4){
    let newViewBox = `${x + vB.x} ${y + vB.y} ${X - x + 1} ${Y - y}`;
    reuleaux.setAttribute("viewBox", newViewBox);
    console.log(`viewBox="${newViewBox}"`);
  }
}
}
svg,
canvas {
  outline: 1px solid;
}
<svg id="svg" viewBox="-100 -100 200 200" width="200">
  <defs>
  <circle id="c1" cx="0" cy="-50" r="80"></circle>
  <circle id="c2" cx="43.3" cy="25" r="80"></circle>
  <circle id="c3" cx="-43.3" cy="25" r="80"></circle>  
  <clipPath id="clipC2"><use xlink:href="#c2"/></clipPath>
  <clipPath id="clipC3"><use xlink:href="#c3"/></clipPath>
  </defs>    
<g id="intersection">
  <g clip-path="url(#clipC3)">
  <use xlink:href="#c1" clip-path="url(#clipC2)"/>
  </g>
</g>
</svg>
<!--<img id="img" width="200" height="200"/>-->

<canvas id="c"></canvas>

<svg id="reuleaux" viewBox="-100 -100 200 200" width="200" style="background:#dfdfdf">
  <use xlink:href="#intersection"/>
</svg>

Upvotes: 1

Owain Evans
Owain Evans

Reputation: 384

Unsure if this was the type of thing you were after.

let myBB = {
    x: c2.getBBox().x,
    get y() {
        return c1.getBBox().y + this.width
    },
    get width() {
        // return (posNum(c3.getBBox().x))  - (posNum(myBB.x));
        let leftPointOfWidth = c2.getBBox().x;
        let rightPointofWidth = c3.getBBox().x + c3.getBBox().width;
        // 10000 to guarantee both positive numbers. very hacky
        let mywidth = (rightPointofWidth + 10000) - (leftPointOfWidth + 10000);
        return mywidth;
    },
    get height() {
        return this.width;
    }
}

I'm quite sure there's a better way to put it. And need to call the getters; They won't appear in the console.log(myBB)

Inputting the obtained coordinates and width gives the rect in yellow. (Pink rects are shoing centres of circles) enter image description here

Upvotes: 0

Related Questions