Convertir imagen SVG a PNG con PHP

111

Estoy trabajando en un proyecto web que involucra un mapa generado dinámicamente de los Estados Unidos coloreando diferentes estados basados ​​en un conjunto de datos.

Este archivo SVG me da un buen mapa en blanco de los EE. UU. Y es muy fácil cambiar el color de cada estado. La dificultad es que los navegadores IE no son compatibles con SVG, por lo que para poder utilizar la útil sintaxis que ofrece svg, tendré que convertirlo a JPG.

Idealmente, me gustaría hacer esto solo con la biblioteca GD2, pero también podría usar ImageMagick. No tengo ni idea de cómo hacer esto.

Se considerará cualquier solución que me permita cambiar dinámicamente los colores de los estados en un mapa de los EE. UU. La clave es que es fácil cambiar los colores sobre la marcha y que es un navegador cruzado. Solo soluciones PHP / Apache, por favor.

Michael Berkompas
fuente
¿Hay clases diseñadas para portar SVG a VML? de esa manera aún podría tener una solución de tipo 'HTML5'
Patrick
echa un vistazo a mi respuesta. exactamente lo que necesita

Respuestas:

142

Es gracioso que hayas preguntado esto, acabo de hacer esto recientemente para el sitio de mi trabajo y estaba pensando que debería escribir un tutorial ... Así es como se hace con PHP / Imagick, que usa ImageMagick:

$usmap = '/path/to/blank/us-map.svg';
$im = new Imagick();
$svg = file_get_contents($usmap);

/*loop to color each state as needed, something like*/ 
$idColorArray = array(
     "AL" => "339966"
    ,"AK" => "0099FF"
    ...
    ,"WI" => "FF4B00"
    ,"WY" => "A3609B"
);

foreach($idColorArray as $state => $color){
//Where $color is a RRGGBB hex value
    $svg = preg_replace(
         '/id="'.$state.'" style="fill:#([0-9a-f]{6})/'
        , 'id="'.$state.'" style="fill:#'.$color
        , $svg
    );
}

$im->readImageBlob($svg);

/*png settings*/
$im->setImageFormat("png24");
$im->resizeImage(720, 445, imagick::FILTER_LANCZOS, 1);  /*Optional, if you need to resize*/

/*jpeg*/
$im->setImageFormat("jpeg");
$im->adaptiveResizeImage(720, 445); /*Optional, if you need to resize*/

$im->writeImage('/path/to/colored/us-map.png');/*(or .jpg)*/
$im->clear();
$im->destroy();

los pasos de reemplazo de color de expresiones regulares pueden variar según la ruta de svg xml y cómo se almacenan los valores de identificación y color. Si no desea almacenar un archivo en el servidor, puede generar la imagen como base 64 como

<?php echo '<img src="data:image/jpg;base64,' . base64_encode($im) . '"  />';?>

(antes de usar borrar / destruir) pero es decir, tiene problemas con PNG como base64, por lo que probablemente tenga que generar base64 como jpeg

puede ver un ejemplo aquí que hice para el mapa del territorio de ventas de un antiguo empleador:

Inicio: https://upload.wikimedia.org/wikipedia/commons/1/1a/Blank_US_Map_(states_only).svg

Terminar: ingrese la descripción de la imagen aquí

Editar

Desde que escribí lo anterior, se me ocurrieron 2 técnicas mejoradas:

1) en lugar de un bucle de expresiones regulares para cambiar el relleno en el estado, use CSS para hacer reglas de estilo como

<style type="text/css">
#CA,#FL,HI{
    fill:blue;
}
#Al, #NY, #NM{
    fill:#cc6699;
}
/*etc..*/
</style>

y luego puede hacer un solo reemplazo de texto para inyectar sus reglas css en el svg antes de continuar con la creación de imagick jpeg / png. Si los colores no cambian, verifique que no tenga ningún estilo de relleno en línea en sus etiquetas de ruta que anulen el CSS.

