Capture SVG and rescale to miniature - javascript

I've got a small application written with Raphael.js that paints a node network with SVG and rearranges it based on selections given.
What I need to be able to do is capture the svg picture I've drawn and display it on a "mini-map" type of display on the bottom of the screen. Since I have no idea what the canvas will look like when I capture it, I want to know if it's possible to just capture it as a bitmap or something similar?
Put together a quick fiddle to show close to what I mean:
http://jsfiddle.net/WNnCH/1/
Is there some way to do something like: SVG.copy? or SVG.img? (I'm sure there's not, but you know what I mean?)
I've looked in to how I could just capture the whole SVG and resize it (which is more code than I think I'd like to write or is necessary), or even copy an entire SVG, but I don't know if it's possible to capture an svg as an image and scale it down to what I need.
The minimap doesn't need to be a SVG, it can just be an image, because it's basically just a history of what the screen looked like on a prior action.
Thanks for all suggestions!

You can easily make thumbnails of a primary SVG by creating a different scaled-down SVG that points the first one.
<div id="main">
<svg id="mainsvg" viewBox="0 0 1000 1000">
<rect x="100" y="100" width="500" height="500" fill="green"
transform="rotate(10,350,350)"/>
<rect x="400" y="400" width="500" height="500" fill="orange"
transform="rotate(-10,650,650)"/>
</svg>
</div>
<div id="thumb">
<svg xmlns:xlink="http://www.w3.org/1999/xlink">
<use xlink:href="#mainsvg" />
</svg>
</div>
The SVG sizes are determined by CSS:
#main {
width: 400px;
height: 400px;
}
#thumb {
width: 80px;
height: 80px;
}
You can do anything you want to the main SVG and the thumb will reflect all the changes.
Demo here

There is a way to do this using just Raphael, creating a set with your minimap elements and then cloning it. However it's more tricky since you want the minimap to be a separate entity/paper. I borrowed some code from this answer to clone your map to a new paper and it works well (jsfiddle)
(function (R) {
var cloneSet; // to cache set cloning function for optimisation
/**
* Clones Raphael element from one paper to another
*
* #param {Paper} targetPaper is the paper to which this element
* has to be cloned
*
* #return RaphaelElement
*/
R.el.cloneToPaper = function (targetPaper) {
return (!this.removed &&
targetPaper[this.type]().attr(this.attr()));
};
/**
* Clones Raphael Set from one paper to another
*
* #param {Paper} targetPaper is the paper to which this element
* has to be cloned
*
* #return RaphaelSet
*/
R.st.cloneToPaper = function (targetPaper) {
targetPaper.setStart();
this.forEach(cloneSet || (cloneSet = function (el) {
el.cloneToPaper(targetPaper);
}));
return targetPaper.setFinish();
};
}(Raphael));
var paper = Raphael(30, 30, 200, 200);
var circle = paper.circle(30, 30, 30);
circle.attr("fill", "#ccc");
var circle1 = paper.circle(70, 70, 30);
circle1.attr("fill", "#ccc");
var circle2 = paper.circle(110, 30, 30);
circle2.attr("fill", "#ccc");
var circle3 = paper.circle(30, 110, 30);
circle3.attr("fill", "#ccc");
var circle4 = paper.circle(110, 110, 30);
circle4.attr("fill", "#ccc");
var s = paper.set();
s.push(circle, circle1, circle2, circle3, circle4);
var minipaper = Raphael(document.getElementById("minimap").getElementsByTagName("div")[1], 140, 140);
var miniset = s.cloneToPaper(minipaper);
miniset.transform("s0.5,0.5,55,55"); // scale by 0.5 around (55,55)

Related

Check if point is inside of shape drawn by a complex Path2D

