Multiple clipping areas on Fabric.js canvas

2019-09-02 05:51发布

For making Photo Collage Maker, I use fabric js which has object based clipping feature. This feature is great but image inside that clipping region cannot be scaled, moved or rotated. I want fixed position clipping region and image can be positioned inside the fixed clipping area as user want.

I googled and find very near solution

var canvas = new fabric.Canvas('c');
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.rect(10,10,150,150);
ctx.rect(180,10,200,200);
ctx.closePath();
ctx.stroke();
ctx.clip();

Multiple Clipping Areas on fabric js canvas

where image of one clipping region is appeared in another clipping region. How can I avoid this or is there another way of accomplishing this using fabric js.

Answer 1:

这可以通过使用光纤来实现clipTo财产,但你必须“反向”的变换(缩放和旋转),在clipTo功能。

当您使用clipTo在织物性能,缩放和旋转限幅之后应用,这意味着剪辑被缩放和与图像旋转。 你必须通过应用的变换的完全相反对付这个clipTo属性功能。

我的解决方案涉及到一Fabric.Rect作为剪辑区域的“占位符”(这方面具有优势,因为你可以用布周围,因此移动物体剪辑区域。

请注意,我的解决方案使用罗短跑工具库,特别是_.bind()见上下文代码)。

例如小提琴


分解

1.初始化面料

首先,我们希望我们的画布,当然:

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

2.剪辑区域

var clipRect1 = new fabric.Rect({
    originX: 'left',
    originY: 'top',
    left: 180,
    top: 10,
    width: 200,
    height: 200,
    fill: 'none',
    stroke: 'black',
    strokeWidth: 2,
    selectable: false
});

我们给这些Rect对象的名称属性, clipFor ,所以clipTo功能可以找到他们想要修剪的一个:

clipRect1.set({
    clipFor: 'pug'
});
canvas.add(clipRect1);

没有成为该截取区域内的实际的对象,但它可以更容易管理,因为你可以把它走动用面料。

3.裁剪功能

我们定义这将通过将图像中使用的功能clipTo属性分开,以避免重复代码:

由于angle的图像对象的属性存储在度,我们将使用此将其转换为弧度。

function degToRad(degrees) {
    return degrees * (Math.PI / 180);
}

findByClipName()是一个方便的功能,这是使用螺短跑 ,以找到与clipFor属性为图像物体被裁剪(例如,下面的图像中, name将是'pug' ):

function findByClipName(name) {
    return _(canvas.getObjects()).where({
            clipFor: name
        }).first()
}

而这是做工作的一部分:

var clipByName = function (ctx) {
    var clipRect = findByClipName(this.clipName);
    var scaleXTo1 = (1 / this.scaleX);
    var scaleYTo1 = (1 / this.scaleY);
    ctx.save();
    ctx.translate(0,0);
    ctx.rotate(degToRad(this.angle * -1));
    ctx.scale(scaleXTo1, scaleYTo1);
    ctx.beginPath();
    ctx.rect(
        clipRect.left - this.left,
        clipRect.top - this.top,
        clipRect.width,
        clipRect.height
    );
    ctx.closePath();
    ctx.restore();
}

请参阅下面和使用说明this在上面的功能。

4. fabric.Image使用对象clipByName()

最后,图像可以被实例化并取得使用clipByName功能如下:

var pugImg = new Image();
pugImg.onload = function (img) {    
    var pug = new fabric.Image(pugImg, {
        angle: 45,
        width: 500,
        height: 500,
        left: 230,
        top: 170,
        scaleX: 0.3,
        scaleY: 0.3,
        clipName: 'pug',
        clipTo: function(ctx) { 
            return _.bind(clipByName, pug)(ctx) 
        }
    });
    canvas.add(pug);
};
pugImg.src = 'http://fabricjs.com/lib/pug.jpg';

是什么_.bind()吗?

注意,参考被包裹在所述_.bind()函数。

我使用_.bind()用于以下两个原因:

  1. 我们需要一个引用传递Image对象clipByName()
  2. clipTo属性传递画布背景下,而不是对象。

基本上, _.bind()您可以创建一个版本,使用您指定为对象的函数的this方面。

Sources
  1. http://lodash.com/docs#bind
  2. http://fabricjs.com/docs/fabric.Object.html#clipTo
  3. http://html5.litten.com/understanding-save-and-restore-for-the-canvas-context/


Answer 2:

我已经调整了由@natchiketa的溶液,作为剪辑区域的定位不正确地定位并且是所有在旋转时靠不住。 但一切似乎现在好。 看看这个修改小提琴: http://jsfiddle.net/PromInc/ZxYCP/

唯一真正的变化在由@natchiketa提供的代码的步骤3的clibByName功能制成。 这是更新的功能:

