Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Maintain strokeWidth while scaling in fabric js

Note: I have refereed SO question, but it is not useful for my case, because

1) I am trying to maintain previous border but as of now its recalculate border while scaling.

I have added below code to stop increasing border automatically while scaling the object. Now the issue is I have added a 5px border to object but when scaling the object then it is not maintaining the border which I added earlier.

canvas.on('object:scaling', (e) => {
  var o = e.target;
  if (!o.strokeWidthUnscaled && o.strokeWidth) {
    o.strokeWidthUnscaled = o.strokeWidth;
  }

  if (o.strokeWidthUnscaled) {
    o.strokeWidth = o.strokeWidthUnscaled / o.scaleX;
  }
});

Now what I want is to prevent increasing of border while scaling the object. Border should remain as it was earlier.

Here is snippet / Codepen

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

$('.add_shape').click(function() {
  var cur_value = $(this).attr('data-rel');
  if (cur_value != '') {
    switch (cur_value) {
      case 'rectangle':
        var rect = new fabric.Rect({
          left: 50,
          top: 50,
          fill: '#aaa',
          width: 50,
          height: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: 1
        });
        canvas.add(rect);
        canvas.setActiveObject(rect);
        break;
      case 'circle':
        var circle = new fabric.Circle({
          left: 50,
          top: 50,
          fill: '#aaa',
          radius: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: 1
        });
        canvas.add(circle);
        canvas.setActiveObject(circle);
        break;
    }
  }
});

canvas.on('object:scaling', (e) => {
  var o = e.target;
  if (!o.strokeWidthUnscaled && o.strokeWidth) {
    o.strokeWidthUnscaled = o.strokeWidth;
  }
  if (o.strokeWidthUnscaled) {
    o.strokeWidth = o.strokeWidthUnscaled / o.scaleX;
  }
});

/* Control the border  */
$('#control_border').change(function() {
  var cur_value = parseInt($(this).val());
  var activeObj = canvas.getActiveObject();
  if (activeObj == undefined) {
    alert('Please select the Object');
    return false;
  }
  activeObj.set({
    strokeWidth: cur_value
  });
  canvas.renderAll();
});
button {
  max-resolution: 10px;
  height: 30px;
}

div {
  margin: 10px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-beta.7/fabric.js"></script>

<div>
  <button class="add_shape" data-rel="circle">Add Circle</button>

  <button class="add_shape" data-rel="rectangle">Add Rectangle</button>

  <label class="control-label">Border</label>
  <input id="control_border" type="range" min="0" max="10" step="1" value="0" />
</div>

<canvas id="canvas1" width="600" height="300" style="border:1px solid #000000;"></canvas>

Steps

1) Add Rectangle
2) Apply the border (lets say 5)
3) Scale that object

Now you can see the applied border is gone. So how to resolve that?

Update

I have tried below option but its not working, basically i am trying to maintain strokeWidth/Border for objects like rectangle, circle, triangle, line, polygon

What i have tried so far:

//1st try
canvas.on('object:scaling', (e) => {
    var o = e.target;
    o.strokeWidth = o.strokeWidth / ((o.scaleX + o.scaleY) / 2);
    var activeObject = canvas.getActiveObject();
    activeObject.set('strokeWidth',o.strokeWidth);
});

//2nd try
canvas.on('object:scaling', (e) => {
    if (!o.strokeWidthUnscaled && o.strokeWidth) {
        o.strokeWidthUnscaled = o.strokeWidth;
    }
    if (o.strokeWidthUnscaled) {
        o.strokeWidth = o.strokeWidthUnscaled / o.scaleX;
    }
});

//3rd try
fabric.Object.prototype._renderStroke = function(ctx) {
    if (!this.stroke || this.strokeWidth === 0) {
        return;
    }
    if (this.shadow && !this.shadow.affectStroke) {
        this._removeShadow(ctx);
    }
    ctx.save();
    ctx.scale(1 / this.scaleX, 1 / this.scaleY);
    this._setLineDash(ctx, this.strokeDashArray, this._renderDashedStroke);
    this._applyPatternGradientTransform(ctx, this.stroke);
    ctx.stroke();
    ctx.restore();
};

Questions i have refereed:

https://github.com/kangax/fabric.js/issues/66

Unable to maintain thickness of strokeWidth while resizing in case of Groups in Fabricjs

Fabricjs How to scale object but keep the border (stroke) width fixed

Resize a fabricjs rect to maintain border size

https://github.com/kangax/fabric.js/issues/2012

But not able to found solution.

like image 536
DS9 Avatar asked Feb 27 '18 09:02

DS9


2 Answers

This has become much easier as of Fabric.js version 2.7.0. There is now a strokeUniform property that when enabled, prevents the stroke width from being affected by the object's scale values.