2) Si no tiene que crear un archivo de imagen jpeg / png (y no necesita admitir navegadores obsoletos), puede manipular el svg directamente con jQuery. No puede acceder a las rutas de svg cuando incrusta el svg usando img o etiquetas de objeto, por lo que tendrá que incluir directamente el svg xml en su página web html como:

<div>
<?php echo file_get_contents('/path/to/blank/us-map.svg');?>
</div>

luego cambiar los colores es tan fácil como:

<script type="text/javascript" src="/path/to/jquery.js"></script>
<script type="text/javascript">
    $('#CA').css('fill', 'blue');
    $('#NY').css('fill', '#ff0000');
</script>
WebChemist
fuente
1
Gracias por el tutorial muy exacto y útil sobre cómo hacer esto. Ciertamente usaré su solución como respaldo, pero estoy ansioso por probar y obtener compatibilidad con svg en todos los principales navegadores.
Michael Berkompas
1
SVG no es compatible con ie8 o versiones anteriores sin requerir que el usuario instale un complemento de visor de svg, desde la página de Wikipedia de SVG: "Todos los principales navegadores web modernos admiten y procesan el marcado SVG directamente con la notable excepción de Microsoft Internet Explorer (IE) [ 3] La versión beta de Internet Explorer 9 admite el conjunto de funciones básicas de SVG. [4] Actualmente, la compatibilidad con los navegadores que se ejecutan en Android también es limitada ".
WebChemist
1
Sí, pero svgweb parece solucionar todas las incompatibilidades usando un poco de js y flash. Esa es la solución con la que fui.
Michael Berkompas
2
Me gusta tu solución limpia y rápida. Personalmente, cuando interactúo con archivos xml, prefiero usar un analizador dom para sentirme más seguro que con expresiones regulares. Algo como:$dom = new DOMDocument(); $dom->loadXML( $svg ); $dom->getElementsByTagName('image')->item(0)->setAttribute('id', $state); $svg = $dom->saveXML();
Tapper
un analizador xml sería una solución más segura, aunque un poco más lenta, con cualquier otro svg ... en este caso, la expresión regular es segura porque me aseguré de que los atributos de cada estado estuvieran formateados exactamente como (id = "XX" style = "fill: # XXXXXX ").
WebChemist
11

Mencionas que estás haciendo esto porque IE no es compatible con SVG.

La buena noticia es que IE hace gráficos de vectores de soporte. De acuerdo, tiene la forma de un lenguaje llamado VML que solo admite IE, en lugar de SVG, pero está ahí y puedes usarlo.

Google Maps, entre otros, detectará las capacidades del navegador para determinar si sirve SVG o VML.

Luego está la biblioteca Raphael , que es una biblioteca de gráficos basada en navegador de Javascript, que admite SVG o VML, de nuevo dependiendo del navegador.

Otro que puede ayudar: SVGWeb .

Todo lo cual significa que puede brindar soporte a sus usuarios de IE sin tener que recurrir a gráficos de mapa de bits.

Consulte también la respuesta principal a esta pregunta, por ejemplo: XSL Transform SVG to VML

Spudley
fuente
+1 por mencionar a raphael, que definitivamente es una buena solución y vale la pena investigar por su excelente implementación de gráficos vectoriales en varios navegadores.
dmp
10

Al convertir SVG a PNG transparente, no olvide poner esto ANTES $imagick->readImageBlob():

$imagick->setBackgroundColor(new ImagickPixel('transparent'));
psycho brm
fuente
¿Cómo es posible llamar a ese método antes de leer la imagen? Recibo un error "No se puede procesar el objeto Imagick vacío". Y sí, mi extensión imagick está instalada ya que está funcionando y convirtiendo imágenes.
Denis2310
6

Esto es muy fácil, he estado trabajando en esto durante las últimas semanas.

Necesita el kit de herramientas Batik SVG . Descargue y coloque los archivos en el mismo directorio que el SVG que desea convertir a JPEG , también asegúrese de descomprimirlo primero.

