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 画布上一次删除多个对象