Converter imagem SVG para PNG com PHP

111

Estou trabalhando em um projeto da web que envolve um mapa gerado dinamicamente dos EUA, colorindo diferentes estados com base em um conjunto de dados.

Este arquivo SVG me dá um bom mapa em branco dos EUA e é muito fácil mudar a cor de cada estado. A dificuldade é que os navegadores IE não suportam SVG, então, para usar a sintaxe útil que o svg oferece, vou precisar convertê-la para JPG.

Idealmente, gostaria de fazer isso apenas com a biblioteca GD2, mas também poderia usar o ImageMagick. Eu não tenho absolutamente nenhuma ideia de como fazer isso.

Qualquer solução que me permita alterar dinamicamente as cores dos estados em um mapa dos EUA será considerada. A chave é que é fácil mudar as cores rapidamente e que é um navegador cruzado. Somente soluções PHP / Apache, por favor.

Michael Berkompas
fonte
Existem classes projetadas para portar SVG para VML? dessa forma, você ainda pode ter uma solução do tipo 'HTML5'
Patrick
dê uma olhada na minha resposta. exatamente o que você precisa

Respostas:

142

Engraçado você ter perguntado isso, acabei de fazer isso recentemente para o site do meu trabalho e estava pensando em escrever um tutorial ... Veja como fazer isso com 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();

as etapas de substituição de cor regex podem variar dependendo do caminho svg xml e como os valores de id e cor são armazenados. Se você não quiser armazenar um arquivo no servidor, pode produzir a imagem como base 64, como

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

(antes de usar limpar / destruir), mas ou seja, tem problemas com PNG como base64, então você provavelmente terá que imprimir base64 como jpeg

você pode ver um exemplo que fiz aqui para um mapa do território de vendas de um ex-empregador:

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

Terminar: insira a descrição da imagem aqui

Editar

Desde que escrevi acima, criei 2 técnicas aprimoradas:

1) em vez de um loop regex para alterar o estado de preenchimento, use CSS para fazer regras de estilo como

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

e então você pode fazer uma única substituição de texto para injetar suas regras de css no svg antes de prosseguir com a criação de imagick jpeg / png. Se as cores não mudarem, certifique-se de que não haja nenhum estilo de preenchimento embutido nas tags de caminho substituindo o css.

2) Se você não precisa realmente criar um arquivo de imagem jpeg / png (e não precisa suportar navegadores desatualizados), você pode manipular o svg diretamente com jQuery. Você não pode acessar os caminhos svg ao incorporar o svg usando tags img ou object, então você terá que incluir diretamente o svg xml no html da sua página da web como:

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

então mudar as cores é tão fácil quanto:

<script type="text/javascript" src="/path/to/jquery.js"></script>
<script type="text/javascript">
    $('#CA').css('fill', 'blue');
    $('#NY').css('fill', '#ff0000');
</script>
WebChemist
fonte
1
Obrigado pelo tutorial muito exato e útil sobre como fazer isso. Certamente usarei sua solução como backup, mas estou ansioso para tentar obter compatibilidade com svg em todos os principais navegadores.
Michael Berkompas
1
SVG não é suportado em ie8 ou inferior sem exigir que o usuário instale um plugin do visualizador SVG - da página da Wikipedia SVG: "Todos os principais navegadores da web modernos, suportam e renderizam marcação SVG diretamente com a notável exceção do Microsoft Internet Explorer (IE) [ 3] O Internet Explorer 9 beta oferece suporte ao conjunto de recursos básicos do SVG. [4] Atualmente, o suporte para navegadores rodando no Android também é limitado. "
WebChemist
1
Sim, mas o svgweb parece resolver todas as incompatibilidades usando um pouco de js e flash. Essa é a solução que escolhi.
Michael Berkompas
2
Gosto da sua solução limpa e rápida. Pessoalmente, ao interagir com arquivos xml, prefiro usar um analisador dom para me sentir mais seguro do que com regex. Sth like:$dom = new DOMDocument(); $dom->loadXML( $svg ); $dom->getElementsByTagName('image')->item(0)->setAttribute('id', $state); $svg = $dom->saveXML();
Tapper
um analisador xml seria uma solução mais segura, embora ligeiramente mais lenta, com qualquer outro svg ... neste caso, o regex é seguro porque me certifiquei de que os atributos de cada estado foram formatados exatamente como (id = estilo "XX" = "preencher: # XXXXXX ").
WebChemist
11