I want to check if a given point is inside of the space drawn inside of a irregular path - inside of the brain (the path is below).
I don't seem to be able to use CanvasRenderingContext2D.isPointInPath(path, x, y) because it only returns true if the point is literally inside the path (the white outline).
I also don't think I can use the odd polygon rule, given that it is possible for a point not to be in the edge and its line still hit the shape wall an even number of times...
As you're working with a SVG, here's a workaround which doesn't involve any abstract calculations.
(1)
Make the inside of your shape, thus the area you want to detect a color completely different from the rest of the shape or any other visuals. In your case for example it would be nothing (or black) to red.
This is controlled by the svg's fill attribute, which takes a hex #ff0000 or a rgb value rgb(255,0,0). Well for reasons that will be important later we make it a rgba(255,0,0,1) value though it ignores the alpha value.
As we don't want the fill to be visible, we also need to set the fill-opacity value to 0.005. This is the lowest value possible and equals the CanvasRenderingContext2D value of 1 in a range from 0-255.
(2)
Now we need to turn the svg into an Image object that can be drawn onto a canvas. This can be done using the following lines:
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let img = new Image();
img.onload = function(e) {
ctx.drawImage(e.currentTarget, canvas.width / 2 - 50, canvas.height / 2 - 50, 100, 100);
}
img.src = 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(svg);
svg is just a string representation of your svg's data.
(3)
The final step involves getting the pixel color at a specific position on the canvas. For this we utilize the .getImageData(x, y, width, height) method, which returns an object consisting of a Uint8ClampedArray which holds four values per pixel. If we set both width and height to 1 we get exactly the four color components for a single pixel - red, green, blue and alpha.
Now we simply compare the color with the red we've used in step (1) and if it's equal, we know it's inside the shape.
Here's a working example (hover your mouse over the image):
let hitColor = 'rgba(255,0,0,1)';
let svg = `<?xml version="1.0" encoding="iso-8859-1"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 511.989 511.989" style="enable-background:new 0 0 511.989 511.989;" xml:space="preserve">
<path style="fill:${hitColor};fill-opacity:0.005" d="M489.333,255.088l-21.344-17.625l8-34.671l4-18.812l-30.656-42.141l6.656-22.53l-8-38.483
l-30.655-30.077h-39.999c0,0-22.655,12.312-16,0c6.672-12.328-33.326-36.577-33.326-36.577h-17.328l-40.663,15.483l-9.602,7.828
l-31.062-23.702h-30.672l-23.999,0.391l-20.929,23.312L150.41,50.75h-22.383l-23.998,10.654L66.694,73.295l-6.664,29.358
l-5.336,42.577l-19.999,22.891v26.516l12.397,37.623l-12.397,16.547l-18.664,22.672v41.326l18.664,29.984v36.67l23.999,55.999
c0,0,35.999,18.672,41.335,21.327c5.328,2.672,14.664,0,14.664,0l29.327,32l27.999,9.328h21.336l42.663-18.656l24.397-18.672
l10.664,24l42.257,13.328h21.344l31.998-6.656l21.719-38.671l11.609,3.999l38.67-7.999l28-28.327l10.656-46.327v-31.343
l17.718-20.156l10.281-35.171L489.333,255.088z"/>
<path style="fill:#ffffff;" d="M511.973,294.009c0-24.546-12.891-46.093-32.28-58.218c7.984-11.203,12.672-24.905,12.672-39.701
c0-22.203-10.562-41.938-26.938-54.453c2.625-7.655,4.078-15.858,4.078-24.42c0-41.281-33.484-74.749-74.764-74.749
c-8.344,0-16.375,1.375-23.875,3.906C363.21,19.594,338.554,0,309.321,0c-22.266,0-41.858,11.359-53.327,28.608
C244.525,11.359,224.925,0,202.667,0c-29.233,0-53.889,19.594-61.537,46.374c-7.5-2.531-15.531-3.906-23.89-3.906
c-41.288,0-74.757,33.468-74.757,74.749c0,8.562,1.445,16.765,4.086,24.42c-16.382,12.516-26.952,32.25-26.952,54.453
c0,14.796,4.695,28.498,12.672,39.701c-19.383,12.125-32.273,33.672-32.273,58.218c0,21.327,9.727,40.374,24.984,52.952
c-2.375,8.375-3.656,17.202-3.656,26.343c0,52.53,42.194,95.185,94.537,95.966c13.405,25.406,40.069,42.719,70.787,42.719
c29.632,0,55.506-16.125,69.326-40.062c13.82,23.938,39.687,40.062,69.327,40.062c30.718,0,57.389-17.312,70.795-42.719
c52.342-0.781,94.529-43.436,94.529-95.966c0-9.141-1.281-17.968-3.656-26.343C502.238,334.383,511.973,315.336,511.973,294.009z
M186.668,490.644c-32.351,0-58.663-26.312-58.663-58.654c0-16.406,7.195-31.688,18.078-42.344c2.008-1.938,3.25-4.641,3.25-7.656
c0-5.891-4.773-10.655-10.664-10.655c-2.906,0-5.547,1.156-7.469,3.047c-15.569,14.593-24.53,34.577-24.53,57.608
c0,5.265,0.516,10.421,1.492,15.405c-16.469-2-31.702-9.391-43.616-21.312c-14.102-14.094-21.867-32.844-21.867-52.78
c0-19.952,7.766-38.702,21.867-52.796c14.102-14.109,32.851-21.875,52.796-21.875c6.351,0,12.585,0.781,18.585,2.312
c0.883,0.234,1.797,0.375,2.742,0.375c5.891,0,10.664-4.78,10.664-10.671c0-5.203-3.727-9.531-8.656-10.469
c-7.477-1.875-15.289-2.875-23.335-2.875c-35.805,0-67.03,19.608-83.529,48.655c-7.734-8.422-12.469-19.641-12.469-31.952
c0-16.406,8.32-31.405,22.265-40.14l9.789-6.125c2.898-1.906,4.812-5.188,4.812-8.922c0-2.266-0.719-4.391-1.938-6.109l-6.609-9.297
c-5.695-7.999-8.711-17.452-8.711-27.326c0-13.922,6.07-26.453,15.695-35.094c13.578,18.766,35.655,30.984,60.593,30.984
c5.89,0,10.765-4.781,10.765-10.672s-4.844-10.656-10.733-10.656c-29.453,0-53.452-23.969-53.452-53.436
c0-29.453,23.968-53.422,53.421-53.422c7.679,0,14.976,1.641,21.585,4.562c2.242,33.312,29.968,59.624,63.842,59.624
c5.891,0,10.664-4.766,10.664-10.671c0-5.891-4.773-10.657-10.664-10.657c-23.522,0-42.663-19.14-42.663-42.671
c0-23.515,19.133-42.655,42.663-42.655c23.523,0,42.663,19.141,42.663,42.655v106.67h-0.016
c-0.156,5.734-4.867,10.359-10.655,10.359c-5.891,0-10.664,4.781-10.664,10.672s4.773,10.671,10.664,10.671
c3.741,0,7.335-0.656,10.671-1.828v87.451c0,17.64-14.358,31.983-31.999,31.983c-5.891,0-10.664,4.781-10.664,10.672
s4.773,10.672,10.664,10.672c12.008,0,23.086-3.969,31.999-10.672v101.357C245.33,464.332,219.011,490.644,186.668,490.644z
M478.177,325.961c-16.5-29.047-47.718-48.655-83.529-48.655c-8.047,0-15.859,1-23.344,2.875c-4.922,0.938-8.656,5.266-8.656,10.469
c0,5.891,4.781,10.671,10.672,10.671c0.953,0,1.859-0.141,2.734-0.375c6-1.531,12.25-2.312,18.594-2.312
c19.938,0,38.687,7.766,52.795,21.875c14.109,14.094,21.859,32.844,21.859,52.796c0,19.937-7.75,38.687-21.859,52.78
c-11.922,11.921-27.14,19.312-43.607,21.312c0.969-4.984,1.484-10.141,1.484-15.405c0-23.031-8.953-43.016-24.531-57.608
c-1.922-1.891-4.562-3.047-7.469-3.047c-5.891,0-10.672,4.765-10.672,10.655c0,3.016,1.25,5.719,3.25,7.656
c10.891,10.656,18.094,25.938,18.094,42.344c0,32.342-26.312,58.654-58.67,58.654c-32.344,0-58.663-26.312-58.663-58.654V330.633
c8.914,6.703,19.991,10.672,31.991,10.672c5.906,0,10.672-4.781,10.672-10.672s-4.766-10.672-10.672-10.672
c-17.625,0-31.991-14.344-31.991-31.983v-87.451c3.336,1.172,6.93,1.828,10.663,1.828c5.891,0,10.672-4.78,10.672-10.671
c0-5.891-4.781-10.672-10.672-10.672c-5.78,0-10.491-4.625-10.647-10.359h-0.016V63.982c0-23.515,19.147-42.655,42.663-42.655
s42.671,19.141,42.671,42.655c0,23.531-19.155,42.671-42.671,42.671c-5.891,0-10.672,4.767-10.672,10.657
c0,5.905,4.781,10.671,10.672,10.671c33.874,0,61.607-26.312,63.842-59.624c6.609-2.922,13.906-4.562,21.578-4.562
c29.468,0,53.436,23.969,53.436,53.422c0,29.467-23.999,53.436-53.467,53.436c-5.891,0-10.719,4.766-10.719,10.656
c0,5.89,4.875,10.672,10.75,10.672c24.937,0,47.029-12.219,60.592-30.984c9.625,8.641,15.703,21.172,15.703,35.094
c0,9.874-3.016,19.327-8.719,27.326l-6.609,9.297c-1.219,1.719-1.938,3.844-1.938,6.109c0,3.734,1.922,7.016,4.812,8.922
l9.797,6.125c13.938,8.734,22.266,23.733,22.266,40.14C490.645,306.32,485.911,317.539,478.177,325.961z"/>
</svg>
`;
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let rect = canvas.getBoundingClientRect();
let img = new Image();
img.onload = function(e) {
ctx.drawImage(e.target, canvas.width / 2 - 50, canvas.height / 2 - 50, 100, 100);
canvas.addEventListener('mousemove', (e) => {
let data = ctx.getImageData(e.clientX - rect.left, e.clientY - rect.top, 1, 1).data;
let hit = hitColor == `rgba(${data[0]},${data[1]},${data[2]},${data[3]})`;
document.getElementById('message').innerText = `inside path: ${hit}`;
});
}
img.src = 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(svg);
<canvas id="canvas" style="background:black;"></canvas><br>
<span id="message">inside path:</span>

