Jak wyodrębnić osadzony obraz z pliku SVG?

26

Mam w środku plik SVG, który zawiera co najmniej jeden osadzony obraz JPG / PNG. Chcę wyodrębnić obrazy JPG / PNG z tego pliku SVG i zapisać je na dysku.

Dodaję inkscapetag, ponieważ jest to program, którego używam do edycji plików SVG, ale akceptuję również rozwiązania przy użyciu innych narzędzi.

Denilson Sá Maia
źródło
1
Jeśli nic więcej, Python prawdopodobnie mógłby to zrobić za pomocą niestandardowego kleju przy użyciu lxml i PIL (lub odpowiednika).
Keith
@ Keith, właśnie napisałem skrypt Pythona, aby rozwiązać to pytanie. Korzysta z wbudowanej xml.etreebiblioteki.
Denilson Sá Maia,

Odpowiedzi:

30

Moje własne rozwiązanie (lub ... obejście):

  1. Wybierz obraz w Inkscape
  2. Otwórz wbudowany XML Editor( Shift+ Ctrl+ X)
  3. Wybierz xlink:hrefatrybut, który będzie zawierał obraz jako dane: URI
  4. Skopiuj cały data:identyfikator URI
  5. Wklej ten data:identyfikator URI do przeglądarki i stamtąd go zapisz.

Alternatywnie mogę otworzyć plik SVG w dowolnym edytorze tekstu, zlokalizować data:identyfikator URI i stamtąd go skopiować.

Chociaż to rozwiązanie działa, jest trochę kłopotliwe i chciałbym nauczyć się lepszego.

Denilson Sá Maia
źródło
2
+1 - Wyeksportowałem obraz 3,5 MB przy użyciu tej metody, co zajęło trochę czasu, ale zadziałało. Jakoś funkcja „Wyodrębnij obraz” nie działała dla mnie.
Martin
Zobacz także w tym celu skrypt wiersza polecenia w języku Python .
Denilson Sá Maia
17

Zamiast tego istnieje lepsze rozwiązanie:

przejdź do Extensions -> Images -> Extract Image..., tam możesz zapisać wybrany obraz rastrowy jako plik. Jednak to rozszerzenie działa dziwnie i jakoś działa raczej powoli (ale idealnie dobrze).

Kolejna uwaga: to rozszerzenie jest nieporęczne i umiera cicho na różnych dużych obrazach. Ponadto przy dużej liczbie obrazów rastrowych może zwiększyć wykorzystanie pamięci Inkscape do przerażających poziomów (jak 3 GB po wyodrębnieniu tylko kilku zdjęć).

Ponieważ mam około 20 plików SVG z około 70 obrazami rastrowymi, każdy obraz o wielkości co najmniej 1 MB, potrzebowałem innego rozwiązania. Po krótkim sprawdzeniu za pomocą końcówki Denilson Sá opracowałem następujący skrypt php, który wyodrębnia obrazy z plików svg:

#!/usr/bin/env php
<?php

$svgs = glob('*.svg');

$existing = array();

foreach ($svgs as $svg){
    mkdir("./{$svg}.images");
    $lines = file($svg);
    $img = 0;
    foreach ($lines as $line){
        if (preg_match('%xlink:href="data:([a-z0-9-/]+);base64,([^"]+)"%i', $line, $regs)) {
            $type = $regs[1];
            $data = $regs[2];
            $md5 = md5($data);
            if (!in_array($md5, $existing)) {
                $data = str_replace(' ', "\r\n", $data);
                $data = base64_decode($data);
                $type = explode('/', $type);
                $save = "./{$svg}.images/{$img}.{$type[1]}";
                file_put_contents($save, $data);
                $img++;
                $existing[] = $md5;
            }
        } else {
            $result = "";
        }
    }
}

echo count($existing);

W ten sposób mogę uzyskać wszystkie potrzebne obrazy, a md5 ratuje mnie przed powtarzaniem się.

Założę się, że musi być inny sposób, który jest o wiele prostszy, ale twórcy inkscape muszą to zrobić lepiej.

Johnny_Bit
źródło
Uwaga: Twój skrypt obsługuje tylko jeden data:adres URL w wierszu i nie obsługuje znaków nowej linii w atrybucie href (inkscape dodaje je do adresów URL danych, a specyfikacja base64 nawet nakazuje, aby wiersze nie miały więcej niż 76 znaków ). Fajny skrypt do szybkiego włamania, ale nie działa ze wszystkimi rodzajami SVG.
Denilson Sá Maia,
@Johnny_Bit +1 za użycie sumy md5, aby zapobiec duplikacji plików. Poprawiłem twój skrypt poniżej .
Ivan Z
dobry, marzec 2019 i pracował łatwo, z dość dużym wizerunkiem. I całkiem stary laptop / ubuntu / inkscape 0.48.4. Dzięki!
Gaoithe
9

Wreszcie, lata później napisałem skrypt, aby poprawnie wyodrębnić wszystkie obrazy z pliku SVG, używając odpowiedniej biblioteki XML do analizy kodu SVG.

http://bitbucket.org/denilsonsa/small_scripts/src/tip/extract_embedded_images_from_svg.py

Ten skrypt został napisany dla Pythona 2.7, ale jego konwersja do Pythona 3 powinna być łatwa. Co więcej, po konwersji do Pythona 3.4 można usunąć około 50 wierszy ze względu na nowe funkcje wprowadzone w tej wersji.

