Add free drawing result on another canvas in fabricjs - javascript

There are two canvas instance, the smaller 'd' for free drawing, the bigger 'c' is to be added on. How can I only add the boundary of drawing on 'd' to 'c', not the entire 'd' canvas area with lots of empty area to 'c'?
Fiddle
I hope the code can explains more clear. thanks.
HTML
<canvas width="300" height="300" id="c"></canvas>
<canvas width="150" height="150" id="d"></canvas>
<button id="btn">add To c</button>
JavaScript
var c = new fabric.Canvas('c')
var d = new fabric.Canvas('d', {
isDrawingMode: true
})
document.getElementById('btn').onclick = addDrawToBig
function addDrawToBig(){
var svg = d.toSVG()
fabric.loadSVGFromString(svg,function(objects, options) {
var obj = fabric.util.groupSVGElements(objects, options);
c.add(obj).centerObject(obj).setActiveObject(obj);
});
}
var circle = new fabric.Circle({
stroke: 'red',
radius: 15,
left: 15,
top: 15
})
d.add(circle)
d.renderAll()
addDrawToBig()

finally i find the answer to my question, just use fabric.Group and .clone() method:
function addDrawToBig(){
var group = [ ]
d.getObjects().forEach(function(path){
path.clone(function(copy){
group.push(copy);
});
});
group = new fabric.Group( group )
c.add(group).centerObject(group).setActiveObject(group);
}
fiddle here
Special thanks to #AndreaBogazzi, wish a good day!

i updated your fiddle with something that works better.
Fiddle
Canvas.toSVG() is not meant to be used as a export import function.
I suggest you to cycle throught the canvas objects and clone all of them in the new canvas.
This demo is not really cloning them, just copying them on the other canvas.
Without cloning you have the side effect that if you modify something in one canvas the modification get mirrored in the other.
You should add
c.add(ao[i].clone());
But for some reason free drawing path .clone() is failing. ( will look in this next days to see if it is a bug or a misusage )
var c = new fabric.Canvas('c')
var d = new fabric.Canvas('d', {
isDrawingMode: true
})
document.getElementById('btn').onclick = addDrawToBig
function addDrawToBig(){
var ao = d.getObjects();
console.log(ao);
for(var i in ao) {
c.add(ao[i]);
}
}
var circle = new fabric.Circle({
stroke: 'red',
radius: 15,
left: 15,
top: 15
})
d.add(circle)
d.renderAll()
addDrawToBig()
Hope it helps.

Related

Draw a textbox on canvas that contains an image and some drawing over it using fabric js

I am trying to draw a textbox on canvas that contains an image and some drawing over it. now I am trying to draw a textbox over it.
When I am trying to initialize canvas object using fabricJS it's making canvas empty first. like..
var canvas = new fabric.Canvas('mycanvas')
after this code, it makes canvas empty. but if I initialize this code in the very beginning just after canvas initialized and then I try to add a textbox to that canvas object like...
var text = new fabric.Textbox('A Computer Science Portal',
{
width: 450,
height: 10,
top: 100,
left: 100,
});
this.canvas.add(text);
Then the textbox is not visible. I think it went in the background of the image.
I have already written lots of code for drawing over the canvas not want to draw textbox on the canvas after with all existing drawing and image that is developed in javascript only.
On Fabric you have bringToFront use that to keep the text on top.
Here is the official documentation:
http://fabricjs.com/docs/fabric.Canvas.html#bringToFront
Sample code:
var image;
var canvas = new fabric.Canvas('canvas');
var url = "http://swagger-net-test.azurewebsites.net/api/SvgImage?"
var text = new fabric.Textbox('TEST', { width: 90, height: 10, top: 30, left: 50 });
this.canvas.add(text);
function loadSVG(url) {
fabric.loadSVGFromURL(url, function(objects, options) {
if (image) canvas.remove(image);
image = fabric.util.groupSVGElements(objects, options);
image.name = 'sticker';
canvas.add(image);
image.scaleToHeight(100);
image.center();
image.setCoords();
canvas.renderAll();
canvas.bringToFront(text)
});
}
loadSVG(url + "color=red")
setTimeout(function() {
loadSVG(url + "color=blue")
}, 3000);
<canvas id="canvas" width="200" height="200"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.4/fabric.min.js"></script>

