19

为了制作Photo Collage Maker,我使用了具有基于对象的剪辑功能的 fabric js。此功能很棒,但该剪切区域内的图像无法缩放、移动或旋转。我想要一个固定位置的剪切区域,并且可以根据用户的需要将图像定位在固定剪切区域内。

我用谷歌搜索并找到了非常接近的解决方案。

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();

Fabric js画布上的多个剪切区域

其中一个剪辑区域的图像出现在另一个剪辑区域中。我怎样才能避免这种情况,或者是否有另一种使用fabric js 来实现这一点的方法。

4

7 回答 7

32

这可以通过使用该clipTo属性的 Fabric 来完成,但您必须在函数中“反转”转换(缩放和旋转)clipTo

在Fabric中使用该clipTo属性时,缩放和旋转是在裁剪之后应用的,也就是说裁剪是随图像一起缩放和旋转的。您必须通过在属性函数中应用完全相反的转换来解决这个问题。clipTo

我的解决方案包括将一个Fabric.Rect用作剪辑区域的“占位符”(这具有优势,因为您可以使用 Fabric 来移动对象,从而移动剪辑区域。

请注意,我的解决方案使用Lo-Dash实用程序库,特别是_.bind()(参见上下文代码)。

示例小提琴


分解

1.初始化Fabric

首先,我们当然需要画布:

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对象一个 name 属性,clipFor所以clipTo函数可以找到它们想要被剪裁的对象:

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

剪辑区域不一定要有实际对象,但它更易于管理,因为您可以使用 Fabric 移动它。

3.剪辑功能

我们分别定义图像clipTo属性使用的函数以避免代码重复:

由于angleImage 对象的属性以度为单位存储,因此我们将使用它来将其转换为弧度。

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

findByClipName()是一个方便的函数,它使用Lo-Dash来查找clipFor要裁剪的 Image 对象的属性(例如,在下图中,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 = 'https://fabricjs.com/lib/pug.jpg';

做什么_.bind()

请注意,引用包含在_.bind()函数中。

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

  1. 我们需要将引用Image对象传递给clipByName()
  2. clipTo属性被传递给画布上下文,而不是对象。

基本上,_.bind()允许您创建一个使用您指定的对象作为this上下文的函数版本。

来源
  1. https://lodash.com/docs#bind
  2. https://fabricjs.com/docs/fabric.Object.html#clipTo
  3. https://html5.litten.com/understanding-save-and-restore-for-the-canvas-context/
于 2013-06-14T04:30:34.280 回答
11

我已经通过@natchiketa 调整了解决方案,因为剪辑区域的定位没有正确定位,并且在旋转时都很不稳定。但现在一切似乎都很好。看看这个修改过的小提琴: https ://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 的间距。
于 2014-07-22T20:54:59.550 回答
8

更新到@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));

看到这个: https ://jsfiddle.net/ZxYCP/185/

正确的结果:

好的

更新 1:

我开发了一个按多边形剪辑的功能: https ://jsfiddle.net/ZxYCP/198/

聚

于 2015-09-14T03:40:41.060 回答
4

这可以更容易地完成。Fabric 提供了渲染方法来通过另一个对象的上下文进行剪辑。

签出这个小提琴我在这里的评论中看到了这一点。

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

    clippingRect.render(ctx);

    ctx.restore();
};
于 2017-04-26T18:30:42.050 回答
3

当我测试上面的所有小提琴时,它们有一个错误。当您将 X 和 Y 值一起翻转时,剪裁边界将是错误的。此外,为了不进行所有计算以将图像放置到正确的位置,您需要为它们指定originX='center'originY='center'

这是来自@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();
}

请检查更新的小提琴

于 2017-06-15T18:40:08.947 回答
1

随着最新的更新fabric 1.6.0-rc.1,您可以通过按住 shift 并拖动中轴来倾斜图像。

我不知道如何扭转歪斜以使剪裁区域保持不变。我尝试了以下代码来尝试将其反转,但没有奏效。

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));

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

于 2015-10-29T16:11:03.453 回答
-1

更新到以前的人的答案。

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
);

现在我们可以毫无疑问地缩放剪切区域。

于 2016-10-27T14:41:20.440 回答