I am working on a canvas application which needs to be replicated in IOS and Android. Hence the goal is whatever is created using the web must be replicated in the other platforms and vise versa.
In the web I am using Fabric JS as my canvas library and I am using the build in JSON method to save and recreate the canvas, where as the IOS developer is using a point based system where he is saving the x , y cordinates for all the objects and lines drawn in the canvas. The IOS developer is diving the x cordinate with the width and the y cordinate with the height of the canvas to make the object and lines positions propotinate in all the devices.
I was able to convert my web based canvas to their desired point based system. But now the challenge is to convert their point based system into a fabric based web canvas. Dealing with the objects would be easy to handle but I need some assitence as to how I can recreate the path's from the Comma Seperated values in the Database.
Here is an example of a Path saved in the database using the IOS app.
[
"{0.88156813, 0.67090207}",
"{0.86200052, 0.67090207}",
"{0.83264917, 0.67908657}",
"{0.81308156, 0.67908657}",
"{0.77883828, 0.68727112}",
"{0.75437874, 0.68727112}",
"{0.72013545, 0.68727112}",
"{0.69567597, 0.68727112}",
"{0.67121649, 0.69545561}",
"{0.65164888, 0.69545561}",
"{0.63208127, 0.70364016}",
"{0.61251366, 0.70364016}",
"{0.59294611, 0.72000921}",
"{0.58316231, 0.7281937}",
"{0.58316231, 0.73637825}"
]
How do I go about to recreate the path from the given data where the first value is x cordinate and the second value is the y cordinate.
Any guidence in this matter would be very helpful.
Regards
Something like this, I've took the width and hight of the canvas as max value =1, in that way pair x,y is a percent from width or height.
If you have coordinate with minus value in interval [-1, 1] use:
points.push({
x : (width\2 * coordinates[i].x),
y : (height\2 * coordinates[i].y)
});
and at line drawing add offset:
canvas.add(new fabric.Line([x1, y1, x2, y2], {
left: width\2,
top: height\2,
stroke: 'red'
}));
Response when considered possible path values are between [0,1]:
var coordinates = [
{x : 0.2, y: 0.2},
{x : 0.88156813, y: 0.67090207},
{x: 0.86200052, y: 0.67090207},
{x: 0.83264917, y: 0.67908657},
{x: 0.81308156, y: 0.67908657},
{x: 0.77883828, y: 0.68727112},
{x: 0.75437874, y: 0.68727112},
{x: 0.72013545, y: 0.68727112},
{x: 0.69567597, y: 0.68727112},
{x: 0.67121649, y: 0.69545561},
{x: 0.65164888, y: 0.69545561},
{x: 0.63208127, y: 0.70364016},
{x: 0.61251366, y: 0.70364016},
{x: 0.59294611, y: 0.72000921},
{x: 0.58316231, y: 0.7281937},
{x: 0.58316231, y: 0.73637825}
];
var canvas = new fabric.Canvas('c');
var width = canvas.width;//0.00 to 1.00
var height = canvas.height;//0.00 to 1.00
var points = [];
//define your points from 0 to width and from 0 to height
for(i =0; i < coordinates.length; i++)
{
points.push({
x : (width * coordinates[i].x),
y : (height * coordinates[i].y)
});
}
//drow lines
for(i =0; i < points.length - 1; i++)
{
//alert(points[i].x);
canvas.add(new fabric.Line([points[i].x, points[i].y, points[i+1].x, points[i+1].y], {
stroke: 'blue'
}));
}
canvas.add(new fabric.Text('x=0.00', { left: 20, top: 5, fontSize: 10 }));
canvas.add(new fabric.Text('y=1.00', { left: 360, top: 5, fontSize: 10 }));
canvas.add(new fabric.Text('y=0.00', { left: 5, top: 20, fontSize: 10 }));
canvas.add(new fabric.Text('y=1.00', { left: 5, top: 380, fontSize: 10 }));
canvas {
border-width: 1px;
border-style: solid;
border-color: #000;
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
Related
I have a shape set up through fabric.js and I'm trying to use fill to achieve 2 goals simultaneously:
1) An rgba fill color rgba(255,0,0,0.5)
AND
2) A fabric.Pattern with an SVG image which has transparency.
I'm able to do the fill color and the fill fabric.Pattern with an SVG image separately, but I can't figure out how do do them together
The code I'm using:
var canvas = this.__canvas = new fabric.Canvas('c');
//To set the pattern with an SVG image
function loadPattern(url) {
fabric.util.loadImage(url, function(img) {
shape.set('fill', new fabric.Pattern({
source: img,
repeat: 'repeat'
}));
canvas.renderAll();
});
}
//To set just a color
function setColor(color) {
shape.set('fill', color);
canvas.renderAll();
}
//To set the backgroundColor
function setbackgroundColor(color) {
shape.set('backgroundColor', color);
canvas.renderAll();
}
var shape = new fabric.Polyline([
{x: 78, y: 138},
{x: 146, y: 96},
{x: 170, y: 117},
{x: 275, y: 127},
{x: 275, y: 216},
{x: 78, y: 138}
], {
fill: 'rgba(255,0,0,0.5)',
stroke: 'red',
left: 100,
top: 100
});
canvas.add(shape);
This is what it looks like when using fill with just the color:
This is what it looks like when using fill with just the pattern:
I have tried using fill with the pattern plus ```shape.backgroundColor = 'rgba(255,0,0,0.5)';
But this is what happened:
The goal would be to have both the color and the pattern contained within the shape like this:
// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');
//To set the pattern with an SVG image
function loadPattern(url) {
fabric.util.loadImage(url, function(img) {
shape.set('fill', new fabric.Pattern({
source: img,
repeat: 'repeat'
}));
canvas.renderAll();
});
}
//To set just a color
function setColor(color) {
shape.set('fill', color);
canvas.renderAll();
}
//
function setbackgroundColor(color) {
shape.set('backgroundColor', color);
canvas.renderAll();
}
var shape = new fabric.Polyline([
{x: 78, y: 138},
{x: 146, y: 96},
{x: 170, y: 117},
{x: 275, y: 127},
{x: 275, y: 216},
{x: 78, y: 138}
], {
fill: 'rgba(255,0,0,0.5)',
stroke: 'red',
left: 100,
top: 100
});
canvas.add(shape);
canvas {
border: 1px solid #999;
}
button {
margin-top: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.min.js"></script>
<button onclick="loadPattern('https://www.heropatterns.com/svg/anchors-away.svg');">Set pattern</button>
<button onclick="setColor('rgba(255,0,0,0.5)');">Set color</button>
<button onclick="setbackgroundColor('rgba(255,0,0,0.5)');">Set backgroundColor</button>
<button onclick="setbackgroundColor('transparent');">Set backgroundColor to transparent</button>
<canvas id="c" width="400" height="400"></canvas>
Here is my jsFiddle:
http://jsfiddle.net/shehan11/1vgps2dw/5/
Thanks for your help!
My solution for this ended up being to pass the color information to a PHP file which renders the SVG with the color info.
JS:
var rgb = {
r: 255,
b: 0,
g: 0
};
var url = '/pattern.php?pattern=' + pattern + '&r=' + rgb.r + '&g=' + rgb.g + '&b=' + rgb.b;
fabric.util.loadImage(url, function (img) {
shape.set('fill', new fabric.Pattern({
source: img,
repeat: 'repeat'
}));
shape.pattern = pattern;
canvas.renderAll();
});
PHP FILE:
echo '<svg style="background-color:'."rgba($r,$g,$b,0.5)".';" xmlns="http://www.w3.org/2000/svg" width="70" height="46" viewBox="0 0 70 46"><g fill-rule="evenodd"><g fill="#000"><polygon points="68 44 62 44 62 46 56 46 56 44 5```
In this way, the JS uses PHP to create an SVG image on the fly with color information
I am trying to flip a group (horizontally) using Konvajs.Following the advice of this post, I am using the scaleX property. This works---mostly. However, it doesn't flip around the center.
function reverse(shape){
var layer = shape.getLayer();
var oldScaleX = shape.attrs.scaleX;
var width = shape.getClientRect().width;
var adjuster = oldScaleX * width;
var startX = shape.attrs.x + adjuster;
var startY = shape.attrs.y;
shape.scaleX(-oldScaleX);
shape.position({x: startX, y: startY});
layer.draw();
};
I tried using the offsetX and offsetY properties like in this post, but it doesn't seem to do anything.
shape.offsetX(shape.width()/2);
shape.offsetY(shape.height()/2);
shape.x(shape.x() - shape.attrs.offsetX);
shape.y(shape.y() - shape.attrs.offsetY);
The shapes will flip initially, but if they are rotated first, they tend to jump around.
Codepen
Based on my function posted in this other question, here is a snippet to rotate a shape around its centre. In this case the shape is a group composed of 2 rects but any shape will work.
// Set up the canvas / stage
var stage = new Konva.Stage({container: 'container', width: 600, height: 200});
var layer = new Konva.Layer({draggable: false});
stage.add(layer);
var shape = new Konva.Group({x: 80, y: 40, width: 60, height: 60 });
var r1 = new Konva.Rect({ x:10, y:10, width: 70, height: 40, fill: 'cyan'})
var r2 = new Konva.Rect({ x:50, y:10, width: 40, height: 100, fill: 'cyan'})
shape.add(r1)
shape.add(r2)
layer.add(shape)
// set the group rotate point. Note - x,y is relative to top-left of stage !
var pos = shape.getClientRect();
RotatePoint(shape, {x: pos.x + pos.width/2, y: pos.y + pos.height/2});
stage.draw();
// This is where the flip happens
var scaleX = 1, inc = -0.2; // set inc = 1 for full flip in one call
function flipShape(){
scaleX = scaleX + inc;
shape.scaleX(scaleX); // and that's it
// fun with colors on front and back of shape
r1.fill(scaleX < 0 ? 'magenta' : 'cyan');
r2.fill(scaleX < 0 ? 'magenta' : 'cyan');
$('#info').html('Set ScaleX(' + scaleX.toFixed(2) + ')'); // What's happening?
layer.draw(); // show the change
inc = (scaleX % 1 === 0 ? -1 * inc : inc); // decide next increment, reversed at 1 & -1
}
$('#flip').on('click', function(e){
flipShape(); // click button - flip shape a bit
})
/*
Set the offset for rotation to the given location and re-position the shape
*/
function RotatePoint(shape, pos){ // where pos = {x: xpos, y: yPos}
var initialPos = shape.getAbsolutePosition();
var moveBy = {x: pos.x - initialPos.x, y: pos.y - initialPos.y};
// offset is relative to initial x,y of shape, so deduct x,y.
shape.offsetX(moveBy.x);
shape.offsetY(moveBy.y);
// reposition x,y because changing offset moves it.
shape.x(initialPos.x + moveBy.x);
shape.y(initialPos.y + moveBy.y);
}
<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>
<p>Clck the button to progressively flip a shape using scaleX <button id='flip'>Flip a bit</button> <span id='info'></span></p>
<div id='container' style="position: absolute; top: 0; z-index: -1; display: inline-block; width: 600px, height: 200px; background-color: silver; "></div>
I am using Fabric.js and I have the following code for drawing a hexagon:
makeObject(originPoint, newPoint, canvasObject, properties) {
let width = Math.abs(newPoint.x - originPoint.x);
let height = 0;
if(this.shouldKeepProportion){
height=width;
}else{
height=Math.abs(newPoint.y - originPoint.y);
}
width = (this.minWidth!=null && width<this.minWidth ? this.minWidth: width);
height = (this.minHeight!=null && height<this.minHeight ? this.minHeight : height);
let sweep=Math.PI*2/6;
let points=[];
//generate points for 6 angles
for(let i=0;i<6;i++){
let x=width*Math.cos(i*sweep);
let y=height*Math.sin(i*sweep);
points.push({x:x/2,y:y/1.75});
}
properties = {
objectType: 'octagon',
left: originPoint.x,
top: originPoint.y,
originX: 'left',
originY: 'top',
fill: 'rgba(0, 0, 0, 1.0)',
width: width,
height: height,
originX: originPoint.x > newPoint.x ? 'right' : 'left',
originY: originPoint.y > newPoint.y ? 'bottom' : 'top',
flipX: originPoint.x > newPoint.x,
stroke: 'rgba(0, 0, 0, 1.0)',
strokeWidth: 1,
strokeLineJoin: 'round',
...properties
};
return new fabric.fabric.Polygon(points, properties);
}
What I want to get is a regular octagon, same as the hexagon. If i try to change the number of corners/angles, I am getting the following type of octagon:
What I actually need is this:
PLEASE NOTE: I do not need it rotated or flipped or something like that, I need it drawn like on the picture.
Thank you for the help!
EDIT: Working JSFiddle: https://jsfiddle.net/42fb716n/
This should do it, using the Polygon() function instead of Line() so it's like a single object, not a group of objects, and so it works in your drawing App as you have different properties for single and group objects:
var canvas = new fabric.Canvas('canvas');
var size = 150;
var side = Math.round((size * Math.sqrt(2)) / (2 + Math.sqrt(2)));
var octagon = new fabric.Polygon([
{x:-side / 2, y:size / 2},
{x:side / 2, y:size / 2},
{x:size / 2, y:side / 2},
{x:size / 2, y:-side / 2},
{x:side / 2, y:-size / 2},
{x:-side / 2, y:-size / 2},
{x:-size / 2, y:-side / 2},
{x:-size / 2, y:side / 2}], {
stroke: 'red',
left: 10,
top: 10,
strokeWidth: 2,
strokeLineJoin: 'bevil'
},false);
canvas.add(octagon);
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.0/fabric.min.js"></script>
<canvas id="canvas" width=200 height=200></canvas>
Let me know if you need anything else. Happy to help!
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 am writing code to maintain same strokeWidth across all the objects in Fabricjs.
I am able to successfully maintain strokeWidth for normal Objects , let it be rectangle or ellipse(I wrote code to maintain both).
But when I use group , I am unable to maintain the strokeWidth.
Here's the fiddle.
Steps to reproduce the issue ?
1)Open above fiddle link
2)resize the circle , check the size of border (thickness of border remains the same on re-sizing)
3)Now resize the rectangle and text(group) , expected result is thickness of border must be same throughout but that doesn't happens.
Code :
`
var gCanvas = new fabric.Canvas('canvDraw');
gCanvas.setWidth(700);
gCanvas.setHeight(700);
var text = new fabric.Text('test', { fontSize: 30, top: 100, left : 100, fill : 'red'});
// add some shapes - these are examples, others should work too
var myRectangle = new fabric.Rect({
left: 100,
top: 100,
fill: '#999999',
width: 120,
height: 120,
strokeWidth: 3,
fill: '',
stroke: '#000000'
});
var group = new fabric.Group([ myRectangle, text ], {borderColor: 'black', cornerColor: 'green', lockScalingFlip : true});
gCanvas.add(group);
var myEllipse = new fabric.Ellipse({
top: 250,
left: 100,
rx: 75,
ry: 50,
fill: '',
stroke: '#000000',
strokeWidth: 3
});
gCanvas.add(myEllipse);
gCanvas.observe('object:scaling', function (e) {
e.target.resizeToScale();
});
fabric.Object.prototype.resizeToScale = function () {
// resizes an object that has been scaled (e.g. by manipulating the handles), setting scale to 1 and recalculating bounding box where necessary
switch (this.type) {
case "ellipse":
this.rx *= this.scaleX;
this.ry *= this.scaleY;
this.width = this.rx * 2;
this.height = this.ry * 2;
this.scaleX = 1;
this.scaleY = 1;
break;
case "rect":
this.width *= this.scaleX;
this.height *= this.scaleY;
this.scaleX = 1;
this.scaleY = 1;
default:
break;
}
}
`
this approach will not work for all kind of shapes. when facing text or polygons you cannot recalculate everything.
you can do something like that:
fabric.Object.prototype.resizeToScale = function () {
if (this.type !=='group') {
this.strokeWidth = this._origStrokeWidth / Math.max(this.scaleX, this.scaleY);
}
else {
this._objects.forEach( function(obj){
console.log(obj);
obj.strokeWidth = obj._origStrokeWidth / Math.max(obj.group.scaleX, obj.group.scaleY);
});
}
}
if you prefer the effect of your solution (border is always uniform) put this line of code in the default case of your switch construct to handle all the types for wich you cannot find a resizing solution.
edit i added group handling.
The point is to make smaller strokewidth when the group / object scale.
Because when you will have polygons and polylines and paths, changing width / height /radius will not help.
http://jsfiddle.net/y344vs5s/4/