zoom into html canvas

I would like show a cutting of a canvas element in another canvas element. For explanation i have the following structure:
Canvas Element which gets filled it's background by an image. On top of this image i draw a arrow and a possible path. The background-image is really big, which means that i can not get that much Information from this big image. This is the reason for point two.
I would like to show a cutting of the canvas element 1. For example like the following image:
Currently i get the coordinate of the red arrow in canvas element 1, now i would like to do something like a cutting of this section with offset like in the image.
How could i solve something like this with JavaScript / JQuery. In summary i have two canvas elements. One of them is showing a big map with a red arrow which represents the current location (this works already), but now i wanna show a second canvas element with the zoom of this section where the red arrow is. Currently i am getting the coordinates, but no idea how i could "zoom" into an canvas element.
Like some of the current answers said, i provide some code:
My HTML Code, there is the mainCanvasMap, which has a Background Image and there is the zoomCanvas, which should display a section of the mainCanvasMap!
Here is a JavaScript snippet, which renders the red arrow on the map and should provide a zoom function (where the red-arrow is located) to the zoomCanvas Element.
var canvas = {}
canvas.canvas = null;
canvas.ctx = null;
canvas.scale = 0;
var zoomCanvas = {}
zoomCanvas.canvas = null;
zoomCanvas.ctx = null;
zoomCanvas.scale = 0;
$(document).ready(function () {
canvas.canvas = document.getElementById('mainCanvasMap');
canvas.ctx = canvas.canvas.getContext('2d');
zoomCanvas.canvas = document.getElementById('zoomCanvas');
zoomCanvas.ctx = zoomCanvas.canvas.getContext('2d');
setInterval(requestTheArrowPosition, 1000);
});
function requestTheArrowPosition() {
renderArrowOnMainCanvasElement();
renderZoomCanvas();
}
function renderArrowOnMainCanvasElement(){
//ADD ARROW TO MAP AND RENDER THEM
}
function renderZoomCanvas() {
//TRY TO ADD THE ZOOM FUNCTION, I WOULD LIKE TO COPY A SECTION OF THE MAINCANVASMAP
zoomCanvas.ctx.fillRect(0, 0, zoomCanvas.canvas.width, zoomCanvas.canvas.height);
zoomCanvas.ctx.drawImage(canvas.canvas, 50, 100, 200, 100, 0, 0, 400, 200);
zoomCanvas.canvas.style.top = 100 + 10 + "px"
zoomCanvas.canvas.style.left = 100 + 10 + "px"
zoomCanvas.canvas.style.display = "block";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<!--MY MAIN CANVAS ELEMENT, WHICH HAS A BACKGROUND IMAGE AND WHERE THE ARROW IS RENDEREED-->
<canvas id="mainCanvasMap" style="width:100%; height:100%; background: url('https://image.jimcdn.com/app/cms/image/transf/dimension=624x10000:format=jpg/path/s7d1eecaa5cee1012/image/i4484f962de0bf3c2/version/1543751018/image.jpg') 0% 0% / 100% 100%;"></canvas>
<!-- MY ZOOM CANVAS ELEMENT, SHOULD SHOW A CUTTING OF THE MAINCANVASMAP -->
<canvas id="zoomCanvas" style="height:100%;width:100%"></canvas>
The code is only a pseudo-code, but it shows what i like to do.
Your code is using css for the canvas image, that not always looks the way we think...
I will recommend you to draw everything from scratch, here is a starting point:
canvas = document.getElementById('mainCanvasMap');
ctx = canvas.getContext('2d');
zoomCanvas = document.getElementById('zoomCanvas');
zoomCtx = zoomCanvas.getContext('2d');
var pos = {x:0, y:40}
image = document.getElementById('source');
image.onload = draw;
function draw() {
ctx.drawImage(image,0,0);
setInterval(drawZoom, 80);
}
function drawZoom() {
// simple animation on the x axis
x = Math.sin(pos.x++/10 % (Math.PI*2)) * 20 + 80
zoomCtx.drawImage(image, x, pos.y, 200, 100, 0, 0, 400, 200);
}
<canvas id="mainCanvasMap"></canvas>
<canvas id="zoomCanvas"></canvas>
<img id="source" src="https://image.jimcdn.com/app/cms/image/transf/dimension=624x10000:format=jpg/path/s7d1eecaa5cee1012/image/i4484f962de0bf3c2/version/1543751018/image.jpg" style="display:none">

when convert canvas to svg need only cropped content

I am using the HTML5 CANVAS with fabric js. Finally will be converted it into SVG.we can upload images and crop into circle,rectangle,.. .The image also being cropped and added text on it.The problem which i am facing is the images are not being cropped into svg.It shows full images like below.i have tried viewBox also in toSVG.Please suggest me what to do or if i am doing anything wrong.
$(function(){
var canvas = new fabric.Canvas('Canvas',{backgroundColor: '#ffffff',preserveObjectStacking: true});
canvas.clipTo = function (ctx) {
ctx.arc(250, 300, 200, 0, Math.PI*2, true);
};
fabric.Image.fromURL('https://fabric-canvas.s3.amazonaws.com/Tulips.jpg', function(oImg) {
canvas.add(oImg);
});
canvas.add(new fabric.IText('Welcome ', {
left : fabric.util.getRandomInt(50,50),
top:fabric.util.getRandomInt(430, 430)
}));
canvas.renderAll();
$('#tosvg_').on('click',function(){
$('#svgcontent').html(canvas.toSVG());
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.18/fabric.js"></script>
<div class="container">
<div id='canvascontain' width='1140' height='600' style='left:0px;background-color:rgb(240,240,240)'>
<canvas id="Canvas" width='1140' height='600'></canvas>
</div>
<input type='button' id='tosvg_' value='create SVG'>
<div id='svgcontent'></div>
I looked through the fabric documentation and could not find any methods for carrying over the canvas's clipTo property to the generated SVG. So I think the best (only) way to crop the SVG image to a circle, is to insert some additional SVG elements into it. Rather than explaining piece by piece, I'll just paste some commented JS code I came up with for this.
$(function(){
var canvas = new fabric.Canvas('Canvas',{backgroundColor: '#ffffff',preserveObjectStacking: true});
canvas.clipTo = function (ctx) {
ctx.arc(250, 300, 200, 0, Math.PI*2, true);
};
// Using a JQuery deferred object as a promise. This is used to
// synchronize execution, so that the SVG is never created before
// the image is added to the canvas.
var dImageLoaded = $.Deferred();
fabric.Image.fromURL('https://fabric-canvas.s3.amazonaws.com/Tulips.jpg', function(oImg) {
// Insert the image object below any existing objects, so that
// they appear on top of it. Then resolve the deferred.
canvas.insertAt(oImg, 0);
dImageLoaded.resolve();
});
canvas.add(new fabric.IText('Welcome ', {
left : fabric.util.getRandomInt(50,50),
top:fabric.util.getRandomInt(430, 430)
}));
canvas.renderAll();
$('#tosvg_').on('click',function(){
// Wait for the deferred to be resolved.
dImageLoaded.then(function () {
// Get the SVG string from the canvas.
var svgString = canvas.toSVG({
viewBox: {
x: 50,
y: 100,
width: 400,
height: 400,
},
width: 400,
height: 400,
});
// SVG content is not HTML, nor even standard XML, so doing
// $(svgString) might mutilate it. Therefore, even though we
// will be modifying the SVG before displaying it, we need to
// insert it straight into the DOM before we can get a JQuery
// selector to it.
// The plan is to hide the svgcontent div, append the SVG,
// modify it, and then show the svcontent div, to prevent the
// user from seeing half-baked SVG.
var $svgcontent = $('#svgcontent').hide().html(svgString);
var $svg = $svgcontent.find('svg');
// Create some SVG elements to represent the clip path, and
// append them to $svg. To create SVG elements, we need to use
// document.createElementNS, not document.createElement, which
// is also what $('<clipPath>') and $('<circle>') would call.
var $clipPath = $(document.createElementNS('http://www.w3.org/2000/svg', 'clipPath'))
.attr('id', 'clipCircle')
.appendTo($svg);
var $circle = $(document.createElementNS('http://www.w3.org/2000/svg', 'circle'))
.attr('r', 200)
.attr('cx', 250)
.attr('cy', 300)
.appendTo($clipPath);
// This part is brittle, since it blindly writes to the
// transform attributes of the image and text SVG elements.
// SVG clip paths clip content relative to the pre-transformed
// coordinates of the element. The canvas.toSVG method places
// the image and the text each inside of group elements, and
// transforms the group elements. It does not transform the
// image and text. Because transforms work equally well on
// image and text as on groups, we move the transform attribute
// from the two groups to their contents. Then, we can use the
// same clip-path on all groups.
// If the contents of the groups already had transforms, we
// would need to create multiple clip paths - one for each group
// - based on the groups' transforms.
$svg.find('g').each(function () {
var $g = $(this);
// Move transform attribute from group to its children, and
// give the group a clip-path attribute.
$g.children().attr('transform', $g.attr('transform'));
$g.removeAttr('transform').attr('clip-path', 'url(#clipCircle)');
});
$('#svgcontent').show();
});
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.18/fabric.js"></script>
<div class="container">
<div id='canvascontain' width='1140' height='600' style='left:0px;background-color:rgb(240,240,240)'>
<canvas id="Canvas" width='1140' height='600'></canvas>
</div>
<input type='button' id='tosvg_' value='create SVG'>
<div id='svgcontent'></div>
</div>
I noticed that part of the Welcome text gets clipped, but I'll leave it to you to move that.

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();

Jquery: convert SVG to PNG with new size

I´m using the following function to transform an SVG into a PNG and offer it as a download:
<svg id="chart">
...some contenthere
</svg>
function() {
var svg = $("#chart")[0],
bBox = $('#chart')[0].getBBox();
var width = bBox.width*2,
height = bBox.height*2;
var canvas = $("canvas")[0],
serializer = new XMLSerializer(),
svgString = serializer.serializeToString(svg);
canvas.width = width;
canvas.height = height;
canvg(canvas, svgString);
var dataURL = canvas.toDataURL('image/png'),
data = atob(dataURL.substring('data:image/png;base64,'.length));
asArray = new Uint8Array(data.length);
for (var i = 0; i < data.length; ++i) {
asArray[i] = data.charCodeAt(i);
}
var blob = new Blob([asArray.buffer], {type: 'image/png'});
saveAs(blob, 'climatechart.png');
}
It actually works fine, despite the fact that the output image is the same size as the SVG in the browser. How can i set a new size for the image? I also tried to get the size directly from the svg not the BBox, but it was the same.
This solution is based on a remote formatting server and not local code. It uses XSL FO server-side to render SVG to bitmap directly, allowing you to set the resolution for high quality output.
Documentation: http://www.cloudformatter.com/CSS2Pdf.APIDoc.Usage
Example: http://jsfiddle.net/g75t4oyq/
Code Implementation for JPG format # 300dpi from SVG in div:
click="return xepOnline.Formatter.Format('JSFiddle', {render:'newwin', mimeType:'image/jpg', resolution:'300', srctype:'svg'})";
jQuery('#buttons').append('<button onclick="'+ click +'">JPG #300dpi</button>');
You can easily resize a canvas as you can draw it as a canvas. The following example ignores any of the SVG rendering, which is working fine according to your question, but it does show how you can easily resize a canvas using another canvas:
var myCanvas = document.createElement('canvas');
var myCtx = myCanvas.getContext('2d');
myCanvas.width = 200;
myCanvas.height = 200;
myCtx.arc(100, 100, 100, 0, Math.PI * 2, true);
myCtx.fill();
var seCanvas = document.createElement('canvas');
var seCtx = seCanvas.getContext('2d');
seCanvas.width = 200;
seCanvas.height = 200;
seCtx.drawImage(myCanvas, 0, 0, 100, 100);
document.body.appendChild(myCanvas);
document.body.appendChild(seCanvas);
After that, you can simply continue the process using this new canvas.
SVG documents can be resized via height and width attributes if the svg element defines a viewBox attribute depending on its size; e.g.
<svg width="500" height="200" viewBox="0 0 50 20" >
Once the viewBox has been set, the scaling of svg in canvas works as you coded.
Ok, thanks for the suggestions, i finally found a solution that perfectly fits (according to Cédric Hartlands hint). The viewbox attribute was the part that was completely missing in my code. The viewbox is defined as the following:
viewbox = "x y width height"
So to scale up the <svg> and ALL the elements in it, it is necessary to make sure that width and height of the viewbox exactly match the new width and height of the <svg> element:
//original version
<svg> width="100" height="100" viewbox="0 0 100 100" preserveAspectRatio="xMinYMin meet"</svg>
//scaled up version
<svg> width="200" height="200" viewbox="0 0 200 200" preserveAspectRatio="xMinYMin meet"</svg>
If i convert the scaled up svg into a canvas, it fits the whole image without loosing any quality (which is the biggest benefit of vector graphics anyway). Cannot believe it took my so long to get that.

Categories