Você mencionou que está fazendo isso porque o IE não oferece suporte a SVG.

A boa notícia é que o IE oferece suporte a gráficos vetoriais. Ok, então está na forma de uma linguagem chamada VML, que só suporta o IE, em vez de SVG, mas está lá e você pode usá-la.

O Google Maps, entre outros, detectará os recursos do navegador para determinar se deve servir SVG ou VML.

Depois, há a biblioteca Raphael , que é uma biblioteca gráfica baseada em navegador Javascript, que oferece suporte a SVG ou VML, novamente dependendo do navegador.

Outro que pode ajudar: SVGWeb .

Tudo isso significa que você pode oferecer suporte aos usuários do IE sem ter que recorrer a gráficos de bitmap.

Veja também a principal resposta a esta pergunta, por exemplo: XSL Transform SVG to VML

Spudley
fonte
+1 por mencionar raphael, que é definitivamente uma boa solução e vale a pena investigar por sua excelente implementação de gráficos vetoriais para vários navegadores.
dmp
10

Ao converter SVG para PNG transparente, não se esqueça de colocar ANTES $imagick->readImageBlob():

$imagick->setBackgroundColor(new ImagickPixel('transparent'));
psicopata brm
fonte
Como é possível chamar esse método antes de ler a imagem, estou recebendo um erro "Não é possível processar o objeto Imagick vazio". E sim, minha extensão imagick está instalada enquanto está funcionando e convertendo imagens.
Denis2310
6

Isso é muito fácil, tenho trabalhado nisso nas últimas semanas.

Você precisa do Batik SVG Toolkit . Baixe e coloque os arquivos no mesmo diretório que o SVG que deseja converter para JPEG , também certifique-se de descompactá-lo primeiro.

Abra o terminal e execute este comando:

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

Isso deve gerar um JPEG do arquivo SVG. Muito fácil. Você pode até colocá-lo em um loop e converter um monte de SVGs,

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
fonte
Isso é ótimo. Obrigado pela dica. Vou usá-lo em conjunto com o perl para processar em lote cargas de arquivos SVG que criei a partir de um modelo.
simbabque
2

Não conheço uma solução autônoma de PHP / Apache, pois isso exigiria uma biblioteca PHP que pudesse ler e renderizar imagens SVG. Não tenho certeza se essa biblioteca existe - não conheço nenhuma.

ImageMagick é capaz de rasterizar arquivos SVG, seja através da linha de comando ou do vínculo do PHP, IMagick , mas parece ter uma série de peculiaridades e dependências externas como mostrado, por exemplo, neste tópico do fórum . Acho que ainda é o caminho mais promissor a seguir, é a primeira coisa que eu procuraria se fosse você.

Pekka
fonte
2

Este é um método para converter uma imagem SVG em um GIF usando as ferramentas GD php padrão

1) Você coloca a imagem em um elemento de tela no 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>    

Em seguida, converta-o no servidor (ProcessPicture.php) de png (padrão) para gif e salve-o. (você também poderia ter salvo como png e usar imagepng em vez de gif de imagem):

//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
fonte
-1

Você pode usar Raphaël — Biblioteca JavaScript e obtê-lo facilmente. Funcionará no IE também.

Lokesh Kumar Ravi
fonte
2
Adicione os detalhes presentes no link. O link pode quebrar a qualquer 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);

ou usando: potrace demo: Tool4dev.com

Thành NV
fonte