Fidel90
Fidel90

Reputation: 1838

How does transforming points with a transformMatrix work in fabricJS?

I'm trying to place points (made via fabric.Circle) on the corners of a fabric.Polygon. The polygon may be moved, scaled or rotated by the user. However, after each modification I want to have the new coordinates of the polygon to place my circles there.

While digging deeper into this topic I found this great explanation of transformation matrices. I thought it's the perfect solution for what I want to achieve. But as you can see in the Fiddle, my points are always way off my polygon.

As I'm not firm with geometric transformation etc. I hope someone can find my error and tell me what I'm missing :) Thanks.

var canvas = new fabric.Canvas("c", {selection: false});

var polygon = new fabric.Polygon([
  new fabric.Point(200, 50),
  new fabric.Point(250, 150),
  new fabric.Point(150, 150)
]);

polygon.on("modified", function () {
  var matrix = this.calcTransformMatrix();
  var transformedPoints = this.get("points").map(function(p){
    return fabric.util.transformPoint(p, matrix);
  });
  var circles = transformedPoints.map(function(p){
    return new fabric.Circle({
      left: p.x,
      top: p.y,
      radius: 3,
      fill: "red",
      originX: "center",
      originY: "center",
      hasControls: false,
      hasBorders: false,
      selectable: false
    });
  });
  
  this.canvas.clear().add(this).add.apply(this.canvas, circles).setActiveObject(this).renderAll();
});

canvas.add(polygon).renderAll();
canvas {
  border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.js"></script>
<p>
Move, scale and rotate the polygon. The three red dots should match with the corners of the polygon after each modification.
</p>
<canvas id="c" width="600" height="400"></canvas>


Related Questions on SO:

Upvotes: 5

Views: 12789

Answers (4)

TheSinOfGreed
TheSinOfGreed

Reputation: 31

2024 version based on fabric 6.4.3 and @VitaliiBratok answer:

import { Point, util } from 'fabric-6.5.1';

polygon.on('modified', (o) => {
      const newCoords = polygon.get('points')
        .map(
          (p: Point) =>
            new Point(p.x - polygon.pathOffset.x, p.y - polygon.pathOffset.y),
        )
        .map((p: Point) => util.transformPoint(p, polygon.calcTransformMatrix()));

         polygon.set({'points': newCoords });
        // (...)
});

Upvotes: 1

Vishwaksena
Vishwaksena

Reputation: 11

The above code was working and saving to database properly, but still the offset persisted in the points while I enlarged and moved. So, I decided to set the width and height along with setting the points.

var width = self.polygon.width;
var height = polygon.height;
var scaleX = polygon.scaleX;
var scaleY = polygon.scaleY;

polygon.set({
    width: width * scaleX,
    height: height * scaleY,
    scaleX: 1,
    scaleY: 1
});

Upvotes: 1

Vitalii Bratok
Vitalii Bratok

Reputation: 771

It looks like the solution provided above doesn't work with the latest version of fabricjs. I'm not sure, but during the investigation, I've read that from version 2.0 they changed the coordinates of the polygon. Before 2.0 points relative to the center of the polygon; after 2.0 they are absolute to the canvas;

I made a few changes to the code snippet and it starts working with fabricjs v2.4.3

So, it works fine not only with moving but with transformations: such as resizing, rotation, flip

var canvas = new fabric.Canvas("c", {selection: false});

var polygon = new fabric.Polygon([
  new fabric.Point(200, 50),
  new fabric.Point(250, 150),
  new fabric.Point(150, 150)
]);

polygon.on("modified", function () {
  var matrix = this.calcTransformMatrix();
  var transformedPoints = this.get("points")
    .map(function(p){
      return new fabric.Point(
         p.x - polygon.pathOffset.x,
         p.y - polygon.pathOffset.y);
    })
  .map(function(p){
    return fabric.util.transformPoint(p, matrix);
  });
  var circles = transformedPoints.map(function(p){
    return new fabric.Circle({
      left: p.x,
      top: p.y,
      radius: 3,
      fill: "red",
      originX: "center",
      originY: "center",
      hasControls: false,
      hasBorders: false,
      selectable: false
    });
  });
  
  this.canvas.clear().add(this).add.apply(this.canvas, circles).setActiveObject(this).renderAll();
});

canvas.add(polygon).renderAll();
canvas {
  border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.3/fabric.js"></script>
<p>
Move, scale and rotate the polygon. The three red dots should match with the corners of the polygon after each modification.
</p>
<canvas id="c" width="600" height="400"></canvas>

Upvotes: 14

AndreaBogazzi
AndreaBogazzi

Reputation: 14731

Not really a geometric problem. The geometric part was solve by you. If you would look at internal polygon class from fabricjs you would notice that polygon as a calcDimension function where every point gets an offset: http://fabricjs.com/docs/fabric.Polygon.html

To calculate canvas position you have to add that offset back before transforming.

var canvas = new fabric.Canvas("c", {selection: false});

var polygon = new fabric.Polygon([
  new fabric.Point(200, 50),
  new fabric.Point(250, 150),
  new fabric.Point(150, 150)
]);

polygon.on("modified", function () {
  var matrix = this.calcTransformMatrix();
  var transformedPoints = this.get("points")
  .map(function(p){
    return new fabric.Point(p.x - polygon.minX -polygon.width/2, p.y - polygon.minY - polygon.height/2);
    })
  .map(function(p){
    return fabric.util.transformPoint(p, matrix);
  });
  var circles = transformedPoints.map(function(p){
    return new fabric.Circle({
      left: p.x,
      top: p.y,
      radius: 3,
      fill: "red",
      originX: "center",
      originY: "center",
      hasControls: false,
      hasBorders: false,
      selectable: false
    });
  });
  
  this.canvas.clear().add(this).add.apply(this.canvas, circles).setActiveObject(this).renderAll();
});

canvas.add(polygon).renderAll();
canvas {
  border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.js"></script>
<p>
Move, scale and rotate the polygon. The three red dots should match with the corners of the polygon after each modification.
</p>
<canvas id="c" width="600" height="400"></canvas>

Upvotes: 19

Related Questions