Fabric.js画布上的多个剪裁区域

时间:2013-05-08 09:57:30

标签: html5-canvas photo fabricjs image-clipping

为了制作Photo Collage Maker,我使用具有基于对象剪辑功能的结构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();

Multiple Clipping Areas on fabric js canvas

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

6 个答案:

答案 0 :(得分:29)

使用clipTo属性可以使用Fabric完成此操作,但您必须在clipTo函数中“反转”转换(缩放和旋转)。

在Fabric中使用clipTo属性时,在剪切之后应用缩放和旋转,这意味着剪裁会随图像缩放并旋转。您必须通过在clipTo属性函数中应用转换的精确反向来解决此问题。

我的解决方案是让Fabric.Rect作为剪辑区域的“占位符”(这有利,因为您可以使用Fabric来移动对象,从而移动剪辑区域。

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

Example Fiddle


击穿

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

没有作为剪辑区域的实际对象,但它更容易管理,因为您可以使用Fabric移动它。

3。剪辑功能

我们定义图像'clipTo属性将单独使用的函数,以避免代码重复:

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

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

findByClipName()是一个便捷函数,使用Lo-Dash来查找要剪裁的Image对象的clipFor属性(例如,在下图中,{ {1}}将为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(); } 的使用情况。

4。使用this

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

  1. 我们需要将引用_.bind()对象传递给Image
  2. clipByName()属性传递画布上下文,而不是对象。
  3. 基本上,clipTo允许您创建使用您指定的对象作为_.bind()上下文的函数版本。

    来源
    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/

答案 1 :(得分:10)

我已经调整了@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的间距。

答案 2 :(得分:2)

这可以更容易地完成。 Fabric提供render方法来剪切另一个对象上下文。

结帐fiddle。我在评论here上看到了这一点。

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

            clippingRect.render(ctx);

            ctx.restore();
        };

答案 3 :(得分:1)

当我测试上面的所有小提琴时,他们有一个错误。当你将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();
}

请查看更新的fiddle

答案 4 :(得分:0)

使用Fabric 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/

答案 5 :(得分:0)

更新以前的家伙的答案。

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

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