I need to convert the visible area of the SVG to a static image for printing and similar purposes. I gather from reading that the approach should be to first convert the SVG to a canvas using canvg
, and then convert the canvas to an image using canvas.toDataURL
. I able to accomplish these basic requirements, however my viewbox is much smaller than my SVG, thus cropping and scaling become issues and this is where I am presently stumbling. My thought was to insert an additional two steps prior to converting to an image where I use canvasContext.drawImage(...)
to crop the unscaled canvas to the region defined by the SVG viewbox. This last step does not work for me: I cannot manage to crop the image and maintain the present scale. I then intend to use pica.resizeCanvas
to achieve high quality scaling of the image (such that it fits the printable page).
Before getting into code the questions are:
- Is the over all approach sound?
- What am I doing wrong with
canvasContext.drawImage(...)
that the image either ends up scaled or cropped incorrectly? The objective is to apply no scaling but to crop some excess white space.
Summary of Steps
- Copy
svg
tocanvas
usingcanvg
. This works.- No scaling is applied
- Offsets are used to move the image to the upper left corner in preparation for future cropping of remaining white space (lower right region).
- Get a hold of newly created
canvas
and draw cropped region to a secondarycanvas
usingcanvasContext.drawImage
. This fails. Either the canvas is the wrong size but the scaling is correct (ie none) or the canvas is the right size but the scaling is wrong (scaled way up). - Use
pica.resizeCanvas
to apply scaling with minimal loss of quality. Have not really tried this yet. - Use
canvasContext.toDataURL(...)
to convert canvas topng
. This works.
Code
function MakeImage() {
//min-x, min-y, width and height
var viewBox = parseViewBox(); //defined below
var vbMinX = viewBox[0];
var vbMinY = viewBox[1];
var vbWidth = viewBox[2];
var vbHeight = viewBox[3];
var svgUnitRatio = getSvgUnitRatio(vbHeight); //defined below
//xMin,yMin,xMax,yMax
var boundingBox = getBounds(); //defined below
var bbXmin = boundingBox[0];
var bbYmin = boundingBox[1];
var bbXmax = boundingBox[2];
var bbYmax = boundingBox[3];
var offsetX = (vbMinX - bbXmin) * svgUnitRatio;
var offsetY = (vbMinY - bbYmin) * svgUnitRatio;
var adjustedWidth = (bbXmax - bbXmin) * svgUnitRatio;
var adjustedHeight = (bbYmax - bbYmin) * svgUnitRatio;
var options = {
ignoreDimensions: false, //allow it to resize the canvas based on the svg size
offsetX: offsetX,
offsetY: offsetY
};
//first we copy the svg to a canvas w/o applying any scaling
window.canvg("workspaceCanvas", $("#mysvg").parent().html(), options);
//now we crop according the svg viewbox
var canvas = document.getElementById("canvas");
var workspaceCanvas = document.getElementById("workspaceCanvas");
var context = canvas.getContext('2d');
context.drawImage(workspaceCanvas, 0, 0, adjustedWidth, adjustedHeight, 0, 0, adjustedWidth, adjustedHeight); //something is wrong here i guess???
//next we do a high quality scaling of the canvas
var pOptions = { //maybe this has problems but i won't kow until i get the previous step right
quality: 3,
alpha: true,
unsharpAmount: 50,
unsharpRadius: 0.5,
unsharpThreshold: 0
};
//holding off on trying this for now
window.pica.resizeCanvas(workspaceCanvas, canvas, pOptions, function(err) { /*this is a mandatory argument*/ });
var img = canvas.toDataURL("image/png,1");
//do stuff with image data
}
function getSvgUnitRatio(viewboxHeight) {
//shouldnt need to worry about width since the aspect ratio should be locked
var height = parseFloat(d3.select("#mysvg").attr("height"));
return height / viewboxHeight;
}
function getBounds() {
//xMin,yMin,xMax,yMax
var boundingBox = [];
var xMin = Number.MAX_SAFE_INTEGER;
var yMin = Number.MAX_SAFE_INTEGER;
var xMax = Number.MIN_SAFE_INTEGER;
var yMax = Number.MIN_SAFE_INTEGER;
window.svg.selectAll(".elementsICareAbout").nodes().forEach(function(d) {
var dx = parseFloat(d.getAttribute("x"));
var dy = parseFloat(d.getAttribute("y"));
var width = parseFloat(d.getAttribute("width"));
var height = parseFloat(d.getAttribute("height"));
if (dx + width > xMax) {
xMax = dx + width;
}
if (dx < xMin) {
xMin = dx;
}
if (dy + height > yMax) {
yMax = dy + height;
}
if (dy < yMin) {
yMin = dy;
}
});
var padding = 25; //add some fluff
//xMin,yMin,xMax,yMax
boundingBox = [
xMin - padding, yMin - padding, xMax + padding, yMax + padding
];
return boundingBox;
}
function parseViewBox() {
var str = d3.select("#mysvg").attr("viewBox");
var parts = str.split(" ");
var parsed = [];
parts.forEach(function(p) {
parsed.push(parseFloat(p));
});
return parsed;
}