2

I'm using sharp to convert the file but sharp gives me an empty png file.

I want to use a base64 formatted SVG image as <image> tag's source and want to convert it to a png file.

Here's an example to show what I'm trying to do;

const sharp = require('sharp');
sharp(Buffer.from(<look below js file to see the SVG>))
    .toFormat('png')
    .toFile('output.png')

And here's a JS to show what I tried to do. Basically, I want to convert this SVG to a png file using sharp but sharp gives me an empty png.

ahwayakchih
  • 2,101
  • 19
  • 24
Kevin S
  • 37
  • 1
  • 5
  • Fiddle you linked to does not contain any JavaScript code. There's only HTML with a single SVG, that's really just a wrapper for another SVG embedded inside it as an `IMAGE` element with data URI. – ahwayakchih Oct 16 '19 at 10:37
  • @ahwayakchih Yes and sharp won't create a png file with this input. That's my problem – Kevin S Oct 16 '19 at 11:58

1 Answers1

2

It looks like sharp does not handle external images well.

You can try to "flatten" SVG before passing it to sharp.

Quick and dirty implementation

This is very error prone. Ideally you would use something like Cheerio for parsing and modifying SVG input.

It also preserves only x, y, width, height and opacity attributes. Others attributes would require further changes.

function flattenSVG (svg) {
  const images = svg.match(/<image [^>]+>/g);
  if (!images || images.length < 1) {
    return svg;
  }

  var result = svg;
  images.forEach(image => {
    const [,data] = image.match(/ xlink:href="data:image\/svg\+xml;base64,([^"]+)"/) || [];
    if (!data) {
      return;
    }

    const innerSVG = Buffer.from(data, 'base64').toString();
    const [,width] = image.match(/ width="([^"]+)"/) || [];
    const [,height] = image.match(/ height="([^"]+)"/) || [];
    const [,opacity] = image.match(/ opacity="([^"]+)"/) || [];
    const [,x] = image.match(/ x="([^"]+)"/) || [];
    const [,y] = image.match(/ y="([^"]+)"/) || [];
    const [header] = innerSVG && innerSVG.match(/<svg[^>]+>/) || [];
    const fixedHeader = header
      .replace(/ (x|y|width|height)="([^"]+)"/g, '')
      .replace('<svg', `<svg x="${x}" y="${y}" width="${width}" height="${height}" opacity="${opacity || 1.0}"`);
    const replacement = innerSVG && innerSVG.replace(header, fixedHeader);
    result = result.replace(image, replacement);
  });

  return result;
}

So, in your code, you would use something like:

sharp(Buffer.from(flattenSVG(testSVG)))
      .toFormat('png')
      .toFile('output.png')

You can check working code (a bit different for test purposes, as it uses buffer instead of file output) at Glitch.

ahwayakchih
  • 2,101
  • 19
  • 24
  • Thank you so much for your effort and that's the answer I was looking for but I think if I convert SVG to PNG, it'll much easier. But thank you. – Kevin S Oct 16 '19 at 14:27
  • Do you mean converting to PNG some other way? Because code above does create PNG file. It's only the example code on Glitch that keeps it in `Buffer` instead (data is still PNG content, just not written to a file). – ahwayakchih Oct 16 '19 at 14:37
  • Yes, and I've read your code. I have the file of encoded SVG image so I'll convert it PNG directly then I'll use PNG in the other SVG template but if there's a performance issue occurs I'll try your answer. Thanks again! – Kevin S Oct 16 '19 at 14:46
  • Hmm.. why do you need to embed PNG inside SVG? That's quite ugly and misses one of the main points of SVG (Scalable Vector :). – ahwayakchih Oct 16 '19 at 14:50
  • Exactly! But this way quite easy for us. We're processing complicated images and we don't have much time. So for the first version, we'll go with PNG images. For now, our goal is not perfect coding. After we published our product We'll start the second version of our product and we'll convert all that PNG images to SVG images and I'll use your answer for that. I'll let you know if there's a problem with your method when we started to work V2. – Kevin S Oct 16 '19 at 15:52