screenshot_to_pdf

Screenshot to PDF

In Zeiten des Format-Wirrwarrs und Rechte-Managements verbleibt das Bildschirmfoto (Screenshot) als universelles Datenaustauschformat. Zwar degeneriert hierbei jeglicher Content zur Bitmap-Grafik, welche allerdings für viele Zwecke ausreicht.

Ein Problem stellen dabei größere Datenmengen dar, die automatisiert aufgenommen und bearbeitet werden müssen. Für den einfachen Fall des Inhalts eines größeren Fensters, das mit Bildlaufleisten (scrollbars) ausgestattet ist, gibt es zwar spezialisierte Programme, allerdings scheitern diese oft an den Datenmengen, da diese nicht mehr als große Bitmap-Grafik behandelt werden können.

Weiter stellt die Strukturierung der aufgenommenen Daten ein Problem dar. Gut ist es, wenn sich bereits die Datenstruktur in der Aufnahme der Bildschirmfotos widerspiegelt. Etwa wenn sich eine Seite, eine Tabelle oder eine Grafik komplett auf den Bildschirm darstellen und aufnehmen lässt. Allerdings reicht für diese Vorgehensweise die Auflösung des Bildschirms häufig nicht aus.

Für diese komplexen Fälle existieren keine Spezialprogramme, so dass der Benutzer auf allgemeine Werkzeuge eigene Skript-Programmierung angewiesen ist.

Im folgenden wird der Arbeitsablauf (Workflow) anhand eines Beispiels vorgestellt. Eine im Acrobat-Reader dargestellte PDF-Datei mit ca. 1000 Seiten soll abfotografiert und der Inhalt in eine weitere PDF-Datei konvertiert werden. Dieser Text darf nicht als fertige Anleitung betrachtet werden, da der ganze Prozess mit vielen Randbedingungen, wie z.B. dem Betriebssystem, der Bildschirmauflösung, usw. unterliegt, die alleine vom Benutzer kontrolliert werden können. Vielmehr sollen die Werkzeuge und die allgemeine Vorgehensweise vorgestellt werden. Die gezeigten Skripten und Kommandozeilen müssen für die eigene Anwendung verstanden und angepasst werden. Es wird dem Leser empfohlen den gesamten Ablauf zuerst mit kleinerer Datenmenge auszuprobieren.

Werkzeuge

Zum Einsatz kommt Debian-Linux mit der Kommandozeile Borne-again-Shell (BASH) und die Programmiersprache Perl.

Für das Erstellen der Screenshots wurden die Perl-Module X11::GUITest und Imager::Screenshot verwendet, welche aus CPAN geladen und mittels dh-make-perl in Debianpakete übersetzt und installiert wurden. Die Übersetzung des Perl-Moduls X11::GUITest mittels dh-make-perl gelang erst, als in der Datei Makefile.PL die Zeichenkette (Dateiname) X11-GUITest.POD~ durch X11-GUITest.POD. ersetzt wurde. Da die Datei X11-GUITest.POD~ nach der Programmerstellung (make) als temporäre Datei gelöscht wird.

Für die Grafikbearbeitung wurde die Bibliothek Imagemagick verwendet. Und zwar auf der Kommandozeile mit den Programmen mogrify und convert (Debianpaket: imagemagick), sowie die Perl-Schnittstelle PerlMagick. (Debianpaket: perlmagick)

Für die Konvertierung wurden die zur Grafikbibliothek libtiff gehörenden Programme tiffcp und tiff2pdf verwendet. (Debianpaket: libtiff-tools)

Aufnahme der Screenshots

Die Erzeugung der Bildschirmfotos muss automatisiert erfolgen, da ein manuelles Erstellen aufgrund des Zeitbedarfs, möglicher Fehlbedienungen und der eintönigen Arbeit schlicht unmöglich ist. Zum Einsatz kommt deshalb ein Perlscript, das das Programm (hier: Acrobat-Reader) steuert, und den Bildschirm aufnimmt.

#!/usr/bin/perl
 
use warnings;
use strict;
 
use Imager::Screenshot qw(screenshot);
use X11::GUITest qw(StartApp WaitWindowLike SetInputFocus SendKeys);
 
StartApp('/home/michael/opt/acroread81/Adobe/Reader8/bin/acroread beispiel.pdf');
 
my ($WindowId) = WaitWindowLike('- Adobe Reader') or die("window not found!");;
 
print "[ENTER]";getc(STDIN);
 
SetInputFocus($WindowId);
 
my $z = 0;
while(1) {
   my $img = screenshot(id => $WindowId);
   $img->write(file => sprintf("screenshot%04s.png",$z));
 
   SendKeys("{PGD}");
   sleep(1);
   $z++;
}

Das Skript startet das Programm (Acrobat Reader mit der PDF-Datei) und wartet danach auf einen Tastendruck, so dass der Benutzer die Möglichkeit hat Einstellungen vorzunehmen. Danach werden im einer Schleife die Screenshots des Programmfensters aufgenommen und das Programm mittels Tastendruck (Page down) gesteuert. Nach dem Tastendruck wird eine Sekunde gewartet um den Programm die Möglichkeit zu geben den Inhalt darzustellen.

Im Beispiel wurden insgesamt 2141 Screenshots mit 1005×739 Pixel in 40 Minuten aufgenommen. Die Datenmenge betrug etwa 190 Megabyte.

Datenbereich feststellen und ausschneiden

Ein Screenshot des Programmfensters enthält nicht nur den Datenbereich, sondern auch unerwünschte Darstellungen wie z.B. Steuerelemente (Menü, Bildlauf- und Statuszeilen). Die Pixelposition und -Länge/-Breite des erwünschten Bereichs wird in einem Bildbearbeitungsprogramm festgestellt.

Im Beispiel betrug die Breite/Länge des Datenbereichs 980/680 Pixel mit der linken oberen Ecke bei Position 4/59. Er wurde mit folgender Kommandozeile ausgeschnitten:

for file in *.png; do echo $file; mogrify -crop '980x680!+4+59' +repage $file; done

Überlappungen feststellen und wegschneiden

Üblicherweise ist es so, das sich Bereiche zwischen den Screenshots überlappen. Schön ist es, wenn diese Überlappungen konstant wären. Allerdings hängen diese oft von äußeren Bedingungen und den angezeigten Daten ab.

Im Beispiel des „Acrobat Readers“ wurde beobachtet, dass wenn die Seite am unteren Rand endet, die nächste Seite am oberen Rand neu ausgerichtet wird. Um dies genauer zu verifizieren wurde folgendes Perlskript geschrieben, das durch die Screenshots iteriert und die Pixelanzahl pro Seite ausgibt.

use strict;
use Image::Magick;
 
my($image, $x);
my $state = 0;
my $zpix = 0;
my $bpix = 0;
my $zfile = 0;
for (my $z=1; $z<2142; $z++) {
  $image = Image::Magick->new;
 
  $x = $image->Read(sprintf("screenshot%04s.png",$z));
  die "$x" if "$x";
  $zfile++;
 
  my ($width, $height) = $image->Get('columns','rows');
 
  for (my $z1=0; $z1<$height; $z1++) {
    my $blue = $image->GetPixel(x=>0,y=>$z1,channel=>'Blue');
    my $red = $image->GetPixel(x=>0,y=>$z1,channel=>'Red');
    my $yellow = $image->GetPixel(x=>0,y=>$z1,channel=>'Yellow');
 
    # print $state," ",$blue," ",$red," ",$yellow,"\n",;
 
    if ($state == 0) {
      if ($blue == 1 && $red == 1 && $yellow == 1) { 
        print "black: $bpix\n";
        $bpix = 0;
        $state = 1
      } else {
        $bpix++;
      }
    } else {
      if ($blue != 1 || $red != 1 || $yellow != 1) { 
         print "$z: höhe:$zpix files:$zfile\n";
         $state = 0;
         $zfile = 1;
         $zpix = 0;
      } else {
         $zpix++;
      }
    }
  }
}

Anhand der Ausgabe des Programms wurde festgestellt, dass lediglich jeder 17te Screenshot (jede 8. Seite) eine andere Überlappung aufweist als der Rest. Die Länge der Überlappungen wurde wieder im Bildbearbeitungsprogramm festgestellt. Sie betrug 16 Pixel und bei den 17ten Screenshots 104 Pixel.

Um diese Überlappungen zu entfernen wurde folgendes Bash-Skript erstellt:

