AWS lambda convert svg to png - node.js

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

Related

svg blobbed image is broken after encoding it

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

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

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.
​

One SVG file, many SVG gradients inside

I’m making a set of buttons which use dynamic gradients. I’ve taken care of Firefox 3.6+ and WebKit by using their proprietary CSS extensions and all I need to do is support Opera, iOS and IE9 by using background-image: url("gradient.svg").
This is relatively easy, I made an SVG file, linked it and got it working. However, I’m making a set so I need at least 6 gradients. When I normally do it in images, I create a sprite for fast HTTP access. I’m not sure how to achieve this in SVG – can I use one file and access different parts of its XML by using #identifiers, like XBL does?
My current SVG:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="select-gradient" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="rgb(231,244,248)"/>
<stop offset="100%" stop-color="rgb(207,233,241)"/>
</linearGradient>
<style type="text/css">
rect {
fill: url(#select-gradient);
}
</style>
</defs>
<rect x="0" y="0" rx="6" ry="6" height="100%" width="100%"/>
</svg>
And then I have CSS:
.button-1 {
background-image: url("gradient-1.svg");
}
.button-2 {
background-image: url("gradient-2.svg");
}
I want to do something like this:
.button-1 {
background-image: url("gradient.svg#gradient1");
}
.button-2 {
background-image: url("gradient.svg#gradient2");
}
Is it even possible? Can you help me out? I really don’t wanna push 6 XML files when I can do it with one.
If you just want gradients for button backgrounds, most of this can be acheived in css. For the remaining browsers, ie6 + can user ms filters:
http://msdn.microsoft.com/en-us/library/ms532847.aspx
iOS uses webkit to render, so you can use -webkit vendor prefix. Unfortunately you will still need svg for opera, but this may make it easier (or just use a normal image sprite for opera's 1% of users)
in theory - according to SVG documentation #Params it is possible. You could use 2 params for setting up both colors, you could create multiple rects with different gradients, height set to 0 and then make only one 100% (like ?gradient2=100%)
What you could do is load your SVG file that contains all of the definitions first, and then load your other SVG files.
Using Firefox, jQuery SVG , and a minor shot of framework...
in your XHTML:
<div id="common_svg_defs"><!--ieb--></div>
<div id="first_thing"><!--ieb--></div>
<div id="second_thing"><!--ieb--></div>
in your JavaScript:
var do_stuff = function()
{
// load your common svg file with this goo.
$('#common_svg_defs').svg({
loadURL: 'path/filename.svg',
onLoad: function(svg, error) { run_test(svg, error);} });
}
var run_test = function(svg, error)
{
if (typeof(error) !== "undefined")
{
if (typeof(console.log) !== "undefined")
{
console.log(error);
}
}
else
{
// load your other svg files here, or just
// set a flag letting you know it's ready.
$('#first_thing').svg({
loadURL: 'path/anotherfilename.svg',
onLoad: function(svg, error) { somecallback(svg, error);} });
$('#second_thing').svg({
loadURL: 'path/anotherfilename.svg',
onLoad: function(svg, error) { somecallback(svg, error);} });
}
}
Because the id can be found in the documents scope, the SVG are capable of finding the IRI reference.
This allows you to define things once (that would not otherwise be defined in a css) and avoid id collisions.
Cheers,
Christopher Smithson

Resources