Hardcopy vom Bildschirm erstellen

Einführung: Aufbau des Hardcopy-Subsystems im PC-BIOS

Beim Drücken von PrtScreen wird im Hintergrund ein DOS-Interrupt 05h ausgelöst, welcher eine sich im BIOS (Basic Input Output System) befindliche Hardcopy-Routine aufruft. Diese eingebaute Hardcopy-Routine tut von sich aus eigentlich nicht mehr als folgendes BASIC-Programmfragment:

SUB Hardcopy
  FOR Zeile%=1 TO 25
    FOR Spalte%=1 TO 80
      LPRINT CHR$(SCREEN(Zeile%,Spalte%));
    NEXT Spalte%
    LPRINT     ' Zeilenumbruch
  NEXT Zeile%
END SUB

Hardcopy programmgesteuert aufrufen

Mit Hilfe des Wissens, dass Interrupt 05h ausgelöst wird, können Sie eine kleine Assembler-Routine

int 05h
retf

ausführen. In der Hilfedatei QBASIC.HLP von QBASIC.EXE befindet sich sogar bei CALL ABSOLUTE folgendes Beispiel:

'Ruft eine Routine auf, die den Bildschirminhalt auf dem Drucker druckt
DIM a%(2)
DEF SEG = VARSEG(a%(0))
FOR i% = 0 TO 2
   READ d%
   POKE VARPTR(a%(0)) + i%, d%
NEXT i%
DATA 205, 5, 203  : ' int 5  retf  'Code in Maschinesprache
                                   'zum Drucken des Bildschirminhalts.
CALL ABSOLUTE(VARPTR(a%(0)))
DEF SEG

Mit dem QuickBASIC-Compiler können Sie sogar CALL INTERRUPT direkt einsetzen:

' $INCLUDE: 'qb.bi'

DIM dosIntEin AS RegType, dosIntAus AS RegType

CALL INTERRUPT(&H5, dosIntEin, dosIntAus)

Da INT 05h keine Parameter erwartet, muss dosIntEin nicht initialisiert werden. Die INT 05h-Hardcopy-Routine existiert beim PC schon seit Beginn mit dem IBM-PC/XT.

Bildschirmausdruck im Grafikmodus

Grafik gab es beim IBM-PC schon sehr früh (CGA- und Hercules-Karte), daher spendierten IBM und Microsoft bereits in DOS 2.x das DOS-Hilfsprogramm GRAPHICS.COM: Dieses TSR-Programm (TSR=Terminate and Stay Resident) biegt den Interrupt-Vektor 05h auf eine eigene Routine um, so dass zuerst der aktuelle Videomodus ermittelt wird, danach entsprechend entweder die BIOS-Routine im Falle von Text oder bei Grafik eben die eigene Routine aufgerufen wird.

Seit Windows 95 ist GRAPHICS.COM nicht mehr im Lieferumfang; Sie müssen daher wie unter Bezug von QuickBASIC beschrieben die Datei OLDDOS.EXE herunterzuladen und installieren. Aufrufbeispiel für einen HP DeskJet-Drucker:

C:\>graphics /b/r hpdeskjet

Ansonsten liefert in MS-DOS 6.2x

C:\>help graphics

eine ausführliche Anleitung zu GRAPHICS.COM. Allerdings hat dieser Weg einen erheblichen Nachteil: Da GRAPHICS.COM nicht mehr von Microsoft weitergepflegt wird, können Sie dementsprechend nur einige wenige, sehr alte Druckermodelle ansteuern, die Druckertreiberliste entspricht also jener vor zehn Jahren! Falls Sie noch einen solchen Drucker (z.B. IBM ProPrinter, Epson-kompatibler 9- oder 24-Nadel-Matrixdrucker, HP DeskJet der ersten Generation) besitzen (stellt jemand überhaupt noch Farbbänder zu diesen Geräten her?), dann sollte es keine Probleme geben. Falls Sie aber üblicherweise einen dieser heutzutage typischen Windows-Billigdrucker verwenden, bei welchen der Hersteller schon lange kein Programmer's Reference Manual belegt, sondern ausschliesslich über den Windows 95/98/NT-Druckertreiber des Herstellers funktioniert, dann haben Sie leider :-( Pech. Wohl könnten Sie versuchen mit Umleiten in eine Datei oder mit Systemsteuerung/Drucker/Verbinden den Anschluss auf FILE: anstelle von LPT1: umstellen, um die Druckerausgabe in eine Datei umzuleiten und mit Hilfe von Norton Commander oder einem ähnlichen Werkzeug den Inhalt dieser Druckerdatei als Hex-Dump betrachten, um den Befehlssatz herauszufinden. Allerdings dürfte dieses Vorhaben bei den heutigen Druckern kaum zu einem Erfolg führen, denn vielfach wird Datenkompression eingesetzt, ebenso mit sog. GDI-Aufrufen (GDI=Graphic Device Interface) gearbeitet, so dass Sie nur mit einem Byte-Wirrwarr begrüsst werden. Ausserdem kommt noch dazu, dass die ganz modernen Drucker bidirektional kommunizieren, um den Füllstand der Tintenpatrone beispielsweise zu melden.

