简体   繁体   中英

Imagick converting SVG via rsvg renders image blank from PHP, but ok from command line

I've read a range of Questions and web articles but seem to be up against a brick wall. I have reduced my problem to a very simple SVG file, containing a single image. When converted from PHP via imagick, the image is rendered blank, but from the command line using the same rsvg engine it renders fine.

This project has worked like a charm on my local Fedora dev machine, only when moving to production on Centos have I had this trouble.

Environment: Centos 6.2 server, PHP 5.6.36 using php-fpm, Imagick module version 3.4.3 using ImageMagick library version 6.7.8-9 2016-06-16

Note : my apache server does not jail or chroot the file system at all, so all normal path references should work fine, eg it writes to a log file in the web root using an absolute path name without problem.

$ identify -list format | grep -i svg
     MSVG  SVG       rw+   ImageMagick's own SVG internal renderer
      SVG  SVG       rw+   Scalable Vector Graphics (RSVG 2.39.0)
     SVGZ  SVG       rw+   Compressed Scalable Vector Graphics (RSVG 2.39.0)


$ identify -list delegate | grep -i svg
        cdr =>          "uniconvertor" "%i" "%o.svg"; mv "%o.svg" "%o"
        dot =>          "dot" -Tsvg "%i" -o "%o"
        svg =>          "rsvg-convert" -o "%o" "%i"

So I believe the format 'SVG' indicates it will use RSVG for the conversion from PHP.

My SVG (saved from Inkscape):

<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="620mm" height="640mm" viewBox="0 0 620 640" version="1.1" id="svg28" inkscape:version="0.92.3 (2405546, 2018-03-11)" sodipodi:docname="radcover-620x640b.svg">
  <defs id="defs22"/>
  <sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.24748737" inkscape:cx="1386.1321" inkscape:cy="1147.0763" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="1920" inkscape:window-height="1016" inkscape:window-x="2048" inkscape:window-y="27" inkscape:window-maximized="1" inkscape:snap-global="false"/>
  <g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(0,343)">
  <image fill-opacity="0" stroke="none" stroke-opacity="0" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" x="54.336063385009766" y="-280.25213623046875" width="280.0984802246094" height="175.3288116455078" preserveAspectRatio="none" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="image.jpg" data-number="1" transform="matrix(0.98887052,-0.14877865,0.14877865,0.98887052,0.00000000,0.00000000)"/>
  </g>
</svg>

My PHP code:

<?php
$widthPx = 500;
$heightPx = 500;
$outFilename = 'out.png';

$svg = file_get_contents('src.svg');

$im = new Imagick();
$im->readImageBlob('<?xml version="1.0"?>' . $svg);
$im->setImageFormat('png24');

$im->resizeImage($widthPx, $heightPx, imagick::FILTER_LANCZOS, 1);

$im->writeImage($outFilename);

echo "Done.\n";

The same attempt from command line:

convert rsvg:src.svg cli.jpg

The same using rsvg-convert (as indicated in delegates output above):

rsvg-convert src.svg -o rsvg-convert.jpg

Result: Both command line attempts, cli.jpg and rsvg-convert.jpg , show the image just fine. The attempt from PHP shows a blank output ie the <image> element did not render.

There is no exception thrown, or error output that I can find.

I have tried:

  • Make the xlink:href be a file:// URL with absolute path, ie xlink:href="file:///home/myuser/public_html/test/svg-problem/image.jpg"
  • Make the xlink:href be a file:// URL with relative to path current directory ie xlink:href="file://./image.jpg"
  • Make the xlink:href be a file:// URL with no path ie xlink:href="file://image.jpg"
  • Force 'RSVG' usage with $im->setFormat('RSVG') .. it throws an exception with Unable to set format . I cannot see why.

Since the conversion via PHP completes without calling setFormat , and the formats list shows above says it will use RSVG by default, I think it's using RSVG, even though my attempt to force RSVG format failed.

I have read that you can recompile the rsvg library to show more error output when it fails to load a file, but on my production cPanel managed server I am very loath to try recompiling anything.

Any ideas what to try next? Pulling my hair out here :)

The problem here is that I don't know how ImageMagick uses the librsvg interface in detail. But a few things stand out in the latest librsvg doc . While the paragraphs were only added in the v2.42 doc, I think they apply also to older versions, according to what I have observed in Gnome systems.

When processing an SVG, librsvg will only load referenced files if they are in the same directory as the base file, or in a subdirectory of it...This is so that malicious SVG files cannot include files that are in a directory above.

If you already have SVG data in memory, you can create a memory input stream...Note that in this case, it is important that you specify the base_file for the in-memory SVG data. Librsvg uses the base_file to resolve links to external content, like raster images.

Depending on the ImageMagick implementation, that leads to two possible solutions:

  1. Load the SVG directly from a file with

     $im->readImage('src.svg');
  2. Use the string, but set the second file argument in

     $im->readImageBlob('<?xml version="1.0"?>' . $svg, 'src.svg')

Only as an aside, $im->setFormat('RSVG') doesn't make any sense, since "RSVG" is only the name of the library and not a file format.

I could not make it work through IMagick PHP library, but using proc_open leads to exactly same result as when calling from command line:

$imgPath = '/path/to/image.svg';
$svg = file_get_contents($imgPath);

// ... manipulate svg content here if needed

$descriptorSpec = array(
  0 => array("pipe", "r"), // STDIN
  1 => array("pipe", "w"), // STDOUT
  2 => array("pipe", "w"), // STDERR
);

// fd:0 - STDIN, fd:1: STDOUT,
// svg: and png: tell ImageMagick input/output formats
$cmd = "convert svg:fd:0 png:fd:1";

$proc = proc_open($cmd, $descriptorSpec, $pipes, null, null);

if (!is_resource($proc)) {
  throw new RuntimeException("Failed to proc_open: $cmd");
}

fwrite($pipes[0], $svg);
fclose($pipes[0]);

$png = stream_get_contents($pipes[1]);
fclose($pipes[1]);

if (!$png) {
  throw new RuntimeException("Command returned no output: $cmd");
}

$stdErr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
if ($stdErr) {
  throw new RuntimeException("Command $cmd returned error: $stdErr");
}

$exitStatus = proc_close($proc);

if ($exitStatus) {
  throw new RuntimeException("Command returned $exitStatus: $cmd");
}

// Render PNG with appropriate headers, or save to file, or return etc

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM