svg blobbed image is broken after encoding it - svg

I've tried parsing an image into blob and refer it in image tag in svg:
`<image xlink:href="${imgRef}" height="${h}" width="${w}" stroke="red" x="50%" y="50%" transform="translate(${-(w / 2)}, ${-(h / 2) - 4})"/>`
and embed it in svg:
const w = 100; // width
const h = 65; // height
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" xmlns:xlink="http://www.w3.org/1999/xlink">` +
`<image xlink:href="${imgRef}" height="${h}" width="${w}" stroke="red" x="50%" y="50%" transform="translate(${-(w / 2)}, ${-(h / 2) - 4})"/>` +
`</svg>`;
Then I tried encoding it through 'data:image/svg+xml;,' + encodeURIComponent(svgString) and it shows a broken image. I tested the above svg string before encoding it via document.body.innerHTML = svgString and the svg image is well displayed. So I'm suspecting the encoding part but it doesn't seem wrong based on many references.
What am I missing? Any insight would be appreciated.
Demo link: https://codepen.io/Dongbin/pen/poRmbmg?editors=0010

External images (ie <img> or CSS background-image) must be self contained. They cannot refer to resources outside the image itself. Basically that means if you need to reference any files (bitmap images, fonts, etc) as part of the SVG image, they need to be Data URLs.
The fix is to not use canvas.toBlob(). Use canvas.toDataURL() instead.
const imgRef = canvas.toDataURL();
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" ` +
`xmlns:xlink="http://www.w3.org/1999/xlink">` +
`<image xlink:href="${imgRef}" height="125" width="200"/>` +
`</svg>`;
Update
If you URI Encode the SVG, it should work.
See: https://jsfiddle.net/dxkeqfnr/
const img = new Image();
img.src = 'https://live.staticflickr.com/7272/7633179468_3e19e45a0c_b.jpg';
img.crossOrigin="anonymous"
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 125;
canvas.getContext('2d').drawImage(img, 0, 0, 200, 125);
canvas.toBlob((blob) => {
const imgRef = URL.createObjectURL(blob);
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="200" height="125" xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="${imgRef}" height="125" width="200"/></svg>`;
//div.innerHTML = svgString;
const newImg = new Image();
newImg.src = `data:image/svg+xml,${encodeURIComponent(svgString)}`;
document.body.appendChild(newImg);
});
};
It works in Firefox, but it doesn't work in Chrome. That seems to be a bug. I have reported the bug to Chrome

Related

Node JS Sharp - Maintain SVG Font When Converting To JPG

I am using the sharp library to create dynamic JPEG license plate images.
Basically, I have a PNG that is a vanity license plate with no numbers. Then I create an svg in code like so
const svg = new Buffer(
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${x} ${y} 500 40">
<defs>
<style type="text/css">
<![CDATA[
#font-face {
font-family: LicensePlate;
src: url('LicensePlate.ttf');
}
svg {
width: 100%;
height: 100%;
}
]]>
</style>
</defs>
<text x="0" y="0" font-family="LicensePlate" font-size="${fontsize}" letter-spacing="${letterspace}">
${platenumber.toUpperCase()}
</text>
</svg>`
);
Passing in the desired width, height, and license plate number. Then I use the sharp library to overlay my SVG in the middle of the license plate. This all works just fine.
However, I have imported a custom license plate font (LicensePlate.ttf). In order to debug my in-code SVG image I made an actual svg image file that I open in the browser to make sure that it all looks correct, which it does.
The problem is that when the final JPEG file is created it does not contain my custom font. Instead it falls back on Verdana.
My question is, is there any way I can maintain the SVG font while creating the image with sharp?
Thanks!
Full Code
function createImage(platenumber) {
//Trying to create some sort of responsiveness
let fontsize = 80;
let letterspace = 10;
let width = 300;
let height = 90;
let x = 0;
let y = -45;
const inputlength = platenumber.length;
//Minumum Length
if (inputlength == 2) {
x = -200;
}
if (inputlength == 3) {
x = -150;
}
if (inputlength == 4) {
x = -130;
}
if (inputlength == 5) {
x = -105;
}
if (inputlength == 6) {
x = -65;
}
try {
console.log('stream is duplex, ', pipe instanceof stream.Duplex);
//Read the svg code into a buffer with a passed in plate number
const svg = new Buffer(
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${x} ${y} 500 40">
<defs>
<style type="text/css">
<![CDATA[
#font-face {
font-family: LicensePlate;
src: url('LicensePlate.ttf');
}
svg {
width: 100%;
height: 100%;
}
]]>
</style>
</defs>
<text x="0" y="0" font-family="LicensePlate" font-size="${fontsize}" letter-spacing="${letterspace}">
${platenumber.toUpperCase()}
</text>
</svg>`
);
const plateid = rand.generate(10);
//Create a write stream to a randomly generated file name
const write = new fs.createWriteStream(`plates/${plateid}.jpg`);
//Create the sharp pipeline
const pipeline = pipe
.overlayWith(svg, { gravity: sharp.gravity.center })//we center the svg image over the top of whatever image gets passed into the pipeline
.jpeg();//we convert to JPG because it is a compressed file format and will save space (we could also do webp if we really want to be slick about it)
//Create the read stream from the license plate template
const read = new fs.createReadStream('plate-2.png')
.pipe(pipeline)//pipe out sharp pipeline
.pipe(write);//add the write stream so that our sharp pipeline knows where to put the image
return plateid;
} catch (e) {
console.log(e);
return null;
}
}
SVG Image
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="90" viewBox="0 -50 500 40">
<defs>
<style type="text/css">
<![CDATA[
#font-face {
font-family: LicensePlate;
src: url('LicensePlate.ttf');
}
]]>
</style>
</defs>
<text x="0" y="0" font-family="LicensePlate" font-size="150" letter-spacing="10">
Pl#T3#
</text>
</svg>
Exact Font
Here is a link to the exact font I used
http://www.fontspace.com/dave-hansen/license-plate
I dealt with this problem by installing the font in my OS, when the file is being converted, the libraries can only access OS fonts.

Download svg including it's text tag value

I'm working on a way to download svg from a webpage.
I have pretty much everything working apart from the last part:
Downloading the svg including the text tag included in it.
Now, to give a better background:
I will need to download images out of an svg using fontawesome icon as text.
The svg is properly downloaded, hower the text tag is not (it's downloaded as a broken image "square")
Here's a simplified version of my code:
function triggerDownload(imgURI, name, format) {
let evt = new MouseEvent('click', {
view: window,
bubbles: false,
cancelable: true
});
let a = document.createElement('a');
a.setAttribute('download', name + '.' + format);
a.setAttribute('href', imgURI);
a.setAttribute('target', '_blank');
a.dispatchEvent(evt);
}
function clickSVG(event) {
const dd = 300;
const format = "png"
let canvas = document.getElementById('canvas'),
target = event.currentTarget;
canvas.width = dd;
canvas.height = dd;
debugger
let newImage = target.cloneNode(true),
circle = newImage.getElementsByClassName('svgCircle-test') ? newImage.getElementsByClassName('svgCircle-test') : null,
image = newImage.getElementsByClassName('svgImage-test');
newImage.height.baseVal.value = dd;
newImage.width.baseVal.value = dd;
if (circle.length > 0) {
circle[0].cx.baseVal.value = dd / 2;
circle[0].cy.baseVal.value = dd / 2;
circle[0].r.baseVal.value = dd / 2;
}
let ctx = canvas.getContext('2d'),
data = (new XMLSerializer()).serializeToString(newImage),
DOMURL = window.URL || window.webkitURL || window,
name = newImage.getAttribute('data-name'),
img = new Image(),
svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'}),
url = DOMURL.createObjectURL(svgBlob);
img.onload = function () {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
let imgURI = canvas
.toDataURL(`image/${format}`)
.replace(`image/${format}`, 'image/octet-stream');
triggerDownload(imgURI, name, format);
};
img.src = url;
}
document.getElementById("svg").addEventListener("click", clickSVG)
The html looks like this:
<svg id="svg" height="200" width="200" data-name="test">
<circle cx="100" cy="100" r="100" fill="#faa" class="svgCircle-test" />
<text x="0" y="120" width="200" height="200" class="svgImage-test"></text>
</svg>
<canvas id="canvas" />
And here's the CSS:
svg {
margin-top:10px;
cursor: pointer;
display: inline-block;
padding: 5px;
}
svg text{
font-family:'FontAwesome';
font-size: 100px;
}
#canvas {
display: none;
}
A codepen to help you understand the issue and help me out a bit better can be found here: https://codepen.io/NickHG/pen/QMmJvd
To see the issue, click on the circle (this will download the svg as a png image).
NB: If the download doesn't start, it's probably your browser blocking popups. Just allow it to see the downloaded image.
Thanks
There are a couple of things going on here.
Once you "convert" the SVG file to an HTMLImageElement (<image>), as you are doing here onto the canvas, things change:
the styling you have applied to the <text> no longer applies. That's because it is in the HTML file, not the SVG "file". You need to add the styling it to the SVG itself.
SVGs rendered as an <image> need to be self contained. They can't reference external files such as the Font Awesome font.
To make it self contained, you need to embed the font file in the SVG itself using a Data URL.
You'll need to add a <style> element to the SVG, and include a #font-face rule that specifies a Base64 encoded font file (or files).
See this question for an example

AWS lambda convert svg to png

I would like to use the AWS Lambda service to convert SVG to PNG with the language NodeJS.
In a second time, the SVG will contain text with custom fonts, I want to know how to use external fonts.
Currently i convert SVG to PNG with PHP Here is an example of conversion in PHP:
$im = new Imagick();
$draw = new ImagickDraw();
$im->setFormat('MSVG');
// Open SVG with ImageMagick
$im->readImageBlob($svgParse->asXML());
$im->setImagePage(0, 0, 0, 0);
$im->scaleImage($viewBox['2'],$viewBox['3'], false);
// Add Text with custom FONT
foreach($ListText as $text){
// Set Custom FONT
$draw->setFont(Mage::getBaseDir('media').DS.'font'.DS.$text['font-family'].".ttf");
$draw->setFontSize( $text['font-size'] );
$draw->setFillColor ( $text['fill']);
// Add text in image
$im->annotateImage($draw, $text['x'], $text['y'], 0, $text['text']);
}
/*png settings*/
// FB image
$imFB = clone $im;
$imFB->setFormat('PNG');
$imFB->cropImage(760, 400,0, 0);
$imFB->scaleImage(600,400,1);
// Save
$imFB->writeImage($filename_FB);
$imFB->clear();
$imFB->destroy();
it would be perfect if I can transform the above code for AWS Lambda. I chose NodeJS because I find more examples on web compared to Python and Java but if you control over Python or Java, no problem I change language
code currently on AWS lambda :
var im = require('imagemagick');
var fs = require('fs');
var util = require("util");
var postProcessResource = function(resource, fn) {
var ret = null;
if (resource) {
if (fn) {
ret = fn(resource);
}
try {
fs.unlinkSync(resource);
} catch (err) {
// Ignore
}
}
return ret;
};
exports.handler = function(event, context) {
var operation = event.operation;
delete event.operation;
if (operation) {
console.log('Operation', operation, 'requested');
}
switch (operation) {
case 'ping':
context.succeed('pong');
break;
case 'convert':
var conv = im.convert(['svg:-', 'png:-'])
conv.on('data', function(data) {
//console.log('data');
//console.log(data);
});
conv.on('end', function() {
//console.log('end');
});
conv.stdin.write('<svg height="100%" version="1.1" width="100%" style="overflow: hidden; position: relative;" id="paper" viewBox="0 0 760 890" preserveAspectRatio="xMinYMin"><desc style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">Created with Raphaël 2.1.2</desc><defs style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></defs><image x="0" y="0" width="760" height="890" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://camaieu.akria.fr/media/catalog/product/cache/1/image_position_coeur/9df78eab33525d08d6e5fb8d27136e95/3/_/3_1_2.png" id="svg_image" preserveaspectratio="" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);" stroke-width="1" product_id="22"></image><text x="320" y="400" text-anchor="left" font-family="detex" font-size="32px" stroke="none" fill="#fefefe" id="svg_message" stroke-width="1" height="80" width="170" style="font-size: 32px;"><tspan dy="10.6015625" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">zdfazerf</tspan></text></svg>');
conv.stdin.end();
console.log(conv.toString('base64'));
break;
default:
context.fail(new Error('Unrecognized operation "' + operation + '"'));
}
};
The process takes 9s but I do not know retrieve the result, I do not even know what that actually
Example unsuccessfully :
nodejs imagemagick converting svg to png adds a white background, how to keep it transparent?
I am starting on NodeJS and AWS Lambda.
Thx.
Here's what I just got to work for custom fonts on AWS Lambda with pandoc/xelatex. I'd bet you can adapt this technique for your purposes.
I created a fonts directory in my project and placed all of my fonts there. Also in that directory I created a fonts.conf file that looks like this:
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<dir>/var/task/fonts/</dir>
<cachedir>/tmp/fonts-cache/</cachedir>
<config></config>
</fontconfig>
And then in my (node.js based) handler function before shelling out to call pandoc I set an ENV var to tell fontconfig where to find the fonts.
process.env.FONTCONFIG_PATH='/var/task/fonts'
After doing that I can refer to a font, like Bitter, in my template by name (just Bitter) and then pandoc/xelatex/fontconfig/whatever knows which version of the font to use (like Bitter-Bold.otf vs Bitter-Italic.otf) based on the styling that any bit of text is supposed to have.
I figured this out based on the tips in this project for getting RSVG to work with custom fonts on Lambda: https://github.com/claudiajs/rsvg-convert-aws-lambda-binary/blob/master/README.md#using-custom-fonts

How to convert/save d3.js graph to pdf/jpeg

I'm working on a client-side/javascript function to save or convert an existing D3-SVG graph into a file.
I've searched a lot and found some recommendations, mainly using canvas.toDataURL().
I have no <canvas> in my page, and instead using:d3.select("body").append("svg")....
I've also tried to append the SVG to the <canvas> but nothing happens.
Could you please help me to resolve this exception:
Uncaught TypeError: Object #<SVGSVGElement> has no method 'toDataURL'
Thank you
To display your svg within a canvas, you first have to convert it using a parser/renderer utility such as http://code.google.com/p/canvg/
(code adapted from: Convert SVG to image (JPEG, PNG, etc.) in the browser, not tested)
// the canvg call that takes the svg xml and converts it to a canvas
canvg('canvas', $("#my-svg").html());
// the canvas calls to output a png
var canvas = document.getElementById("canvas");
var img = canvas.toDataURL("image/png");
Just a heads up that I turned this concept into a small JavaScript library: https://github.com/krunkosaurus/simg
It simply converts any SVG to an image to swap out or trigger a download. Idea taken from here: http://techslides.com/save-svg-as-an-image/
As pointed out by #Premasagar in this comment on this question Convert SVG to image (JPEG, PNG, etc.) in the browser
If the borwser supports both SVG and canvas you can use this technique https://svgopen.org/2010/papers/62-From_SVG_to_Canvas_and_Back/index.html
function importSVG(sourceSVG, targetCanvas) {
// https://developer.mozilla.org/en/XMLSerializer
svg_xml = (new XMLSerializer()).serializeToString(sourceSVG);
var ctx = targetCanvas.getContext('2d');
// this is just a JavaScript (HTML) image
var img = new Image();
// http://en.wikipedia.org/wiki/SVG#Native_support
// https://developer.mozilla.org/en/DOM/window.btoa
img.src = "data:image/svg+xml;base64," + btoa(svg_xml);
img.onload = function() {
// after this, Canvas’ origin-clean is DIRTY
ctx.drawImage(img, 0, 0);
}
}
The Simg library created and suggest by Mauvis Ledford above worked great for allowing my svg charts created with Dimple to be downloaded.
I did however need to change one aspect of the code to make it work. Inside of the toString() prototype, inside the forEach loop (line 37), if you change "svg.setAttribute(..)" to "svg[0].setAttribute" it will alleviate the "setAttribute(..) is not a function" error. Similarly the same needs to be done right below in the return statement, appending "[0]" after svg (line 39).
I also had to manually edit the "canvas.width" and "canvas.height" (lines 48 & 49) assignments in the toCanvas() prototype, in order to make the downloaded image a more correct size (it was previously just downloading a static 300x150 square in the top left corner of the chart).
This is an old question, in 2022 we have ES6 and we don't need 3rd party libraries.
Here is a very basic way to convert svg images into other formats.
The trick is to load the svg element as an img element, then use a canvas element to convert into a desired format. So four steps are necessary:
Extract svg as xml data string.
Load the xml data string into a img element
Convert the img element to a dataURL using a canvas element
Load the converted dataURL into a new img element
Step 1
Extracting a svg as xml data string is simple:
// Select the element:
const $svg = document.getElementById('svg-container').querySelector('svg')
// Serialize it as xml string:
const svgAsXML = (new XMLSerializer()).serializeToString($svg)
// Encode it as a data string:
const svgData = `data:image/svg+xml,${encodeURIComponent(svgAsXML)}`
Step 2
Loading the xml data string into a img element:
// This function returns a Promise whenever the $img is loaded
const loadImage = async url => {
const $img = document.createElement('img')
$img.src = url
return new Promise((resolve, reject) => {
$img.onload = () => resolve($img)
$img.onerror = reject
$img.src = url
})
}
Step 3
Converting the img element to a dataURL using a canvas element:
const $canvas = document.createElement('canvas')
$canvas.width = $svg.clientWidth
$canvas.height = $svg.clientHeight
$canvas.getContext('2d').drawImage(img, 0, 0, $svg.clientWidth, $svg.clientHeight)
return $canvas.toDataURL(`image/${format}`, 1.0)
Step 4
Loading the converted dataURL into a new img element:
const $img = document.createElement('img')
$img.src = dataURL
$holder.appendChild($img)
Here you have a working snippet:
const $svg = document.getElementById('svg-container').querySelector('svg')
const $holder = document.getElementById('img-container')
const $label = document.getElementById('img-format')
const destroyChildren = $element => {
while ($element.firstChild) {
const $lastChild = $element.lastChild ?? false
if ($lastChild) $element.removeChild($lastChild)
}
}
const loadImage = async url => {
const $img = document.createElement('img')
$img.src = url
return new Promise((resolve, reject) => {
$img.onload = () => resolve($img)
$img.onerror = reject
$img.src = url
})
}
const convertSVGtoImg = e => {
const $btn = e.target
const format = $btn.dataset.format ?? 'png'
$label.textContent = format
destroyChildren($holder)
const svgAsXML = (new XMLSerializer()).serializeToString($svg)
const svgData = `data:image/svg+xml,${encodeURIComponent(svgAsXML)}`
loadImage(svgData).then(img => {
const $canvas = document.createElement('canvas')
$canvas.width = $svg.clientWidth
$canvas.height = $svg.clientHeight
$canvas.getContext('2d').drawImage(img, 0, 0, $svg.clientWidth, $svg.clientHeight)
return $canvas.toDataURL(`image/${format}`, 1.0)
}).then(dataURL => {
console.log(dataURL)
const $img = document.createElement('img')
$img.src = dataURL
$holder.appendChild($img)
}).catch(console.error)
}
const buttons = [...document.querySelectorAll('[data-format]')]
for (const $btn of buttons) {
$btn.onclick = convertSVGtoImg
}
<div style="float: left; width: 70%">
<div style="float: left; width: 50%">
<div id="svg-container">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="200" height="200" viewBox="0 0 248 204">
<path fill="#1d9bf0" d="M221.95 51.29c.15 2.17.15 4.34.15 6.53 0 66.73-50.8 143.69-143.69 143.69v-.04c-27.44.04-54.31-7.82-77.41-22.64 3.99.48 8 .72 12.02.73 22.74.02 44.83-7.61 62.72-21.66-21.61-.41-40.56-14.5-47.18-35.07 7.57 1.46 15.37 1.16 22.8-.87-23.56-4.76-40.51-25.46-40.51-49.5v-.64c7.02 3.91 14.88 6.08 22.92 6.32C11.58 63.31 4.74 33.79 18.14 10.71c25.64 31.55 63.47 50.73 104.08 52.76-4.07-17.54 1.49-35.92 14.61-48.25 20.34-19.12 52.33-18.14 71.45 2.19 11.31-2.23 22.15-6.38 32.07-12.26-3.77 11.69-11.66 21.62-22.2 27.93 10.01-1.18 19.79-3.86 29-7.95-6.78 10.16-15.32 19.01-25.2 26.16z"/>
</svg>
</div>
<div>svg</div>
</div>
<div style="float: right; width: 50%">
<div id="img-container"></div>
<div id="img-format"></div>
</div>
</div>
<div>
<button id="btn-png" data-format="png">PNG</button>
<button id="btn-jpg" data-format="jpeg">JPG</button>
<button id="btn-webp" data-format="webp">WEBP</button>
</div>

How can we make SVG transparent on Canvas?

how can we achieve this?
I got the SVG in the function, how can i make it transparent on top of canvas?? Currently i have all my functions working on the canvas. But I found out that SVG can do the add and remove function. How can I go about it?
function Add() {
var id = Math.floor(Math.random()*101+1);
x = Math.random() * 550;
y = Math.random() * 250;
if (document.getElementById('amount').value < 50){
document.getElementById('amount').value++;
svg = document.getElementById("main");
// construct uniqueid for the images
uniqueid = "frog" + document.getElementById('amount').value;
//namespaces for SVG
svgNS="http://www.w3.org/2000/svg";
xlinkNS="http://www.w3.org/1999/xlink";
// create a image element
image = document.createElementNS(svgNS, 'image');
// set id and other attributes
image.setAttributeNS(null, "id", uniqueid);
image.setAttributeNS(xlinkNS, "href","jef-frog.gif");
image.setAttributeNS(null, "x", x);
image.setAttributeNS(null, "y", y);
image.setAttributeNS(null, "width", "50");
image.setAttributeNS(null, "height", "50");
// append to svg
svg.appendChild(image);
} else {
alert("we got 50");
}
}
Assuming you are asking about transparency in SVG <image> elements, I'm pleased to say that it works just fine:
Demo: http://jsfiddle.net/XBCEK/
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink">
<image xl:href="http://phrogz.net/tmp/alphaball.png"
x="20" y="30" width="128" height="128" />
<image xl:href="http://phrogz.net/tmp/hand.gif"
x="220" y="30" width="32" height="32" />
</svg>​
If you embed that SVG on a page along with the following CSS:
body { background:url(http://phrogz.net/tmp/grid.gif) }
svg { background:rgba(255,0,0,0.3) /*…*/ }
…then you will see that:
The background of the SVG is transparent by default. We can even provide a low-opacity color background that lets the background of the page (the grid) show through.
The background of both 8-bit-transparency PNG (the ball) and 1-bit transparency GIF (the hand) allow the background of the SVG/page to shine through correctly.
​

Resources