Abra la terminal y ejecute este comando:

java -jar batik-rasterizer.jar -m image/jpeg -q 0.8 NAME_OF_SVG_FILE.svg

Eso debería generar un JPEG del archivo SVG. Realmente fácil. Incluso puede colocarlo en un bucle y convertir montones de SVG,

import os

svgs = ('test1.svg', 'test2.svg', 'etc.svg') 
for svg in svgs:
    os.system('java -jar batik-rasterizer.jar -m image/jpeg -q 0.8 '+str(svg)+'.svg')
Willi Mentzel
fuente
Esto es genial. Gracias por el consejo. Lo usaré junto con perl para procesar por lotes cargas de archivos SVG que creé a partir de una plantilla.
Simbabque
2

No conozco una solución PHP / Apache independiente, ya que esto requeriría una biblioteca PHP que pueda leer y renderizar imágenes SVG. No estoy seguro de que exista una biblioteca así, no conozco ninguna.

ImageMagick es capaz de rasterizar archivos SVG, ya sea a través de la línea de comandos o el enlace PHP, IMagick , pero parece tener una serie de peculiaridades y dependencias externas como se muestra, por ejemplo, en este hilo del foro . Creo que sigue siendo el camino más prometedor a seguir, es lo primero que investigaría si fuera usted.

Pekka
fuente
2

Este es un método para convertir una imagen svg en un gif utilizando herramientas estándar php GD

1) Pones la imagen en un elemento de lienzo en el navegador:

<canvas id=myCanvas></canvas>

<script>
var Key='picturename'
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
base_image = new Image();
base_image.src = myimage.svg;
base_image.onload = function(){

    //get the image info as base64 text string

    var dataURL = canvas.toDataURL();
    //Post the image (dataURL) to the server using jQuery post method
    $.post('ProcessPicture.php',{'TheKey':Key,'image': dataURL ,'h': canvas.height,'w':canvas.width,"stemme":stemme } ,function(data,status){ alert(data+' '+status) });
}
</script>    

Y luego conviértalo en el servidor (ProcessPicture.php) de png (predeterminado) a gif y guárdelo. (también podría haber guardado como png y luego usar imagepng en lugar de image gif):

//receive the posted data in php
$pic=$_POST['image'];
$Key=$_POST['TheKey'];
$height=$_POST['h'];
$width=$_POST['w'];
$dir='../gif/'
$gifName=$dir.$Key.'.gif';
 $pngName=$dir.$Key.'.png';

//split the generated base64 string before the comma. to remove the 'data:image/png;base64, header  created by and get the image data
$data = explode(',', $pic);
$base64img = base64_decode($data[1]);
$dimg=imagecreatefromstring($base64img); 

//in order to avoid copying a black figure into a (default) black background you must create a white background

$im_out = ImageCreateTrueColor($width,$height);
$bgfill = imagecolorallocate( $im_out, 255, 255, 255 );
imagefill( $im_out, 0,0, $bgfill );

//Copy the uploaded picture in on the white background
ImageCopyResampled($im_out, $dimg ,0, 0, 0, 0, $width, $height,$width, $height);

//Make the gif and png file 
imagegif($im_out, $gifName);
imagepng($im_out, $pngName);
oleviolina
fuente
-1

Puede usar Raphaël — JavaScript Library y lograrlo fácilmente. También funcionará en IE.

Lokesh Kumar Ravi
fuente
2
Agregue los detalles presentes en el enlace. El enlace puede romperse en cualquier momento
Sagar Jain
-1
$command = 'convert -density 300 ';
                        if(Input::Post('height')!='' && Input::Post('width')!=''){
                            $command.='-resize '.Input::Post('width').'x'.Input::Post('height').' ';
                        }
                        $command.=$svg.' '.$source;
                        exec($command);
                        @unlink($svg);

o usando: demostración de potrace : Tool4dev.com

Thành NV
fuente