Fabricjs. image with rounded corner and stroke - javascript

Is there a simple way of clipping an image with rounded corner and inner stroke?
https://jsfiddle.net/4tursk3y/
$('#clip').click(function() {
canvas._objects[1].set({
'clipTo': function(ctx) {
var rect = new fabric.Rect({
left: 0,
top: 0,
rx: 20 / this.scaleX,
ry: 20 / this.scaleY,
width: this.width,
height: this.height,
fill: '#000000'
});
rect._render(ctx, false);
}
});
canvas.renderAll();
});

You can use a rectangle with a pattern fill to get rounded image corners without clipping the stroke.
var rect = new fabric.Rect({
left: 10,
top: 10,
width: 140,
height: 215,
stroke: 'red',
strokeWidth: 3,
rx:10,
ry:10
});
canvas.add(rect);
fabric.util.loadImage('http://fabricjs.com/assets/pug.jpg', function (img) {
rect.setPatternFill({
source: img,
repeat: 'no-repeat',
patternTransform: [0.2, 0, 0, 0.2, 0, 0]
});
canvas.renderAll();
});
https://jsfiddle.net/melchiar/78nt10ua/

for newer fabric versions use this
const roundedCorners = (fabricObject, cornerRadius) => new fabric.Rect({
width: fabricObject.width,
height: fabricObject.height,
rx: cornerRadius / fabricObject.scaleX,
ry: cornerRadius / fabricObject.scaleY,
left: -fabricObject.width / 2,
top: -fabricObject.height / 2
})
image.set("clipPath", roundedCorners(image, 20))

Related

Fabric js wrong shape when having negative height or width when setting rx and ry to nonzero value

I'm trying to draw rectangle with rounded corners but when setting negative width or height I'm getting a wrong shape like this
How to have the correct shape (the top left one in the gif) regardless of the start point and the draw direction?
canvas.on('mouse:down', (e) => {
const roundedRect = new fabric.Rect( {
top: e.pointer.y,
left: e.pointer.x,
width: 0,
height: 0,
stroke: 'black',
strokeWidth: 3,
rx: 10,
ry: 10,
fill: 'transparent',
hasControls: false,
});
canvas.add(roundedRect);
canvas.setActiveObject(roundedRect);
});
canvas.on('mouse:move', (e) => {
const pointer = e.pointer;
const activeObj = canvas.getActiveObject();
let height = pointer.y - activeObj.top;
let width = pointer.x - activeObj.left;
activeObj.set({ height: height, width: width});
activeObj.setCoords();
});

How to create object stick on center of parent object in FabricJS?