Access SVG attributes using Fabric.js

I'd like to load a SVG image into canvas using Fabric.js and have access to all attributes of it.
Let's assume I have an image like this:
SVG Image
I tried to it using Fabric.js on 3 ways:
1. (Saved as String) - this method is not loading my image, I took other SVG and attributes can be accessed with svgImg.fill.color property.
fabric.loadSVGFromString(svg, function (objects, options) {
var svgImg = fabric.util.groupSVGElements(objects, options);
canvas.add(svgImg);
});
2. Directly from URL:
fabric.loadSVGFromURL(URLsvg, function (objects, options) {
var svgImg = fabric.util.groupSVGElements(objects, options);
canvas.add(svgImg);
});
3.Using fabric.Image:
<img id="Test_Img" src="~/SVG/homer-simpson.svg" style="display: none;" />
var Img = document.getElementById('Test_Img');
var imgInstance = new fabric.Image(Img, {
left: 90,
top: 80
});
How can I access inner attributes of my SVG and change for example color of Homers T-Shirt?
Loaded SVG objects become groups in fabric.js (if they are not simple geometric forms like a rectangle, circle, etc.).
You can access the members of the group with getObjects(), which returns an array.
Here is a simple example. It reads an SVG consisting of 3 arcs and changes the color of the first arc to blue:
var canvas = new fabric.Canvas('canvas', { preserveObjectStacking: true });
canvas.backgroundColor="#fff";
canvas.renderAll();
fabric.loadSVGFromURL("https://media-public.canva.com/MABtmZU4zXQ/1/screen.svg",function(objects,options) {
var loadedObjects = fabric.util.groupSVGElements(objects, options);
loadedObjects.set({
width: 300,
height: 150
});
canvas.add(loadedObjects);
// Change the first arc to blue
loadedObjects.getObjects()[0].set({fill: "blue"});
canvas.renderAll();
});
It runs with fabric.js Version 3.5.
Working example: https://jsfiddle.net/awehring/k7azLb84/9/

Adding canvas inside another canvas: obj.setCoords is not a function( fabric js)