#!/bin/bash
for file in *.png; do 
   echo $file
   if [ $(( 10#${file:10:4} % 17 )) == 16 ]; then 
      mogrify -crop '980x576!+0+0' +repage $file
   else
      mogrify -crop '980x664!+0+0' +repage $file
   fi
done

Der Erfolg kann mit obigen Perl-Skript kontrolliert werden.

Ein Ausnahme stellt der letzte bzw. zweitletzte Screenshot dar. Da der Scrollbereich am Ende ist, tritt hier eine größere Überlappung auf, die manuell korrigiert werden muss.

Daten zusammensetzen und schneiden

Bisher liegen die Daten unstrukturiert in einzelnen Screenshots vor. Nun müssen diese zusammengesetzt werden, dass eine sinnvolle Struktur entsteht. Die gesamten Daten in eine einzige fortlaufende Grafik zu speichern, wäre mit einem Kacheltiff zwar möglich, in unserem Falle aber nicht sinnvoll, da diese Grafikdateien nur mit Spezialbetrachtern angesehen werden können.

In unserem Falle der PDF-Datei ist es sinnvoll für die weitere Bearbeitung die Screenshots zu einzelnen Seiten zusammenzusetzen und diese jeweils in einer separaten Datei zu speichern.

Dazu wurde folgendes Perl-Skript erstellt:

#!/usr/local/bin/perl
 
use strict;
use Image::Magick;
 
my $pagesize = 1392;
my $page = Image::Magick->new(size=>"980x$pagesize");
my $x = $page->ReadImage('xc:red');
die "$x" if "$x";
 
my $state = 'bp';
my $zp = 0; my $zb = 4;
my $pz = 0;
my $z = 0; while($z < 2142) {
 
  my $image = Image::Magick->new;       
  $x = $image->Read(sprintf("screenshot%04s.png",$z));
  die "$x" if "$x";
 
  print "$z: $pz\n";
 
  if ($state eq 'bp') {
    my ($width, $height) = $image->Get('columns','rows');
    $x = $image->Crop(x => 0, y => $zb, width=> $width, height => $height - $zb);
    die "$x" if "$x";
    $state = 'ip';          
  }
  my ($width, $height) = $image->Get('columns','rows');  
 
  if ($zp + $height >= $pagesize) {
     $zb = ($pagesize - $zp);
     $x = $image->Crop(x => 0, y => 0, width=> $width, height => $zb);
     die "$x" if "$x";
     $zb += 8; if ($zb >= $height) { $zb -= $height; $z++; }
     my ($width, $height) = $image->Get('columns','rows');  
     $state = 'bp';
  }
 
  $x = $page->Composite(image=>$image, compose=>'over', x=>0, y=>$zp);
  die "$x" if "$x";
 
  if ($state eq 'ip') { $z++; $zp += $height; $zb = 0; }
  if ($state eq 'bp') { 
    $zp = 0; 
    $x = $page->Write(filename=>sprintf("page%04s.png",$pz));
    die "$x" if "$x";
    $pz++;
  }
}

Daten konvertieren und komprimieren

Die einzelnen Seiten der PDF-Datei werden nun in Tiff-Dateien konvertiert. Um eine Reduzierung der Datenmenge zu erhalten werden diese gleichzeitig in Graustufenbilder konvertiert. Für den in der PDF-Datei enthaltenen Textinhalt reicht dies aus.

Die folgende Kommandozeile zeigt den Befehl:

for file in page*.png; do echo $file; convert $file -colorspace gray -alpha Off -depth 8 -compress ZIP -density 72x72 -units PixelsPerInch $file.tiff; done

Für diesen Befehl musste ich viel ausprobieren. Es zeigte sich, dass die nachfolgend verwendeten Programme der Tiff-Bibliothek (Library) mit der von ImageMagick standardmäßig verwendeten Komprimierung Adobedeflate Müll produzierten. In einem solchen Fall ist es sinnvoll testweise eine Tiff-Datei, die von einem viel verwendeten Bildbearbeitungsprogramm gespeichert wurde, zu verwenden und im Erfolgsfall diese mit den Parametern von ImageMagick nachzubilden. Das Programm tiffinfo, welches die Eigenschaften einer Tiffdatei ausgibt, hilft dabei.

Seiten zusammensetzen und in PDF konvertieren

Die nun erzeugten Tiffdateien, die jeweils eine Seite beinhalten, werden mit dem Programm tiffcp zu einer Mehrseiten-Tiff-Datei zusammengefasst. Da dabei nur einzelne Seiten nacheinander in die Datei kopiert werden, ist dies ein schneller und speicherschonender Prozess.

tiffcp page*.tiff gesammt.tiff

Diese Datei gesammt.tiff wird mit dem Programm tiff2pdf nun in eine PDF-Datei eingepackt.

tiff2pdf -z -o gesammt.pdf gesammt.tiff

Die erzeugte PDF ist mit 61 MByte nur knapp 5,5 mal größer als die 11 MByte große Originaldatei.

screenshot_to_pdf.txt · Zuletzt geändert: 2014/09/13 19:12 (Externe Bearbeitung)

Seiten-Werkzeuge