Als ich vor 15 Jahren meine Hobby-Computerei startete, gab es noch keine fertigen Druckertreiber, sondern jeder Hersteller dokumentierte in Form von kleinen Beispiel-BASIC-Programmen die einzelnen Drucker-Features. Dies wandelte sich jedoch ziemlich rasch seit der Einführung von Windows 3.x, als sich eben der Personal-Computer zum reinen Anwendungsgerät hochmauserte, denn von einem Anwender, der lediglich seine Geschäftszahlen mit Harvard Graphics präsentieren möchte, wollte man keine Programmierkenntnisse verlangen, um HG aufsetzen zu können.

Allerdings haben Sie heute dank Internet eine gute Chance, mit geeigneten Suchen in AltaVista und sonstigen Suchdiensten Entwicklerunterstützung (engl. Developer Support) zu Ihrem Drucker besorgen zu können, am besten kontaktieren Sie den Hersteller selber via e-Mail oder suchen in dessen Technical Support-WWW-Bereich nach Entwicklerdokumentationen ab. Dabei sollten Sie den (meist englischsprachigen) internationalen Hauptsitz anpeilen, da die regionalen Ländervertretungen in der Regel nur Verkauf betreiben.

Nach dieser längeren Diskussion betreffend Druckerprogrammierung habe ich unbemerkt bereits die nächste Variante angeschnitten:

Programmierung Ihrer eigenen Hardcopy-Routine

Textbildschirm ausdrucken

Für Textdruck (alle Variationen von SCREEN 0) reicht im Prinzip die zu Beginn dieses Artikels aufgeführte Bildschirmausdruckroutine bereits aus:

SUB Hardcopy
  FOR Zeile%=1 TO 25
    FOR Spalte%=1 TO 80
      LPRINT CHR$(SCREEN(Zeile%,Spalte%));
    NEXT Spalte%
    LPRINT     ' Zeilenumbruch
  NEXT Zeile%
END SUB

Allerdings hat sie noch ein paar erhebliche Mängel: LPRINT sendet die Zeichen cooked. Um dies zu verstehen, sollten Sie am besten Ihren Drucker in den sog. Hex-Dump-Modus umschalten (ältere Drucker haben dies noch, die neuen Modelle jedoch meist nicht mehr). Nun probieren Sie bitte einmal folgendes Programm aus:

FOR i%=0 TO 255
  LPRINT CHR$(i%);
NEXT i%

Unsere Erwartung: Es sollten doch exakt 256 Bytes erscheinen:

0000 : 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0010 : 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
0020 : 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
0030 : 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
0040 : 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
0050 : 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
0060 : 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
0070 : 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
0080 : 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
0090 : 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
00A0 : A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
00B0 : B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
00C0 : C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
00D0 : D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
00E0 : E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
00F0 : F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF

Falls Sie diesen Versuch aber richtig durchführen, werden Sie jedoch folgendes erhalten:

0000 : 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0A 0E
0010 : 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E
0020 : 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E
0030 : 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E
0040 : 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E
0050 : 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E
0060 : 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E
0070 : 6F 0D 0A 70 71 72 73 74 75 76 77 78 79 7A 7B 7C
0080 : 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C
0090 : 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C
00A0 : 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC
00B0 : AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC
00C0 : BD BE BF 0D 0A C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA
00D0 : CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA
00E0 : DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA
00F0 : EB EC ED EE EF F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA
0100 : FB FC FD FE FF