Started using fabric.js and trying to add a canvas inside another canvas, so that the top canvas stays constant and I'll add objects to inner canvas.
Here is the snippet of adding a canvas to another canvas.
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
canvas.add(innerCanvas);
and my html looks like this
<canvas id="artcanvas" width="500" height="500"></canvas>
<canvas id="innerCanvas" width="200" height="200" ></canvas>
Once adding these successfully, what I am going to do is , add the coordinates to the inner canvas, so that it looks like one on another to the end user.
However, ran into the below error for the tried code
Uncaught TypeError: obj.setCoords is not a function
at klass._onObjectAdded (fabric.js:6894)
at klass.add (fabric.js:231)
at main.js:60
at fabric.js:19435
at HTMLImageElement.fabric.util.loadImage.img.onload (fabric.js:754)
_onObjectAdded # fabric.js:6894
add # fabric.js:231
(anonymous) # main.js:60
(anonymous) # fabric.js:19435
fabric.util.loadImage.img.onload # fabric.js:754
Looking at the error message, just went to the line of error and here is what I found in chrome console
Can someone point the mistake in my codes ?
After going through no.of discussions and internet solutions, for time being I am using Fabric Rectangle as a clipper and setting it's boundaries so user can be able to drop/play with in that particular clipper.
Dotted red(image below) is my clipper and now I can bound the dropping and below is the code to add an image with a clipper.
function addImageToCanvas(imgSrc) {
fabric.Object.prototype.transparentCorners = false;
fabric.Image.fromURL(imgSrc, function(myImg) {
var img1 = myImg.set({
left: 20,
top: 20,
width: 460,
height: 460
});
img1.selectable = false;
canvas.add(img1);
var clipRectangle = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 150,
top: 150,
width: 200,
height: 200,
fill: 'transparent',
/* use transparent for no fill */
strokeDashArray: [10, 10],
stroke: 'red',
selectable: false
});
clipRectangle.set({
clipFor: 'layer'
});
canvas.add(clipRectangle);
});
}
Now while appending any image/layer to the canvas, I bind that image/layer/text to the clipper I created.
function addLayerToCanvas(laImg) {
var height = $(laImg).height();
var width = $(laImg).width();
var clickedImage = new Image();
clickedImage.onload = function(img) {
var pug = new fabric.Image(clickedImage, {
width: width,
height: height,
left: 150,
top: 150,
clipName: 'layer',
clipTo: function(ctx) {
return _.bind(clipByName, pug)(ctx)
}
});
canvas.add(pug);
};
clickedImage.src = $(laImg).attr("src");
}
And the looks like, after restriction of bounds,
Here is the fiddle I have created with some static image url.
https://jsfiddle.net/sureshatta/yxuoav39/
So I am staying with this solution for now and I really feel like this is hacky and dirty. Looking for some other clean solutions.
As far as I know you can't add a canvas to another canvas - you're getting that error as it tries to call setCoords() on the object you've added, but in this case it's another canvas and fabric.Canvas doesn't contain that method (see docs). I think a better approach would be to have two canvases and position them relatively using CSS - see this simple fiddle
HTML
<div class="parent">
<div class="artcanvas">
<canvas id="artcanvas" width="500" height="500"></canvas>
</div>
<div class="innerCanvas">
<canvas id="innerCanvas" width="200" height="200" ></canvas>
</div>
</div>
CSS
.parent {
position: relative;
background: black;
}
.artcanvas {
position: absolute;
left: 0;
top: 0;
}
.innerCanvas {
position: absolute;
left: 150px;
top: 150px;
}
JS
$(document).ready(function() {
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
var rect = new fabric.Rect({
fill: 'grey',
width: 500,
height: 500
});
canvas.add(rect);
var rect2 = new fabric.Rect({
fill: 'green',
width: 200,
height: 200
});
innerCanvas.add(rect2);
})
To handle the object serialization, you can do something like this:
var innerObjs = innerCanvas.toObject();
console.dir(innerObjs);
var outerObjs = canvas.toObject();
innerObjs.objects.forEach(function (obj) {
obj.left += leftOffset; // offset of inner canvas
obj.top += topOffset;
outerObjs.objects.push(obj);
});
var json = JSON.stringify(outerObjs);
This will then give you the JSON for all objects on both canvases
I have no understanding why you want to do this thing, but to put a canvas inside another canvas, you have one simple way:
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
imageContainer = new fabric.Image(innerCanvas.lowerCanvasEl);
canvas.add(imageContainer);
Then depending what you want to do, you may need additional tweaks, but this should work out of the box.
Don't create a canvas
Most objects in fabric (from my limited experience) are at some point converted to a canvas. Creating an additional fabric canvas to manage a group of objects is kind of pointless as you are just adding overhead and mimicking fabrics built in groups object.
Fabric objects are basically DOM canvases wrappers.
The following example shows how fabric uses a canvas to store the content of a group. The demo creates a group and adds it to the fabric canvas, then gets the groups canvas and adds it to the DOM. Clicking on the group's canvas will add a circle. Note how it grows to accommodate the new circles.
const radius = 50;
const fill = "#0F0";
var pos = 60;
const canvas = new fabric.Canvas('fCanvas');
// create a fabric group and add two circles
const group = new fabric.Group([
new fabric.Circle({radius, top : 5, fill, left : 20 }),
new fabric.Circle({radius, top : 5, fill, left : 120 })
], { left: 0, top: 0 });
// add group to the fabric canvas;
canvas.add(group);
// get the groups canvas and add it to the DOM
document.body.appendChild(group._cacheContext.canvas);
// add event listener to add circles to the group
group._cacheContext.canvas.addEventListener("click",(e)=>{
group.addWithUpdate(
new fabric.Circle({radius, top : pos, fill : "blue", left : 60 })
);
canvas.renderAll();
pos += 60;
});
canvas {
border : 2px solid black;
}
div {
margin : 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js"></script>
<div>Fabric's canvas "canvas = new fabric.Canvas('fCanvas');"</div>
<canvas id="fCanvas" width="256" height="140"></canvas>
<div>Fabric group canvas below. Click on it to add a circle.</div>
Use a group rather than a new instance of a fabric canvas.
As you can see a canvas is generated for you. Adding another fabric canvas (Note that a fabric canvas is not the same as a DOM canvas) will only add more work for fabric to do, which already has a lot of work to do.
You are best of to use a group and have that hold the content of the other fabric object you wish to shadow. That would also contain its content in a group.
Just an image
And just a side not, a DOM canvas is an image and can be used by fabric just as any other image. It is sometimes better to do the rendering directly to the canvas rather than via fabric so you can avoid rendering overheads that fabric needs to operate.
To add a DOM canvas to fabric just add it as an image. The border and text are not fabric object, and apart from the code to render them take up no memory, and no additional CPU overhead that would be incurred if you used a fabric canvas and objects.
const canvas = new fabric.Canvas('fCanvas');
// create a standard DOM canvas
const myImage = document.createElement("canvas");
// size it add borders and text
myImage.width = myImage.height = 256;
const ctx = myImage.getContext("2d");
ctx.fillRect(0,0,256,256);
ctx.fillStyle = "white";
ctx.fillRect(4,4,248,248);
ctx.fillStyle = "black";
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("The DOM canvas!",128,128);
// use the canvas to create a fabric image and add it to fabrics canvas.
canvas.add( new fabric.Image(myImage, {
left: (400 - 256) / 2,
top: (400 - 256) / 2,
}));
canvas {
border : 2px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js"></script>
<canvas id="fCanvas" width="400" height="400"></canvas>
innerCanvas.setCoords(); ìs a function, but you need it only after you set the coordinates. Or more precise, set these four elements:
innerCanvas.scaleX = 1;
innerCanvas.scaleY = 1;
innerCanvas.left = 150;
innerCanvas.top = 150;
innerCanvas.setCoords();
canvas.renderAll();

draw images in a layer and align them as a matrix

How can I make the images that I call in a for loop in KineticJS to be aligned in a kind of matrix?
The behavior that I want to have is to draw them four by four. I don't know their exact number, since I'm using a database to call their paths, so I just use a for loop.
EDIT
I changed the code sample with a fiddle to make things simpler : http://jsfiddle.net/tg8TM/
So I did this test
imageObj.onload = function() {
var ligne = 1;
for (var i=0; i<10; i++) {
var img = new Kinetic.Image({
x: 100*i,
y: 50,
image:imageObj,
width: 100,
height: 100
});
if (img.getX()>stage.getWidth()){ //maybe I should use if (i%4 ===0)
ligne++;
img.setX(100* ligne+1);
img.setY(50 * (ligne+1) );
layer.add(img);
layer.draw();
}
else {
layer.add(img);
layer.draw();
}
};
}
Here, the images in the end of the stage should be set to another x,y to be drawn in another "line" (like \n). Instead, the output now is diagonal images. Please help me to figure out how to make it. Did I miss something?
You can draw your imageObj in rows/cols within the stage like this:
(untested code alert!)
var w=imageObj.width;
var h=imageObj.height;
var nextX=0;
var nextY=0;
for(var i=0;i<10;i++){
var img=new Kinetic.Image({
x:nextX,
y:nextY,
...
});
nextX+=w;
if(nextX+w>stage.getWidth()){
nextX=0;
nextY+=h;
}
}

Transform (Move/Scale/Rotate) shapes with KineticJS

I'm trying to build a transform manager for KineticJS that would build a bounding box and allow users to scale, move, and rotate an image on their canvas. I'm getting tripped up with the logic for the anchor points.
http://jsfiddle.net/mharrisn/whK2M/
I just want to allow a user to scale their image proportionally from any corner, and also rotate as the hold-drag an anchor point.
Can anyone help point me in the right direction?
Thank you!
Here is a proof of concept of a rotational control I've made:
http://codepen.io/ArtemGr/pen/ociAD
While the control is dragged around, the dragBoundFunc is used to rotate the content alongside it:
controlGroup.setDragBoundFunc (function (pos) {
var groupPos = group.getPosition()
var rotation = degrees (angle (groupPos.x, groupPos.y, pos.x, pos.y))
status.setText ('x: ' + pos.x + '; y: ' + pos.y + '; rotation: ' + rotation); layer.draw()
group.setRotationDeg (rotation); layer.draw()
return pos
})
I am doing the same thing, and I've posted a question which is allmoast the same, but I found a link where you have the resize and move tool ready developed. So I have used the same. It does not contain the rotate tool however, but this can be a good start for you too, it is very simple and logical. Here is the link: http://www.html5canvastutorials.com/labs/html5-canvas-drag-and-drop-resize-and-invert-images/
I will come back with the rotation tool as well if I manage to get it working perfectly.
I hope I am not late yet for posting this code snippet that I made. I had the same problem with you guys dealing with this kind of task. Its been 3 days since I tried so many workarounds to mimic the fabricjs framework capability when dealing with images and objects. I could use Fabricjs though but it seems that Kineticjs is more faster/consistent to deal with html5.
Luckily, we already have existing plugin/tool that we could easily implement together with kineticjs and this is jQuery Transform tool. SUPER THANKS TO THE AUTHOR OF THIS! Just search this on google and download it.
I hope the code below that I created would help lots of developers out there who is pulling their hair off to solve this kind of assignment.
$(function() {
//Declare components STAGE, LAYER and TEXT
var _stage = null;
var _layer = null;
var simpleText = null;
_stage = new Kinetic.Stage({
container: 'canvas',
width: 640,
height: 480
});
_layer = new Kinetic.Layer();
simpleText = new Kinetic.Text({
x: 60,
y: 55,
text: 'Simple Text',
fontSize: 30,
fontFamily: 'Calbiri',
draggable: false,
name:'objectInCanvas',
id:'objectCanvas',
fill: 'green'
});
//ADD LAYER AND TEXT ON STAGE
_layer.add(simpleText);
_stage.add(_layer);
_stage.draw();
//Add onclick event listener to the Stage to remove and add transform tool to the object
_stage.on('click', function(evt) {
//Remove all objects' transform tool inside the stage
removeTransformToolSelection();
// get the shape that was clicked on
ishape = evt.targetNode;
//Add and show again the transform tool to the selected object and update the stage layer
$(ishape).transformTool('show');
ishape.getParent().moveToTop();
_layer.draw();
});
function removeTransformToolSelection(){
//Search all objects inside the stage or layer who has the name of "objectInCanvas" using jQuery iterator and hide the transform tool.
$.each(_stage.find('.objectInCanvas'), function( i, child ) {
$(child).transformTool('hide');
});
}
//Event listener/Callback when selecting image using file upload element
function handleFileSelect(evt) {
//Remove all objects' transform tool inside the stage
removeTransformToolSelection();
//Create image object for selected file
var imageObj = new Image();
imageObj.onload = function() {
var myImage = new Kinetic.Image({
x: 0,
y: 0,
image: imageObj,
name:'objectInCanvas',
draggable:false,
id:'id_'
});
//Add to layer and add transform tool
_layer.add(myImage);
$(myImage).transformTool();
_layer.draw();
}
//Adding source to Image object.
var f = document.getElementById('files').files[0];
var name = f.name;
var url = window.URL;
var src = url.createObjectURL(f);
imageObj.src = src;
}
//Attach event listener to FILE element
document.getElementById('files').addEventListener('change', handleFileSelect, false);
});

Categories