obj.set('strokeUniform', true);

https://github.com/fabricjs/fabric.js/pull/5546

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

$('.add_shape').click(function() {
  var cur_value = $(this).attr('data-rel');
  if (cur_value != '') {
    switch (cur_value) {
      case 'rectangle':
        var rect = new fabric.Rect({
          left: 50,
          top: 50,
          fill: '#aaa',
          width: 50,
          height: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: 1,
          noScaleCache: false,
          strokeUniform: true,
        });
        canvas.add(rect);
        canvas.setActiveObject(rect);
        break;
      case 'circle':
        var circle = new fabric.Circle({
          left: 50,
          top: 50,
          fill: '#aaa',
          radius: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: 1,
          noScaleCache: false,
          strokeUniform: true
        });
        canvas.add(circle);
        canvas.setActiveObject(circle);
        break;
    }
  }
});

/* Control the border  */
$('#control_border').change(function() {
  var cur_value = parseInt($(this).val());
  var activeObj = canvas.getActiveObject();
  if (activeObj == undefined) {
    alert('Please select the Object');
    return false;
  }
  activeObj.set({
    strokeWidth: cur_value
  });
  canvas.renderAll();
});
button {
  max-resolution: 10px;
  height: 30px;
}

div {
  margin: 10px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.7.0/fabric.min.js"></script>

<div>
  <button class="add_shape" data-rel="circle">Add Circle</button>
  <button class="add_shape" data-rel="rectangle">Add Rectangle</button>
  <label class="control-label">Border</label>
  <input id="control_border" type="range" min="0" max="10" step="1" value="0" />
</div>

<canvas id="canvas1" width="600" height="300" style="border:1px solid #000000;"></canvas>
like image 115
melchiar Avatar answered Oct 08 '22 15:10

melchiar


Set strokeWidth to strokeWidthUnscaled for first time then set @ at scalling, for caching make it false only when scalling after modified change it.

object.strokeWidth = object.strokeWidthUnscaled/((object.scaleX+object.scaleY)/2);

And for selecting new stroke width, do this

var newStrokeWidth = cur_value / ((activeObj.scaleX + activeObj.scaleY) / 2)
activeObj.set({
  strokeWidth: newStrokeWidth,
  strokeWidthUnscaled : cur_value
});

Example

var canvas = new fabric.Canvas('canvas1');
var globalStrokeWidth = 1;

$('.add_shape').click(function() {
  var cur_value = $(this).attr('data-rel');
  if (cur_value != '') {
    switch (cur_value) {
      case 'rectangle':
        var rect = new fabric.Rect({
          left: 50,
          top: 50,
          fill: '#aaa',
          width: 50,
          height: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: globalStrokeWidth
        });
        canvas.add(rect);
        canvas.setActiveObject(rect);
        break;
      case 'circle':
        var circle = new fabric.Circle({
          left: 50,
          top: 50,
          fill: '#aaa',
          radius: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: globalStrokeWidth
        });
        canvas.add(circle);
        canvas.setActiveObject(circle);
        break;
    }
  }
});

canvas.on('object:scaling', (e) => {
  var o = e.target;
  if (!o.strokeWidthUnscaled && o.strokeWidth) {
    o.strokeWidthUnscaled = o.strokeWidth;
  }
  if (o.strokeWidthUnscaled) {
    if(o.objectCaching) o.objectCaching = false;
    o.strokeWidth = o.strokeWidthUnscaled / ((o.scaleX + o.scaleY) / 2);
  }
});

canvas.on('object:modified', (e) => {
  var o = e.target;
  if(!o.objectCaching) o.objectCaching = true;
  canvas.renderAll();
});

/* Control the border  */
$('#control_border').change(function() {
  var cur_value = parseInt($(this).val());
  globalStrokeWidth = cur_value;
  var activeObj = canvas.getActiveObject();
  if (activeObj == undefined) {
    alert('Please select the Object');
    return false;
  }
  var newStrokeWidth = cur_value / ((activeObj.scaleX + activeObj.scaleY) / 2)
  activeObj.set({
    strokeWidth: newStrokeWidth,
    strokeWidthUnscaled : cur_value
  });
  activeObj.setCoords();
  canvas.renderAll();
});
button {
  max-resolution: 10px;
  height: 30px;
}

div {
  margin: 10px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-beta.7/fabric.js"></script>

<div>
  <button class="add_shape" data-rel="circle">Add Circle</button>

  <button class="add_shape" data-rel="rectangle">Add Rectangle</button>

  <label class="control-label">Border</label>
  <input id="control_border" type="range" min="0" max="10" step="1" value="0" />
</div>

<canvas id="canvas1" width="600" height="300" style="border:1px solid #000000;"></canvas>
like image 6
Durga Avatar answered Oct 08 '22 16:10

Durga