Man stellt fest:

Dem letzteren Problem könnte man mit einem passenden WIDTH LPRINT begegnen (in GW-BASIC war meines Wissens 255 = unendliche Weite), jedoch das erste Problem wäre damit noch nicht gelöst :-(. Aus diesem Grunde sollten wir LPRINT besser durch PRINT# ersetzen, wobei wir anstelle von

OPEN "lpt1:" FOR OUTPUT AS 1

besser

OPEN "lpt1" FOR OUTPUT AS 1

verwenden (man beachte den fehlenden Doppelpunkt!) => QBASIC betrachtet nun das Gerät nicht mehr als Drucker, sondern als normale Datei und unterlässt solche, wie oben gezeigte cooked-Veränderungen, es lässt also die Daten 1:1, auch als raw bezeichnet, zum Drucker passieren :-). Zugleich haben wir jetzt den Vorteil, eben beliebige Parallelports ansprechen zu können. Bei Netzwerkdrucker unter Windows 95/98/NT Hinweise zu modernen Betriebssystemumgebungen beachten.

So, dies zur Ansteuerung des Druckers aus QuickBASIC heraus. Ein Problem haben wir jedoch noch: Der Drucker sollte jene Zeichen, welche einen ASCII-Wert <32 besitzen, wörtlich als Grafiksymbol ausdrucken, also CHR$(27) sollte auch tatsächlich als Links-Pfeil erscheinen und nicht einen Steuerbefehl (ESCape) einleiten. Im folgenden zeige ich Ihnen dies für die beiden Drucker HP DeskJet und Fujitsu DL1100:

' Hardcopy-Routine
' für FUJITSU DL-1100

SUB Hardcopy
  OPEN "lpt1" FOR OUTPUT AS 1
  FOR Zeile%=1 TO 25
    FOR Spalte%=1 TO 80
      PRINT#1, CHR$(27);"eE";RIGHT$("00"+MID$(STR$(SCREEN(Zeile%,Spalte%)),2),3);
    NEXT Spalte%
    PRINT#1, ""     ' Zeilenumbruch
  NEXT Zeile%
  CLOSE 1
END SUB

Dieser 24-Nadeldrucker des Herstellers Fujitsu verlangt wörtlich zu druckende Zeichen immer als <Esc> "eEnnn" mit nnn=dreistelliger Dezimalwert.

Nun dasselbe für einen HP DeskJet, geschrieben für ein Gerät der ersten Generation aus dem Jahre 1989; ich vermute aber, dass die neueren Tintenstrahldrucker-Modelle von HP noch voll kompatibel sind:

' Hardcopy-Routine
' für HP DeskJet

SUB Hardcopy
  OPEN "lpt1" FOR OUTPUT AS 1
  PRINT#1,CHR$(27);"E";CHR$(27);"&k0W";
  FOR Zeile%=1 TO 25
    PRINT#1,CHR$(27);"&p80X";
    FOR Spalte%=1 TO 80
      PRINT#1,CHR$(SCREEN(Zeile%,Spalte%));
    NEXT Spalte%
    PRINT#1, ""     ' Zeilenumbruch
  NEXT Zeile%
  PRINT#1,CHR$(27);"&k1W";CHR$(12);
  CLOSE 1
END SUB

Bei den Druckern der Firma Hewlett Packard kommt ein anderes Prinzip zur Anwendung: Angabe eines Befehls, welcher dem Drucker sagt, dass die nächsten n Zeichen (Bytes!) eben wörtlich, d.h. ohne ihre Steuerzeichenfunktion zu drucken sind, in diesem Fall also <Esc> "&pnX" mit n=dezimaler Wert als ASCII-String.

Falls Ihre auszudruckenden Bildschirme viele solche Spezialzeichen verwenden und Sie sich auch schon geärgert haben, warum die Rahmenzeichendarstellung lauter solche Versätze

Rahmenzeichengrafik mit Verschiebungen
Unerwünschter Versatz durch ausgelassene Steuerzeichen

statt korrekt

Rahmenzeichengrafik mit Verschiebungen