Denilson Sá Maia
źródło
Dzięki, ponieważ to działa. Ale jest znacznie wolniejszy niż obejście PDF. Czy myślałeś o równoległym przetwarzaniu? W tej chwili skrypt używa tylko jednego rdzenia / wątku procesora.
DanMan
@DanMan Niestety, uczynienie go równoległym nie jest magicznym rozwiązaniem do przyspieszenia czegokolwiek. Musiałbym profilować kod, aby zidentyfikować wąskie gardło. Jeśli wąskim gardłem jest parsowanie XML, przepraszam, tej części nie można wykonać równolegle. Czy możesz przesłać mi pocztą e-mail dokładne pliki SVG, które są zbyt wolne? Ilekroć mam trochę czasu, mogę sprawdzić wyniki.
Denilson Sá Maia,
Tak, próbowałem to zrobić sam i okazało się, że parsowanie XML jest wolną częścią, a nie dekodowaniem obrazów. To powiedziawszy, cElementTreema być szybsze. Ale może coś takiego jak Sax również działa lepiej.
DanMan
@DanMan cElementTreejest prawdopodobnie szybszy. Jednak w Pythonie 3.3 oba są takie same . W pewnym momencie prawdopodobnie zaktualizuję ten skrypt do Pythona 3.
Denilson Sá Maia
5

Jako kolejne obejście można zapisać jako plik PDF, a następnie otworzyć ten dokument za pomocą Inkscape.

Odznacz „osadzanie obrazów” i bingo, wszystkie pngs / jpegs zostaną wyrzucone do twojego katalogu domowego.

Bałagan, ale szybciej niż wygłupiać się z danymi: URL.

Nicholas Wilson
źródło
Gdzie znalazłeś tę opcję „osadzania obrazów”?
mik01aj
1
Po otwarciu dokumentu PDF w programie inkscape znajduje się on w następnym oknie dialogowym.
Nicholas Wilson
Miałem plik PDF, z którego próbowałem wyodrębnić obraz, importując go do Inkscape. W takim przypadku możliwość zrobienia tego podczas importu zamiast po imporcie jest jeszcze bardziej przydatna.
user149408
Nie jestem pewien, ale w ten sposób wszelkie osadzone profile ICC wydają się gubić w tym procesie. Obrazy wyodrębnione bezpośrednio z SVG za pomocą tego skryptu Python mają osadzone profile ICC.
DanMan
1

Poprawiam skrypt php @Johnny_Bit . Nowa wersja skryptu może używać svg z nowymi wierszami. Wyodrębnia wiele obrazów z pliku svg i zapisuje je w zewnętrznych plikach png. Pliki SVG i PNG znajdują się w katalogu „SVG”, ale można to zmienić w stałej „SVG_DIR”.

<?php

define ( 'SVG_DIR', 'svg/' );
define ( 'SVG_PREFIX', 'new-' );

$svgs = glob(SVG_DIR.'*.svg');
$external = array();
$img = 1;

foreach ($svgs as $svg) {
    echo '<p>';
    $svg_data = file_get_contents( $svg );
    $svg_data = str_replace( array("\n\r","\n","\r"), "", $svg_data);
    $svg_file = substr($svg, strlen(SVG_DIR) );
    echo $svg_file.': '.strlen($svg_data).' ????';

    if ( preg_match_all( '|<image[^>]+>|', $svg_data, $images, PREG_SET_ORDER) ) {
        foreach ($images as $image_tag) {

            if ( preg_match('%xlink:href="data:([a-z0-9-/]+);base64,([^"]+)"%i', $image_tag[0], $regs) ) {
                echo '<br/>Embeded image has benn saved to file: ';

               $type = $old_type = $regs[1];
               $data = $old_data = $regs[2];
               $md5 = md5($data);
               if ( array_key_exists($md5, $external) ) {
                $image_file = $external[$md5];
               } else {
                    $data = str_replace(" ", "\r\n", $data);
                    $data = base64_decode($data);
                    $type = explode('/', $type);
                    $image_file = substr( $svg_file, 0, strlen($svg_file)-4 ) . '-' . ($img++) . '.png';
                    file_put_contents(SVG_DIR.$image_file, $data);
                    $external[$md5] = $image_file;
               }
               echo $image_file;
               $svg_data = str_replace('xlink:href="data:'.$old_type.';base64,'.$old_data.'"', 'xlink:href="'.$image_file.'"', $svg_data);
            }
        }
        file_put_contents(SVG_DIR.SVG_PREFIX.'.svg', $svg_data);
    }

   echo '</p>';
}

?>
Ivan Z
źródło
0

Otwórz plik w Inkscape i wybierz mapę bitową, którą chcesz wyeksportować. Kliknij Plik-> Eksportuj bitmapę (Ctrl + Shift + E) i powinna wyeksportować tylko wybraną bitmapę.

Chris
źródło
Nie podoba mi się to rozwiązanie, ponieważ ponownie koduje obraz. Wolałbym rozwiązanie, które wyodrębnia obraz w oryginalnym formacie.
Denilson Sá Maia,
1
Tak, wygląda na to, że Inkscape ponownie koduje obraz, ale domyślnie zapisuje obrazy PNG. Zakładam więc, że ponowne kodowanie jest co najmniej bezstratne.
Chris
1
No nie bardzo. Osadzony obraz mógł mieć transformacje (skalowanie, obrót…), mógł zostać przycięty, a nawet coś innego, czego nie jestem świadomy. Inkscape z pewnością wyeksportuje wybrany obiekt po zastosowaniu wszystkich tych transformacji, co oznacza, że ​​to rozwiązanie nie jest dokładnie bezstratne.
Denilson Sá Maia,