var clipByName = function (ctx) {
    this.setCoords();

    var clipRect = findByClipName(this.clipName);

    var scaleXTo1 = (1 / this.scaleX);
    var scaleYTo1 = (1 / this.scaleY);
    ctx.save();

    var ctxLeft = -( this.width / 2 ) + clipRect.strokeWidth;
    var ctxTop = -( this.height / 2 ) + clipRect.strokeWidth;
    var ctxWidth = clipRect.width - clipRect.strokeWidth + 1;
    var ctxHeight = clipRect.height - clipRect.strokeWidth + 1;

    ctx.translate( ctxLeft, ctxTop );

    ctx.rotate(degToRad(this.angle * -1));
    ctx.scale(scaleXTo1, scaleYTo1);
    ctx.beginPath();

    ctx.rect(
        clipRect.left - this.oCoords.tl.x,
        clipRect.top - this.oCoords.tl.y,
        ctxWidth,
        ctxHeight
    );
    ctx.closePath();
    ctx.restore();
}

两个小卡子,我发现:

  1. 添加行程至剪贴对象似乎是几个像素扔东西了。 我试图弥补的定位,但随后在旋转时它会增加2个像素的底部和右侧。 所以,我选择了刚刚完全删除。
  2. 在一段时间后,当你旋转图像,它最终会在裁剪上随机双方1px的间距。


Answer 3:

更新到Promlnc答案。

你需要为了进行适当的裁剪,以取代语境转换的顺序。

  1. 翻译
  2. 缩放
  3. 回转

否则,你会得到错误剪取的物体 - 当你扩展,不必保持纵横比(只改变一维)。

码(69-72):

ctx.translate( ctxLeft, ctxTop );

ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);

替换为:

ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.rotate(degToRad(this.angle * -1));

看到这一点: http://jsfiddle.net/ZxYCP/185/

正确的结果:

更新1:

我已经开发功能,通过多边形剪辑。 http://jsfiddle.net/ZxYCP/198/



Answer 4:

这可以更容易地完成。 织物提供了渲染方法被另一个对象上下文剪辑。

结帐这个小提琴 。 我看到这个的注释在这里 。

        obj.clipTo = function(ctx) {
            ctx.save();
            ctx.setTransform(1, 0, 0, 1, 0, 0);

            clippingRect.render(ctx);

            ctx.restore();
        };


Answer 5:

正如我所测试的所有小提琴上面他们有一个错误。 这是当你将翻转X和Y值加在一起,剪裁边界将是错误的。 此外,为了不这样做的所有计算用于放置图片到合适的位置,你需要指定originX =“中心”和originY =为他们“中心”。

下面是一个剪辑功能更新从@natchiketa原码

var clipByName = function (ctx) {
    var clipRect = findByClipName(this.clipName);
    var scaleXTo1 = (1 / this.scaleX);
    var scaleYTo1 = (1 / this.scaleY);
    ctx.save();
    ctx.translate(0,0);

    //logic for correct scaling
    if (this.getFlipY() && !this.getFlipX()){
      ctx.scale(scaleXTo1, -scaleYTo1);
    } else if (this.getFlipX() && !this.getFlipY()){
      ctx.scale(-scaleXTo1, scaleYTo1);
    } else if (this.getFlipX() && this.getFlipY()){
      ctx.scale(-scaleXTo1, -scaleYTo1);
    } else {
        ctx.scale(scaleXTo1, scaleYTo1);
    }
    //IMPORTANT!!! do rotation after scaling
    ctx.rotate(degToRad(this.angle * -1));
    ctx.beginPath();
    ctx.rect(
        clipRect.left - this.left,
        clipRect.top - this.top,
        clipRect.width,
        clipRect.height
    );
    ctx.closePath();
    ctx.restore();
}

请检查更新小提琴



Answer 6:

随着面料1.6.0-RC.1最新的更新,您可以通过扭曲保持迁移的图像并拖动中间轴。

我对如何扭转倾斜,使裁剪区域停留了同样的麻烦。 我曾尝试下面的代码,试图扭转回来,但没有奏效。

var skewXReverse = - this.skewX;
var skewYReverse = - this.skewY;

ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.transform(1, skewXReverse, skewYReverse, 1, 0, 0);
ctx.rotate(degToRad(this.angle * -1));

演示: http://jsfiddle.net/uimos/bntepzLL/5/



Answer 7:

更新以前的人的答案。

ctx.rect(
    clipRect.oCoords.tl.x - this.oCoords.tl.x - clipRect.strokeWidth,
    clipRect.oCoords.tl.y - this.oCoords.tl.y - clipRect.strokeWidth,
    clipRect.oCoords.tr.x - clipRect.oCoords.tl.x,
    clipRect.oCoords.bl.y - clipRect.oCoords.tl.y
);

现在我们能够扩展裁剪区域毫无疑问。



文章来源: Multiple clipping areas on Fabric.js canvas