gemacht hat, ist es genau das Richtige für Sie, mit der obigen Routine anstelle der BIOS-Routine zu arbeiten, denn die Standard-BIOS-Routine macht all diese Filterungen nicht!

Text-Bildschirmausdruck in Farbe

Mit SCREEN(y%, x%, 1) können Sie zusätzlich auf die Farbinformation zurückgreifen:

Vordergrundfarbe% = SCREEN(y%, x%, 1) AND 15  
Hintergrundfarbe% = SCREEN(y%, x%, 1) \ 16 AND 7
Blinkend% = SCREEN(y%, x%, 1) \ 128    ' blinkend (1) / nicht blinkend

Somit können Sie bei einem Farbdrucker sogar farbige Hardcopies produzieren :-) Epson-kompatible Drucker: <Esc> "rx" mit x=Farbe.

' Hardcopy-Routine in Farbe
' für FUJITSU DL-1100 Color

' Zu Beginn initialisieren
DIM SHARED Uebersetz$(0 TO 7)
FOR i%=0 TO 7
  READ Uebersetz$(i%)
NEXT i%
DATA "0", "3", "6", "2", "5", "1", "4", "0"

SUB Hardcopy
  OPEN "lpt1" FOR OUTPUT AS 1
  FOR Zeile%=1 TO 25
    FOR Spalte%=1 TO 80
      PRINT#1, CHR$(27);"r";Uebersetz$(SCREEN(Zeile%,Spalte%) AND 7);
      PRINT#1, CHR$(27);"eE";RIGHT$("00"+MID$(STR$(SCREEN(Zeile%,Spalte%)),2),3);
    NEXT Spalte%
    PRINT#1, ""     ' Zeilenumbruch
  NEXT Zeile%
  CLOSE 1
END SUB

Hardcopy im Grafikmodus

In diesem Fall brauchen wir zum einen die Bitmap am Bildschirm auszulesen, was mit der POINT-Funktion kein Problem darstellt. Ebenso brauchen wir noch die Farbpalette, und dies ist schon schwieriger, denn QBASIC.EXE erlaubt Ihnen die Farben mit PALETTE zu setzen, jedoch nicht mehr auszulesen, da es keine PALETTE(Farbnummer%)-Funktion gibt! :-( Aber es gibt hier zwei Wege als Abhilfe:

Als Anregung für einen Bildschirmausdruck im Grafikmodus folgt eine sehr einfach gehaltene Schwarzweiss-Hardcopy-Routine für einem Epson LX-800 (9-Nadeldrucker mit ESC/P-Standardbefehlssatz), wobei als Bildschirmmodus SCREEN 2 (nur schwarzweiss) verwendet wird:

' Hardcopy-Routine für einen Epson LX-800-Drucker

' Hauptprogramm: Etwas Grafik und Text zeichnen

SCREEN 2

pi! = 4! * ATN(1!)
sw! = 2! * pi! / 73!
FOR i% = 1 TO 73
  w! = CSNG(i%) * sw!
  LINE (400!, 100!)-(400! + 228! * COS(w!), 100! + 95! * SIN(w!))
NEXT i%
PRINT "Und nun noch"
PRINT "etwas Text..."

Hardcopy
WHILE INKEY$ = ""
WEND
SCREEN 0

SUB Hardcopy
  OPEN "lpt1" FOR OUTPUT AS 1
  PRINT #1, CHR$(27); "@";          ' Drucker initialisieren
  PRINT #1, CHR$(27); "A"; CHR$(8)  ' 8/72" Zeilenvorschub
  FOR y% = 0 TO 192 STEP 8
    PRINT #1, CHR$(27); "L"; MKI$(640);  ' Grafikmodus aktivieren (640 Pixel)
    FOR x% = 0 TO 639
      w% = 0
      FOR y1% = y% TO y% + 7
        w% = 2 * w% + POINT(x%, y1%)  ' hier wird pixelweise ausgelesen
      NEXT y1%
      PRINT #1, CHR$(w%);     ' und die Daten an den Drucker geschickt
    NEXT x%
    PRINT #1, ""    ' Neue Zeile
  NEXT y%
  PRINT #1, CHR$(27); "2"; ' Wieder Standard-Zeilenvorschub von 1/6"
  CLOSE 1
END SUB

Wieder zurück zur Übersicht


© 2000 by Andreas Meile