I have the path of a heart (below), but when I draw it on the canvas with fabric.js, the boundingbox is not displayed correctly. This happens even if I use setCoords(). Moreover, when I export the object to svg, the location of the heart is at the top of the boundingbox instead of the location it is displayed on the canvas.
var path = new fabric.Path("M248.078,5.883c-36.691-14.739-77.771-0.839-98.517,31.125C128.817,5.044,87.735-8.856,51.043,5.883 C9.354,22.632-10.863,70.009,5.887,111.696c16.06,39.98,143.314,139.607,143.314,139.607l0.359,0.28l0.36-0.28 c0,0,127.251-99.627,143.314-139.607C309.986,70.009,289.768,22.632,248.078,5.883z");
The jsfiddle, http://jsfiddle.net/mPhL2/
Does anyone know how to solve this, is this a bug?
Thanks!
It has something to do with the coordinates of the path. Let's do a simple example.
The blue triangle is defined with absolute coordinates and the selection if fine
It has been produced with
canvas = new fabric.Canvas('myCanvas', { selection: false });
var path = new fabric.Path('M 0 0 L 0 100 L 100 100 Z');
path.set({ left: 20, top: 0, fill: 'blue', });
canvas.add(path);
canvas.renderAll();
If we take relative coordinates as with the green triangle then the selection does not surround the object properly anymore.
canvas = new fabric.Canvas('myCanvas', { selection: false });
var path = new fabric.Path('M 20 20 l 0 100 l 100 0 Z');
path.set({ left: 20, top: 0, fill: 'green', });
canvas.add(path);
canvas.renderAll();
So if we take another path for the heart (copied out from wikipedia) it works.
canvas = new fabric.Canvas('myCanvas', { selection: false });
var path = new fabric.Path('M 272.70141,238.71731 \
C 206.46141,238.71731 152.70146,292.4773 152.70146,358.71731 \
C 152.70146,493.47282 288.63461,528.80461 381.26391,662.02535 \
C 468.83815,529.62199 609.82641,489.17075 609.82641,358.71731 \
C 609.82641,292.47731 556.06651,238.7173 489.82641,238.71731 \
C 441.77851,238.71731 400.42481,267.08774 381.26391,307.90481 \
C 362.10311,267.08773 320.74941,238.7173 272.70141,238.71731 \
z ');
var scale = 100 / path.width;
path.set({ left: 20, top: 0, scaleX: scale, scaleY: scale, fill: 'red', });
canvas.add(path);
canvas.renderAll();
Related
I updated fabricjs for new control feature.
But my old image clipping is not working anymore since clipTo in fabric.Object is removed in new version.
How can I clip image without using clipTo, in change log they said I should use clipPath instead.
img.set({
clipTo: function (ctx) {
ctx.moveTo(0, 0);
ctx.arc(0, 0,300, -Math.PI/6, -Math.PI+Math.PI/6 , true);
}
});
Here is jsfiddle
Also this official demo won't work in version 4 beta
http://fabricjs.com/clipping
So, clipTo is deprecated since version 2 and was removed in version 4. The correct way of clipping is to use the clipPath property. Here is a simple example:
var radius = 150;
var clipPath = new fabric.Circle({
left: 0,
top: 0,
radius: radius,
startAngle: 0,
angle: Math.PI * 2,
originX: "top"
});
fabric.Image.fromURL("../public/pug_small.jpg", function(img) {
img.scale(0.5).set({
left: 100,
top: 100,
angle: -15,
clipPath: clipPath
});
canvas.add(img).setActiveObject(img);
});
Here is the official tutorial for the clipPath http://fabricjs.com/clippath-part1
Recent discussion about clipPath in the beta 4 version: https://github.com/fabricjs/fabric.js/issues/6159
And a sample SandBox demo: https://codesandbox.io/s/stackoverflow-60664120-fabric-js-400-beta8-mi0y7
I found temporary solution but it is not really answer:
I draw circle and polygon
var radius = 100;
var start = -150*Math.PI/180
var end = -30*Math.PI/180
let point1 = new fabric.Point(
(radius+1)*Math.cos(start),
(radius+1)*Math.sin(start)
)
let point2 = new fabric.Point(
(radius+1)*Math.cos(end),
(radius+1)*Math.sin(end)
)
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function (img) {
img.scale(1).set({
left: 0,
top: 0,
clipPath:new fabric.Group([
new fabric.Circle({
originX:'center',
originY:'center',
radius,
startAngle: start,
endAngle: end,
stroke:false,
strokeWidth:6
}),
new fabric.Polygon([point1,{x:0,y:0},point2],{
originX:'center',
originY:'center',
strokeWidth:6
})
])
);
canvas.add(img).setActiveObject(img);
});
http://jsfiddle.net/mudin/z75nvgqs/31/
Help me to find better solution
My problem is how to animate the drawing of a path between two points.
Consider a curved line or path between two points, A & B. I can draw this easily on the canvas using the line drawing functions in Konvajs.
However, what I actually want is to animate the revealing of the line so that it starts from point A and progressively draws to point B. The reveal should be animated so I can apply pleasing easings.
As a comparable example, see the brief video on this site https://coggle.it/ where the video shows the creation of a new box and the line draws to connect it to the old.
Here is a potential answer (special thanks to #markov00 re same technique in SVG). It works by manipulating the path dashOffset and dash attributes. There is an excellent explanation of the technique here in a post by Jake Archibald which also includes an interactive experiment with sliders which I found very useful.
I have tried to make the demo as lightweight as possible and just show the technique - though I added a slider and some UI to help understand the process. The use of jquery is only for the UI parts which are not needed for the technique.
Couple of points:
The demo here uses a path from 3 straight line segments. But I tried curves and combination paths and the technique works in those cases too - so any path should work.
I found that using a close-path command (z) on the path caused the path length function to be short on the true distance. This appears as an amount of the path remaining stroked or gapped at either end, with the size depending on the jump between first & last to close the path.
The path length is virtually always going to be decimal so don't try to do everything as integers as you will ultimately find your stroke is slightly overlong or short.
To adopt this for animation and easing etc, take the couple of lines from the slider change event and stick them inside the frame callback, manipulating the maths to suit your case.
// Set up the canvas / stage
var stage = new Konva.Stage({container: 'container1', width: 320, height: 180});
// Add a layer
var layer = new Konva.Layer({draggable: false});
stage.add(layer);
// show where the start of the path is.
var circle = new Konva.Circle({
x: 66,
y: 15,
radius: 5,
stroke: 'red'
})
layer.add(circle);
// draw a path.
var path = new Konva.Path({
x: 0,
y: 0,
data: 'M66 15 L75 100 L225 120 L100 17 L66 15',
stroke: 'green'
});
// get the path length and set this as the dash and dashOffset.
var pathLen = path.getLength();
path.dashOffset(pathLen);
path.dash([pathLen]);
layer.add(path)
stage.draw();
// Some UI bits
$('#dist').attr('max', parseInt(pathLen)); // set slider max to length of path
$('#pathLen').html('Path : ' + pathLen); // display path length
// jquery event listener on slider change
$('#dist').on('input', function(){
// compute the new dash lenth as original path length - current slider value.
// Means that dashLen initially = path len and moves toward zero as slider val increases.
var dashLen = pathLen - $(this).val();;
path.dashOffset(dashLen); // set new value
layer.draw(); // refresh the layer to see effect
// update the UI elements
$('#dashLen').html('Dash: ' + dashLen);
$('#pathPC').html(parseInt(100-(100 * (dashLen/pathLen)), 10) + '%');
})
.info
{
padding-left: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.js"></script>
<div class="slidecontainer">
<input class='slider' id='dist' type="range" min="0" max="100" value="0" class="slider" id="myRange"/>
<span class='info' id='pathPC'></span>
<span class='info' id='pathLen'></span>
<span class='info' id='dashLen'></span>
</div>
<div id='container1' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>
<div id='img'></div>
My solution with animation:
var width = window.innerWidth;
var height = window.innerHeight;
// Set up the canvas / stage
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
// Add a layer
var layer = new Konva.Layer({
draggable: false
});
stage.add(layer);
// show where the start of the path is.
var circle = new Konva.Circle({
x: 66,
y: 15,
radius: 5,
stroke: 'red'
})
layer.add(circle);
// draw a path.
var path = new Konva.Path({
x: 0,
y: 0,
data: 'M66 15 L75 100 L225 120 L100 17 L66 15',
stroke: 'green'
});
// get the path length and set this as the dash and dashOffset.
var pathLen = path.getLength();
path.dashOffset(pathLen);
path.dash([pathLen]);
// make some animation with stop
var anim = new Konva.Animation(function (frame) {
var dashLen = pathLen - frame.time / 5;
path.dashOffset(dashLen);
if (dashLen < 0) {
anim.stop();
}
}, layer);
anim.start();
layer.add(path)
stage.draw();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.js"></script>
<div id='container' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>
<div id='img'></div>
And here is an alternative to #Roxane's animated version but using a tween.
var width = window.innerWidth;
var height = window.innerHeight;
// Set up the canvas / stage
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
// Add a layer
var layer = new Konva.Layer({
draggable: false
});
stage.add(layer);
// show where the start of the path is.
var circle = new Konva.Circle({
x: 66,
y: 15,
radius: 5,
stroke: 'red'
})
layer.add(circle);
// draw a path.
var path = new Konva.Path({
x: 0,
y: 0,
data: 'M66 15 L75 100 L225 120 L100 17 L66 15',
stroke: 'green'
});
// get the path length and set this as the dash and dashOffset.
var pathLen = path.getLength();
path.dashOffset(pathLen);
path.dash([pathLen]);
layer.add(path); // have to add to layer for tweening.
// create the tween
var tween = new Konva.Tween({
node: path,
dashOffset: 0,
easing: Konva.Easings['BounceEaseOut'],
duration: 1.5
});
tween.play(); // execute the tween
stage.draw();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.js"></script>
<div id='container' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>
<div id='img'></div>
I'm new to fabricjs (and to Javascript development in general). I am "porting" a legacy Flex/Actionscript project and need to enable the user to create a complex clipping path for an image.
My approach in Actionscript was to use the Actionscript Graphics class using BlendMode.ERASE to "erase" from the yellow base rectangle (i.e. give the appearance of erasing) and then using that set of rects to create a bitmap to serve as an alpha channel for the final image (Step 3) created on the fly.
Can anyone suggest how I might accomplish a similar functionality in Fabric? It doesn't seem to support HTML5 Canvas Blend modes and while I see that it supports clipping paths for images, I'm not seeing how I can enable the user to interactively create a clipping path without doing lots of intersection checks to try to derive the points to create a new path on the fly.
Thanks!
Step 1: After the user has drawn a base rectangle, drag-option/alt-key enables them to draw a rectangle (the red line) which will be subtracted from the base rect.
Step 2: The base rect is shown with the subtraction.
Step 3: The base rect is used to clip or mask a section of the base image
Step 1
Step 2
Step 3
Will Tower,
There is no easy way to do it. Here are the steps:
Draw 'Yellow' rectangle
Draw 'Red' rectangle
Use clipping library like PolyBool for intersection and xor operations
Convert drawing result into the clipped path of combining rectangles
clip your image
I created some quick fiddle. You have to click on a each button to clip. It won't clip if you will not add 2 rectangles on the canvas. This is very simple example. In order to work properly you have to draw rectangles with mouse (make them dynamic). Also, this logic is not accounting for these variations (you have to work on them as well):
For these use cases Clipping Library will return to you 2 set of results, which means different logic should be implemented.
Actual code without jQuery, FabriJs, and PolyBool libraries:
var imgURL = 'http://fabricjs.com/lib/pug.jpg';
var clipYellowRect = null;
var clipRedRect = null;
var pug = null;
var canvas = new fabric.Canvas('c');
// insert image into canvas
var pugImg = new Image();
pugImg.onload = function (img) {
pug = new fabric.Image(pugImg, {
angle: 0,
width: 500,
height: 500,
left: 100,
top: 50,
scaleX: 0.5,
scaleY: 0.5,
clipName: 'pug',
});
canvas.add(pug);
};
pugImg.src = imgURL;
//draw yellow rectangle
$('#btnYellowRect').on('click', function(){
clipYellowRect = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 120,
top: 60,
width: 200,
height: 200,
fill: 'rgba(255,255,0,0.5)',
strokeWidth: 0,
selectable: false
});
canvas.add(clipYellowRect);
});
//draw red rectangle
$('#btnRedRect').on('click', function(){
clipRedRect = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 90,
top: 120,
width: 100,
height: 100,
strokeWidth: 3,
fill: 'transparent',
stroke: 'rgba(255,0,0,1)', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
canvas.add(clipRedRect);
});
//clip
$('#btnClip').on('click', function(){
var yellowRectRegion = getRegion(clipYellowRect);
var redRectRegion = getRegion(clipRedRect);
//determine inersection
var intersectResult = PolyBool.intersect({
regions: [yellowRectRegion],
inverted: false
}, {
regions: [redRectRegion],
inverted: false
});
//generate clipping path
var xorResult = PolyBool.xor({
regions: [yellowRectRegion],
inverted: false
}, {
regions: intersectResult.regions,
inverted: false
});
clipImage(xorResult.regions[0]);
});
//prepare data for clipping library
function getRegion(rect){
return [[rect.left, rect.top],
[rect.left + rect.width, rect.top],
[rect.left + rect.width, rect.top + rect.height],
[rect.left, rect.top + rect.height]]
}
function clipImage(points){
//actual clipping
pug.clipTo = function (ctx) {
var scaleXTo1 = (1 / pug.scaleX);
var scaleYTo1 = (1 / pug.scaleY);
ctx.save();
var ctxLeft = -( pug.width / 2 );
var ctxTop = -( pug.height / 2 );
ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
console.log(points)
ctx.moveTo(points[0][0] - pug.oCoords.tl.x, points[0][1] - pug.oCoords.tl.y);
for (var i=1; i < points.length; i++){
ctx.lineTo(points[i][0] - pug.oCoords.tl.x, points[i][1] - pug.oCoords.tl.y);
}
ctx.closePath();
ctx.restore();
};
clipYellowRect.remove();
clipRedRect.remove();
canvas.renderAll();
}
Hopefully it will help you.
I'm trying to clip an image with a path but can't seem to find any help to do this with.
I've found examples to clip an image with a rectangle or circle, but a path example seems to be missing. I've also managed to clip the entire canvas with the path, but what I want is just clipping 1 image. Any help will be appreciated!
The following example is for clipping a canvas with the path.
var canvas = new fabric.Canvas('c');
canvas.setDimensions({width:_winWidth,height:_winHeight});
var path = new fabric.Path('M2.9,245h-5.8c-35.3,0-69.3-14.4-93.3-39.4c-23.3-24.3-35.2-56.4-33.7-90.4c1.8-40.1,21-68.9,39.6-91.8l2.2-2.8\
c10.6-13,22.6-27.8,23.6-43.4c0.9-14.6-7.1-27.8-15-38.9c-14.5-20.3-31-43.3-31-74.5c-0.1-28,11.5-55.1,32.4-76.1\
C-57.2-233.1-29.8-245-2.9-245h5.8c27,0,54.3,11.9,75.1,32.7c21,21,32.5,48,32.4,76C110.4-105,93.9-82,79.4-61.7\
c-7.9,11.1-15.9,24.4-15,38.9c1,15.6,13,30.4,23.6,43.4l2.2,2.8c18.6,22.9,37.8,51.7,39.6,91.8c1.5,34-10.4,66.2-33.7,90.4\
C72.2,230.6,38.2,245,2.9,245z');
path.centeredScaling = true;
path.scale(1);
//path.selectable = true;
path.originX = 'center';
path.originY = 'center';
path.set({ left: (_winWidth/2), top: (_winHeight/2)});
fabric.Image.fromURL('../images/home/2.jpg', function(img) {
img.selectable = false;
img.scale(1.2);
canvas.centerObject(img);
canvas.add(img);
canvas.clipTo = function (ctx) {
path.render(ctx);
}
canvas.renderAll();
});
canvas = new fabric.Canvas('fabric', { selection: false });
var path = new fabric.Path('M 272.70141,238.71731 \
C 206.46141,238.71731 152.70146,292.4773 152.70146,358.71731 \
C 152.70146,493.47282 288.63461,528.80461 381.26391,662.02535 \
C 468.83815,529.62199 609.82641,489.17075 609.82641,358.71731 \
C 609.82641,292.47731 556.06651,238.7173 489.82641,238.71731 \
C 441.77851,238.71731 400.42481,267.08774 381.26391,307.90481 \
C 362.10311,267.08773 320.74941,238.7173 272.70141,238.71731 \
z ');
var scale = 100 / path.width;
path.set({ left: 20, top: 0, scaleX: scale, scaleY: scale, fill: 'red', });
canvas.add(path);
addPattern(path);
canvas.renderAll();
function addPattern(obj){
fabric.util.loadImage('http://fabricjs.com/assets/pug_small.jpg', function (img) {
obj.fill = new fabric.Pattern({
source: img,
repeat: 'no-repeat'
});
canvas.renderAll();
});
}
To clip a single image, try this:-
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(img) {
img.scale(0.5).set({
left: 100,
top: 100,
angle: -15,
clipTo: function (ctx) {
path.render(ctx);
}
});
canvas.add(img).setActiveObject(img);
});
Note: I'd use var myPath = xxxx instead since 'path' is too close to SVG's path
When I add a circle with an opacity lower than 1 to a group, its opacity becomes actually lower than the specified value. This does not happen if I don't specify opacity (i.e., opacity = 1). It also doesn't happen with a rectangle.
Here is the code to reproduce this issue:
HTML
<canvas id="stage" width="400" height="300">
JavaScript
var OPACITY = 0.65;
var FILL = '#fff';
var canvas = new fabric.Canvas('stage', {
backgroundColor: '#222'
});
/**
* Rectangles
* both appear to have the same color
*/
var rect1 = new fabric.Rect({
width: 40,
height: 40,
fill: FILL,
opacity: OPACITY,
left: 60,
top: 60
});
canvas.add(rect1);
var rect2 = new fabric.Rect({
width: 40,
height: 40,
fill: FILL
opacity: OPACITY,
});
var rect2Group = new fabric.Group([rect2], {
left: 120,
top: 60
});
canvas.add(rect2Group);
/**
* Circles
* the second circle is darker
*/
var circle1 = new fabric.Circle({
radius: 20,
fill: FILL,
opacity: OPACITY,
left: 60,
top: 120
});
canvas.add(circle1);
var circle2 = new fabric.Circle({
radius: 20,
fill: FILL,
opacity: OPACITY,
});
var circle2Group = new fabric.Group([circle2], {
left: 120,
top: 120
});
canvas.add(circle2Group);
Here is the JSFiddle.
If you run it, you can see that the second circle is darker than the first one, meaning that its opacity is lower.
Am I doing something wrong, or is this a bug? (Could be reproduced in 1.2.0 and 1.3.0.)
It's most likely because of this line in fabric.Circle:
// multiply by currently set alpha
// (the one that was set by path group where this object is contained, for example)
ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity;
This has to do with circles being part of SVG group, IIRC.
In any case, it's definitely a bug — the opacity shouldn't be multiplied in your case. We need to have a better check.
Please file an issue on github.