I have an object e.g circle and I want to add more circles on border of the parent circle that must be center aligned.
On the attached gif image you can see my cursor - it only allows me to create a child circle on border of the parent.
Till now I was only able to create the parent circle.
var circle = new fabric.Circle({
strokeWidth: 1,
stroke: "#222",
noScaleCache: false,
strokeUniform: true,
scaleX: 1,
scaleY: 1,
left: canvas.getWidth() / 2,
top: canvas.getHeight() / 2,
radius: fabric.util.parseUnit(circleSize + 'in')
});
Basically you need to calculate Euclidean distance between the circle that you want to draw and the main circle, and if the distance is near to the border of the main circle you just draw a circle at the mouse position where you clicked.
The distance is calculated using Math.hypot function.
And here is the code.
var canvas = new fabric.Canvas('c', {
selection: false
});
var circleRadius = 100;
var c = new fabric.Circle({
strokeWidth: 3,
stroke: "#222",
fill: "#55ff55",
noScaleCache: false,
strokeUniform: true,
selectable: false,
scaleX: 1,
scaleY: 1,
left: canvas.getWidth() / 2 - circleRadius,
top: canvas.getHeight() / 2 - circleRadius,
radius: circleRadius
});
canvas.add(c);
var circleAngle = Math.atan2(c.getCenterPoint().x,c.getCenterPoint().y);
var circle = new fabric.Circle({
strokeWidth: 3,
stroke: "#222",
radius: 30,
selectable: true,
originX: 'center',
originY: 'center',
fill: "#ff5555ff",
left: Math.sin(circleAngle) * circleRadius + canvas.getWidth() / 2,
top: Math.cos(circleAngle) * circleRadius + canvas.getHeight() / 2
});
canvas.add(circle);
var isDown, origX, origY;
canvas.on('mouse:down', function(o) {
var pointer = canvas.getPointer(o.e);
origX = pointer.x;
origY = pointer.y;
if (isDown) {
circle.set({
strokeWidth: 3,
stroke: "#222",
radius: 30,
left: pointer.x,
top: pointer.y,
fill: "#ff5555ff"
});
canvas.add(circle);
circle = new fabric.Circle({
strokeWidth: 3,
stroke: "#222",
radius: 1,
selectable: true,
originX: 'center',
originY: 'center'
});
canvas.add(circle);
}
});
canvas.on('mouse:move', function(o) {
var pointer = canvas.getPointer(o.e);
var x = pointer.x - c.getCenterPoint().x;
var y = pointer.y - c.getCenterPoint().y;
circle.set({
strokeWidth: 3,
stroke: "#222",
radius: 30,
left: Math.sin(Math.atan2(x,y))*c.radius + canvas.getWidth() / 2,
top: Math.cos(Math.atan2(x,y))*c.radius + canvas.getHeight() / 2,
fill: "#ff5555ff"
});
if (nearToBorder(pointer, c.getCenterPoint(), c.radius)) {
isDown = true;
} else {
isDown = false;
}
canvas.renderAll();
});
canvas.on('mouse:up', function(o) {
isDown = false;
});
function nearToBorder(pointer, circlePoint, r) {
var d = Math.hypot(pointer.x - (circlePoint.x), pointer.y - (circlePoint.y));
return d >= r - 10 && d <= r + 10;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.0.0-rc.1/fabric.min.js"></script>
<canvas id="c" width="500" height="500" style="border:1px solid #ccc"></canvas>

Fabric.js: Grayout other portion of image other than the selected portion

In the below Image I have three boxes in the canvas and there are three buttons at the bottom of the image. Whenever I click a button, the corresponding object in the canvas gets selected(i,e, when I click the green button, green rectangle in the canvas, gets selected).
My requirement is to highlight only the selected portion and other portion of the canvas should be grayed out. (Ex: If I click the green button green rectangle should be selected and other portion should be overlayed with a gray background).
Js Fiddle Link: https://jsfiddle.net/rnvs2hdk/1/
var canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'yellow';
var li= []
canvas.renderAll();
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
var img1 = myImg.set({ left: 0, top: 0 ,width:400,height:500});
canvas.add(img1);
var green = new fabric.Rect({
left: 50,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(34,177,76,1)',
strokeWidth: 5,
name:"green"
});
var yellow = new fabric.Rect({
left: 150,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,255,0,1)',
strokeWidth: 5,
name:"yellow"
});
var red = new fabric.Rect({
left: 250,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,0,0,1)',
strokeWidth: 5,
name:"red"
});
canvas.add(green, yellow,red);
li.push(green);
li.push(yellow);
li.push(red);
li.some(v=>{
var btn = document.createElement("BUTTON"); // Create a <button> elem
btn.innerHTML = v.name;
btn.addEventListener('click',function(e){
var name = e.target
if(name.innerText == "green"){
canvas.setActiveObject(li[0]);
}
if(name.innerText == "yellow"){
canvas.setActiveObject(li[1]);
}
if(name.innerText == "red"){
canvas.setActiveObject(li[2]);
}
});// Insert text
document.body.appendChild(btn);
});
console.log(li);
});
Expected Result:(example)
Here's my solution. Using the after:render event, you can perform canvas draw actions over each frame after it is rendered. This approach has the benefit of avoiding having to create and destroy fabric objects as needed which is an expensive operation.
Be sure to call the .setCoords() method during actions like scaling and moving so that objects will update their position information while performing these actions.
var canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'yellow';
var li = [];
canvas.renderAll();
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
var img1 = myImg.set({
left: 0,
top: 0,
width: 400,
height: 500
});
canvas.add(img1);
var green = new fabric.Rect({
left: 50,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(34,177,76,1)',
strokeWidth: 5,
name: "green",
hasRotatingPoint: false
});
var yellow = new fabric.Rect({
left: 150,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,255,0,1)',
strokeWidth: 5,
name: "yellow",
hasRotatingPoint: false
});
var red = new fabric.Rect({
left: 250,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,0,0,1)',
strokeWidth: 5,
name: "red",
hasRotatingPoint: false
});
canvas.add(green, yellow, red);
li.push(green);
li.push(yellow);
li.push(red);
li.some(v => {
var btn = document.createElement("BUTTON"); // Create a <button> elem
btn.innerHTML = v.name;
btn.addEventListener('click', function(e) {
var name = e.target
if (name.innerText == "green") {
canvas.setActiveObject(li[0]);
}
if (name.innerText == "yellow") {
canvas.setActiveObject(li[1]);
}
if (name.innerText == "red") {
canvas.setActiveObject(li[2]);
}
}); // Insert text
document.body.appendChild(btn);
});
console.log(li);
});
canvas.on({
'object:moving': function(e) {
//makes objects update their coordinates while being moved
e.target.setCoords();
},
'object:scaling': function(e) {
//makes objects update their coordinates while being scaled
e.target.setCoords();
}
});
//the after:render event allows you to perform a draw function on each frame after it is rendered
canvas.on('after:render', function() {
var ctx = canvas.contextContainer,
obj = canvas.getActiveObject();
if (obj) {
//set the fill color of the overlay
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
var bound = obj.getBoundingRect();
ctx.beginPath();
//draw rectangle to the left of the selection
ctx.rect(0, 0, bound.left, canvas.height);
//draw rectangle to the right of the selection
ctx.rect(bound.left + bound.width, 0, canvas.width - bound.left - bound.width, canvas.height);
//draw rectangle above the selection
ctx.rect(bound.left, 0, bound.width, bound.top);
//draw rectangle below the selection
ctx.rect(bound.left, bound.top + bound.height, bound.width, canvas.height - bound.top - bound.height)
ctx.fill();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
<div id="bt"></div>
I have Accomplished this creating 4 more rectangles around the actual rectangle. I have grayed out the outer rectangles so it gives the overlay effect. Also, I will delete all the rectangles before creating a new one.
let blankColor = "rgba(0,0,0,0)";
let grayOut = "rgba(0,0,0,0.4)";
let rect = createRect(x, y, width, height, 1, blankColor);
let rect1 = createRect(0, 0, getViewPortDimensions()[0], y, 0, grayOut);
let rect2 = createRect(0, y + height, getViewPortDimensions()[0], getViewPortDimensions()[1] - (y + height), 0, grayOut);
let rect3 = createRect(0, y, x, height, 0, grayOut);
let rect4 = createRect(x + width, y, getViewPortDimensions()[0] - (x + width), height, 0, grayOut);
state.canvas.add(rect, rect1, rect2, rect3, rect4);
Deleting the already drawn rectangle:
state.canvas.forEachObject((o, index) => {
if (index != 0) {
state.canvas.remove(o);
}
})

Fabricjs Selection on multiple objects on mouse click

Any suggestion on select multi objects on canvas mouse click? not all object, I want to select objects that overlay on the point.
For my knowledge, the target on mouse event is always the top most object only.
I had tried to bind event on object, but it wont fired for those at the back side.
I had tried select based on item size and height, but it is not working perfectly after rotate.
var canvas = this.__canvas = new fabric.Canvas('c', {
enableRetinaScaling: false
});
function LoopOnObjects(e) {
var mouse = canvas.getPointer(e.e, false);
var x = Math.ceil(mouse.x);
var y = Math.ceil(mouse.y);
var count = 0;
canvas.getObjects().forEach(function(object, index){
if(CheckObjectWithin(object, x, y)) {
count++;
}
});
alert("ya, there is " + count + " objects touch on click");
}
function CheckObjectWithin(object, x, y) {
var objectBoundRect = object.getBoundingRect(true);
var widthRange = objectBoundRect.width;
var heightRange = objectBoundRect.height;
if (x > objectBoundRect.left && x < objectBoundRect.left + widthRange) {
if (y > objectBoundRect.top && y < objectBoundRect.top + heightRange) {
return true;
}
}
return false;
}
function GetElement(e) {
LoopOnObjects(e);
}
canvas.on("mouse:up", GetElement);
canvas.add(new fabric.Rect({
width: 100, height: 100, left: 100, top: 20, angle: -10,
fill: 'rgba(0,200,0,0.5)'
}));
canvas.add(new fabric.Rect({
width: 50, height: 100, left: 220, top: 80, angle: 45,
stroke: '#eee', strokeWidth: 10,
fill: 'rgba(0,0,200,0.5)'
}));
canvas.add(new fabric.Circle({
radius: 50, left: 220, top: 175, fill: '#aac'
}));
template for testing
Actually, there is already a library method for that: fabric.Object.prototype.containsPoint(). It works with rotate, but keep in mind that the point is checked against the bounding box, not the visible shape (e.g. circle shape still has a rectangular bounding box).
var canvas = this.__canvas = new fabric.Canvas('c');
function loopOnObjects(e) {
var mouse = canvas.getPointer(e.e, false);
var point = new fabric.Point(mouse.x, mouse.y)
var count = 0;
canvas.getObjects().forEach(function(object, index){
if (object.containsPoint(point)) {
count++;
}
});
}
function getElement(e) {
loopOnObjects(e);
}
canvas.on("mouse:down", getElement);
canvas.add(new fabric.Rect({
width: 100, height: 100, left: 100, top: 20, angle: -10,
fill: 'rgba(0,200,0,0.5)'
}));
canvas.add(new fabric.Rect({
width: 50, height: 100, left: 220, top: 80, angle: 45,
stroke: '#eee', strokeWidth: 10,
fill: 'rgba(0,0,200,0.5)'
}));
canvas.add(new fabric.Circle({
radius: 50, left: 220, top: 175, fill: '#aac'
}));
#c {
border: 1px black solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js"></script>
<canvas id="c" width="500" height="300"></canvas>

Konva group X&Y different than shape X&Y

I am trying to edit another example that was posted on here but something doesn't seem right. I have a shape and then a couple of shapes that are grouped together with an arrow that is pointing in between the two shapes. As one of the shapes is dragged the arrow position should move as well.
The Problem
The problem is that the shape seems to be on a different coordinate system where its 0,0 point is in the upper left corner, whereas the groups coordinate system where its 0,0 point is right in the middle of the screen. What am I doing wrong?
Code
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.rawgit.com/konvajs/konva/0.13.0/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Circle Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var group = new Konva.Group({
x:120,
y:120,
draggable: true,
});
var circle = new Konva.Circle({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
radius: 40,
fill: 'green',
stroke: 'black',
strokeWidth: 2,
});
var circleA = new Konva.Circle({
x: stage.getWidth() / 5,
y: stage.getHeight() / 5,
radius: 30,
fill: 'red',
stroke: 'black',
strokeWidth: 2,
draggable: true
});
var arrow = new Konva.Arrow({
points: [circle.getX(), circle.getY(), circleA.getX(), circleA.getY()],
pointerLength: 10,
pointerWidth: 10,
fill: 'black',
stroke: 'black',
strokeWidth: 4
});
var star = new Konva.Star({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
numPoints: 5,
innerRadius: 30,
outerRadius: 50,
fill: '#89b717',
opacity: 0.8,
scale: {
x : 1.4,
y : 1.4
},
rotation: Math.random() * 180,
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {
x : 5,
y : 5
},
shadowOpacity: 0.6,
});
//layer.add(star);
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
function adjustPoint(e){
var p=[circle.getX(), circle.getY(), circleA.getX(), circleA.getY()];
arrow.setPoints(p);
layer.draw();
console.log(group.getX(),group.getY());
console.log(circleA.getX(),circleA.getY());
}
//circle.on('dragmove', adjustPoint);
group.on('dragmove', adjustPoint);
circleA.on('dragmove', adjustPoint);
group.add(star,circle);
//group.add(circle);
layer.add(group);
layer.add(circleA);
// add the shape to the layer
//layer.add(circle);
layer.add(arrow);
//layer.add(star);
// add the layer to the stage
stage.add(layer);
</script>
</body>
</html>
When you put a shape on a group, it's getX() and getY() functions return values relative to the origin point of the group. Your error was to assume that the X, Y position of the circle in the group would change as the group is dragged.
In the working code below, based on your posted code, I changed only the first line of the AdjustPoint() function so that the X, Y position of the circle has added to it the X, Y position of the group.
This fixes your issue.
Tip: as you start using groups be aware that their extent on the page is that of the minimum rectangle needed to contain the group shapes. If you want to specifically control the size and location of a group, add to it a Rect() shape of specific width and height to give a known size to the group.
I also added a call to that function at the end of the code so that the arrow joins when the code initially runs.
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var group = new Konva.Group({
x:120,
y:10,
draggable: true,
});
var circle = new Konva.Circle({
x: stage.getWidth() / 2,
y: 60,
radius: 40,
fill: 'green',
stroke: 'black',
strokeWidth: 2,
});
var circleA = new Konva.Circle({
x: stage.getWidth() / 5,
y: stage.getHeight() / 5,
radius: 30,
fill: 'red',
stroke: 'black',
strokeWidth: 2,
draggable: true
});
var arrow = new Konva.Arrow({
points: [circle.getX(), circle.getY(), circleA.getX(), circleA.getY()],
pointerLength: 10,
pointerWidth: 10,
fill: 'black',
stroke: 'black',
strokeWidth: 4
});
var star = new Konva.Star({
x: stage.getWidth() / 2,
y: 60,
numPoints: 5,
innerRadius: 30,
outerRadius: 50,
fill: '#89b717',
opacity: 0.8,
scale: {
x : 1.4,
y : 1.4
},
rotation: Math.random() * 180,
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {
x : 5,
y : 5
},
shadowOpacity: 0.6,
});
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
function adjustPoint(e){
// changes here
var p=[ group.getX() + circle.getX(), group.getY() + circle.getY(), circleA.getX(), circleA.getY()];
// changes here
arrow.setPoints(p);
layer.draw();
stage.draw();
// console.log('group = ' + group.getX() + ', ' + group.getY());
// console.log('red circle = ' + circleA.getX() + ', ' + circleA.getY());
}
//circle.on('dragmove', adjustPoint);
group.on('dragmove', adjustPoint);
circleA.on('dragmove', adjustPoint);
group.add(star,circle);
//group.add(circle);
layer.add(group);
layer.add(circleA);
// add the shape to the layer
//layer.add(circle);
layer.add(arrow);
//layer.add(star);
// add the layer to the stage
stage.add(layer);
// changes here
adjustPoint();
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
<script src="https://cdn.rawgit.com/konvajs/konva/0.13.0/konva.min.js"></script>
<body>
<div id="container"></div>
</body>

Categories