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

Posted

技术标签:

【中文标题】Fabric.js 画布上的多个剪切区域【英文标题】:Multiple clipping areas on Fabric.js canvas 【发布时间】:2013-05-02 12:10:57 【问题描述】:

为了制作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();

Multiple Clipping Areas on fabric js canvas

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

【问题讨论】:

如果你能选择我的答案,我会很感激,没有其他答案,当然假设它对你有用。 @ep4f 这正是我所追求的,您是否使用以下答案解决了这个问题? - 如果是这样,当您将图像缩放得更大时,剪切区域和位置会发生变化,您是如何停止的?我怎样才能使剪辑区域始终保持该大小和位置? 如果有人注意到在 Fabric.js 中裁剪到一组对象的问题,那么也应该看看 this answer。 【参考方案1】:

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

当您在Fabric 中使用clipTo 属性时,缩放和旋转是在 剪辑之后应用的,这意味着剪辑随图像一起缩放和旋转。您必须通过在 clipTo 属性函数中应用精确的 reverse 转换来解决此问题。

我的解决方案是让 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 属性(例如,在下图中,name 将是@987654348 @):

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() 有以下两个原因:

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

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

来源
    https://lodash.com/docs#bind https://fabricjs.com/docs/fabric.Object.html#clipTo https://html5.litten.com/understanding-save-and-restore-for-the-canvas-context/

【讨论】:

小提琴需要更新(因为fabricjs的位置已经改变):jsfiddle.net/32Acb @natchiketa 除了一件事之外,这正是我所追求的——我能问一下为什么实际的图像和剪辑区域与黑色矩形不匹配吗?为什么当您将图像缩放得更大时,剪切区域和位置会发生变化?我怎样才能使剪辑区域始终保持该大小和位置? Fabric 有一个 utils 类,其中有一个 degreeToRadians 函数。 fabric.util.degreesToRadians(值) TypeError: _(canvas.getObjects()).where 不是函数。 (在 '_(canvas.getObjects()).where( clipFor: name )' 中,'_(canvas.getObjects()).where' 未定义)【参考方案2】:

我已经通过@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();

我发现了两个小问题:

    向剪裁对象添加描边似乎会使内容偏离几个像素。我试图补偿定位,但在旋转时,它会在底部和右侧添加 2 个像素。所以,我选择完全删除它。 有时,当您旋转图像时,剪裁的随机边上会出现 1 像素的间距。

【讨论】:

注意 - 我发现我原来的小提琴在 FireFox 中不能正常工作。我能够通过使用 FabricJS 1.4.8 而不是 1.4.0 来解决这个问题。看来 FF 和 crossOrigin 图像存在一些错误/冲突。 github.com/kangax/fabric.js/issues/903我已经更新了上面帖子中的小提琴链接。 请注意,需要保持图像的纵横比。如果你不这样做,它会产生一些奇怪的效果。这可能是转换矩阵的一些问题 在我的例子中,剪裁区域没有采用矩形的角度。 prntscr.com/8gw2qi 剪裁区域总是直截了当。这应该完全适合这个框架部分。但它被剪裁了 90 度。有什么想法吗?【参考方案3】:

更新到@Promlnc 答案。

您需要替换上下文转换的顺序才能执行正确的剪辑。

    翻译 缩放 旋转

否则,您将得到错误的对象 - 当您在不保持纵横比的情况下进行缩放(仅更改一个维度)。

代码 (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/

【讨论】:

在我的例子中,剪裁区域没有采用矩形的角度。 prntscr.com/8gw2qi 剪辑区域总是直截了当。这应该完全适合这个框架部分。但它被剪裁了 90 度。有什么想法吗? @deveshsinghal 提出新问题。我会回答你的。这不是这个问题的主题。 好的@Luk,但我在***.com/questions/32505705/…得到了解决我的问题 不幸的是,我已经开始编写我的解决方案了:P 试试吧,也许它会以某种方式帮助你。 我还有一个问题要问你。 ;) :P 如果你能帮忙,请看看。 ***.com/questions/32626889/…【参考方案4】:

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

查看此fiddle。我在here 的评论中看到了这个。

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

    clippingRect.render(ctx);

    ctx.restore();
;

【讨论】:

【参考方案5】:

当我测试上面的所有小提琴时,它们都有一个错误。当您将 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

【讨论】:

这工作正常,我有一个对象的原点中心,以前我没有使用原点,现在我在动态计算顶部和左侧时遇到问题。【参考方案6】:

随着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/

【讨论】:

【参考方案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
);

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

【讨论】:

如果您要更新其他人的答案,请更新他们而不是发布另一个答案。

以上是关于Fabric.js 画布上的多个剪切区域的主要内容,如果未能解决你的问题,请参考以下文章

在 html5 中的 fabric.js 画布上一次删除多个对象

Fabric.js 动态裁剪区域对图像和 SVG/形状的影响不同

Fabric.js橡皮擦问题画布

使用 Fabric.js 选择画布上的所有对象

Fabric.js 将画布(或对象组)剪辑到多边形

使用 fabric.js 在画布上绘制文本