Как изменить размер SVG с помощью Imagick/ImageMagick
Я отправляю строковое представление SVG файла на сервер и используя Imagick, чтобы превратить это в jpeg следующим образом:
$image = stripslashes($_POST['json']);
$filename = $_POST['filename'];
$unique = time();
$im = new Imagick();
$im->readImageBlob($image);
$im->setImageFormat("jpeg");
$im->writeImage('../photos/' . $type . '/humourised_' . $unique . $filename);
$im->clear();
$im->destroy();
Однако я хочу изменить размер SVG до его растеризации, чтобы получившееся изображение было больше размеров, указанных в файле SVG.
Я изменил свой код на следующее:
$image = stripslashes($_POST['json']);
$filename = $_POST['filename'];
$unique = time();
$im = new Imagick();
$im->readImageBlob($image);
$res = $im->getImageResolution();
$x_ratio = $res['x'] / $im->getImageWidth();
$y_ratio = $res['y'] / $im->getImageHeight();
$im->removeImage();
$im->setResolution($width_in_pixels * $x_ratio, $height_in_pixels * $y_ratio);
$im->readImageBlob($image);
$im->setImageFormat("jpeg");
$im->writeImage('../photos/' . $type . '/humourised_' . $unique . $filename);
$im->clear();
$im->destroy();
Этот код должен выработать разрешение и соответствующим образом изменить размер SVG. Он отлично работает, если холст SVG и его элементы имеют ширину, основанную на процентах, однако она не работает с элементами, определенными в 'px'. К сожалению, это требование.
Типичная строка SVG, которая будет отправлена на сервер, выглядит так:
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/SVG/DTD/svg10.dtd">
<svg id="tempsvg" style="overflow: hidden; position: relative;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="333" version="1.1" height="444">
<image transform="matrix(1,0,0,1,0,0)" preserveAspectRatio="none" x="0" y="0" width="333" height="444" xlink:href="#" onclick="location.href='http://www.songbanc.com/assets/embed/photos/full/133578615720079914224f9e7aad9ac871.jpg'; return false;"></image>
<image transform="matrix(1,0,0,1,0,0)" preserveAspectRatio="none" x="85.5" y="114" width="50" height="38" xlink:href="#" onclick="location.href='http://www.songbanc.com/assets/embed/humourise/elements/thumb/thumb_lips4.png'; return false;"></image>
<path transform="matrix(1,0,0,1,0,0)" fill="none" stroke="#000" d="M110.5,133L140.5,133" stroke-dasharray="- " opacity="0.5"></path>
<circle transform="matrix(1,0,0,1,0,0)" cx="140.5" cy="133" r="5" fill="#000" stroke="#000"></circle>
<path transform="matrix(1,0,0,1,0,0)" fill="none" stroke="#000" d="M110.5,133L110.5,155.8" stroke-dasharray="- " opacity="0.5"></path>
<circle transform="matrix(1,0,0,1,0,0)" cx="110.5" cy="155.8" r="5" fill="#000" stroke="#000"></circle>
<circle transform="matrix(1,0,0,1,0,0)" cx="110.5" cy="133" r="5" fill="#000" stroke="#000"></circle>
</svg>
Как вы можете видеть, элементы, составляющие этот SVG, имеют ширину и высоту определения пикселов (использование процентов, к сожалению, не является вариантом для этого приложения)
Есть ли способ обойти это? Или любой другой способ преобразования SVG в png и рендеринг его при заданном размере без потери качества.
Спасибо.
EDIT: Хотя мне никогда не удавалось найти идеальное решение. Вместо этого я закончил передачу SVG-данных как json, прокручивая его серверную сторону и масштабируя пиксели до предполагаемой высоты.
Затем, после долгих проб и ошибок, я понял, что у imagemagick есть проблемы с стандартными командами трансформирования/поворота SVG, которые бросают любые управляемые элементы из-под удара. В итоге я переключил слишком "inkscape", чтобы визуализировать полученный SVG как растрированное изображение. И все хорошо. Я все еще вникаю в потенциальное формульное решение, чтобы компенсировать различия, которые создает imagemagick. Если у меня все получится, я снова обновлю этот вопрос.
Ответы
Ответ 1
В качестве обходного пути ошибки php_imagick вы можете масштабировать svg width = ".." и height = "..":
function svgScaleHack($svg, $minWidth, $minHeight)
{
$reW = '/(.*<svg[^>]* width=")([\d.]+px)(.*)/si';
$reH = '/(.*<svg[^>]* height=")([\d.]+px)(.*)/si';
preg_match($reW, $svg, $mw);
preg_match($reH, $svg, $mh);
$width = floatval($mw[2]);
$height = floatval($mh[2]);
if (!$width || !$height) return false;
// scale to make width and height big enough
$scale = 1;
if ($width < $minWidth)
$scale = $minWidth/$width;
if ($height < $minHeight)
$scale = max($scale, ($minHeight/$height));
$width *= $scale*2;
$height *= $scale*2;
$svg = preg_replace($reW, "\${1}{$width}px\${3}", $svg);
$svg = preg_replace($reH, "\${1}{$height}px\${3}", $svg);
return $svg;
}
Затем вы можете легко создать красивый прозрачный PNG!
createThumbnail('a.svg', 'a.png');
function createThumbnail($filename, $thname, $size=50)
{
$im = new Imagick();
$svgdata = file_get_contents($filename);
$svgdata = svgScaleHack($svgdata, $size, $size);
$im->setBackgroundColor(new ImagickPixel('transparent'));
$im->readImageBlob($svgdata);
$im->setImageFormat("png32");
$im->resizeImage($size, $size, imagick::FILTER_LANCZOS, 1);
file_put_contents($thname, $im->getImageBlob());
$im->clear();
$im->destroy();
}
Примечание:
Я искал решение о том, как масштабировать SVG с его начального небольшого размера. Однако кажется, что imagick:: setResolution нарушен. Однако сама библиотека ImageMagick работает, поэтому вы можете использовать exec ('convert...') (может быть отключен по соображениям безопасности хостинг-провайдером).
Итак, чтобы создать миниатюру 50x50 из меньшего svg, вы бы сделали:
convert -density 500 -resize 50 50 -background transparent a.svg PNG32:a.png
Ответ 2
Я искал решение, и я нашел this сразу после прочтения этого сообщения и работал как прелесть:
$im = new Imagick();
$im->readImage("/path/to/image.svg");
$res = $im->getImageResolution();
$x_ratio = $res['x'] / $im->getImageWidth();
$y_ratio = $res['y'] / $im->getImageHeight();
$im->removeImage();
$im->setResolution($width_in_pixels * $x_ratio, $height_in_pixels * $y_ratio);
$im->readImage("/path/to/image.svg");
// Now you can do anything with the image, such as convert to a raster image and output it to the browser:
$im->setImageFormat("png");
header("Content-Type: image/png");
echo $im;
Кредиты отправляются автору этого комментария на странице справочников php.
Ответ 3
Вам не нужно воображать, чтобы выполнить эту задачу.
Например, вы не будете изменять размер svg (w: 60px, h: 70px) = > (w: 36px, h: 36px), чтобы получить значок для кнопки.
$svg = file_get_contents($your_svg_file);
// I prefer to use DOM, because it safer and easier as to use preg_match
$svg_dom = new DOMDocument();
libxml_use_internal_errors(true);
$svg_dom->loadXML($svg);
libxml_use_internal_errors(false);
//get width and height values from your svg
$tmp_obj = $svg_dom->getElementsByTagName('svg')->item(0);
$svg_width = floatval($tmp_obj->getAttribute('width'));
$svg_height = floatval($tmp_obj->getAttribute('height'));
// set width and height of your svg to preferred dimensions
$tmp_obj->setAttribute('width', 36);
$tmp_obj->setAttribute('height', 36);
// check if width and height of your svg is smaller than the width and
// height you set above => no down scaling is needed
if ($svg_width < 36 && $svg_height < 36) {
//center your svg content in new box
$x = abs($svg_width - 36) / 2;
$y = abs($svg_height - 36) / 2;
$tmp_obj->getElementsByTagName('g')->item(0)->setAttribute('transform', "translate($x,$y)");
} else {
// scale down your svg content and center it in new box
$scale = 1;
// set padding to 0 if no gaps are desired
$padding = 2;
// get scale factor
if ($svg_width > $svg_height) {
$scale = (36 - $padding) / $svg_width;
} else {
$scale = (36 - $padding) / $svg_height;
}
$x = abs(($scale * $svg_width) - 36) / 2;
$y = abs(($scale * $svg_height) - 36) / 2;
$tmp_obj->getElementsByTagName('g')->item(0)->setAttribute('transform', "translate($x,$y) scale($scale,$scale)");
file_put_contents('your_new_svg.svg', $svg_dom->saveXML());
}
Будьте осторожны, установив translate (x, y), потому что может случиться так, что ваш svg-контент можно установить за пределами поля, и вы не увидите ничего, кроме фона.
Мой script выше работает правильно, только если ваш начальный перевод имеет значение (0,0). Вы можете использовать этот
$svg_path = $svg_dom->getElementsByTagName('path')->item(0);
$svg_g = $svg_dom->getElementsByTagName('g')->item(0);
$transform = $svg_g->getAttribute('transform');
// get x and y of translate
$transform = substr($transform, strlen('translate('));
$transform = substr($transform, 0, strlen($transform)-1);
$transform_data = explode(',', $transform);
// get path data
$d = $svg_path->getAttribute('d');
$d_data = explode(' ', $d);
$tmp = explode(',', $d_data[1]);
$d_data[1] = ($tmp[0] + $transform_data[0]).','.($tmp[1]+$transform_data[1]);
$svg_path->setAttribute('d', implode(' ', $d_data));
$svg_g->setAttribute('transform','translate(0,0)');
file_put_contents('your_new_svg.svg',$svg_dom->saveXML());
чтобы установить перевод (0,0) и адаптировать данные пути к новым настройкам, поскольку данные пути зависят от перевода и наоборот.
Я использую эти два скрипта для генерации значков png, изменяя размеры моих значков svg до необходимого размера и преобразовывая их в png без потери качества.
Надеюсь, это ясно, что я имею в виду.
Ответ 4
Вот пример того, как взять изображение, которое уже находится в строке (скажем, из базы данных), и изменить его размер, добавить границу и распечатать ее. Я использую это для отображения логотипов реселлера
// Decode image from base64
$image=base64_decode($imagedata);
// Create Imagick object
$im = new Imagick();
// Convert image into Imagick
$im->readimageblob($image);
// Create thumbnail max of 200x82
$im->thumbnailImage(200,82,true);
// Add a subtle border
$color=new ImagickPixel();
$color->setColor("rgb(220,220,220)");
$im->borderImage($color,1,1);
// Output the image
$output = $im->getimageblob();
$outputtype = $im->getFormat();
header("Content-type: $outputtype");
echo $output;