{ From: remcos@htsa.aha.nl (Remco Schellekens) } {$R-} {Range checking off} {$B-} {Boolean complete evaluation off} {$S-} {Stack checking off} {$I+} {I/O checking on} {$N-} {No numeric coprocessor} PROGRAM show_pcx; {****************************************************************************} { } { SHOW_PCX is an example program written in Borland's Turbo Pascal(R) 5.0. } { (Turbo Pascal is a registered trademark of Borland International, Inc.) } { SHOW_PCX doesn't use any of the graphics routines built into Turbo Pascal, } { since many programmers won't be using Pascal for their final program. } { } { PERMISSION TO COPY: } { } { SHOW_PCX -- (C) Copyright 1989 ZSoft, Corporation. } { } { You are licensed to freely copy SHOW_PCX and incorporate it into your } { own programs, provided that: } { } { IF YOU COPY SHOW_PCX WITHOUT CHANGING IT: } { (1) You must retain this "Permission to Copy" notice, and } { (2) You must not charge for the SHOW_PCX software or } { documentation; however, you may charge a service fee for } { disk duplication and distribution, so long as such fee is } { not more than $5.00. } { } { IF YOU MODIFY SHOW_PCX AND/OR INCORPORATE SHOW_PCX INTO YOUR OWN PROGRAMS } { (1) You must include the following acknowledgment notice in the } { appropriate places: } { } { Includes portions of SHOW_PCX. } { Used by permission of ZSoft Corporation. } { } { } { ZSoft Corporation reserves all rights to SHOW_PCX except as stated herein. } { } { } { [END OF "PERMISSION TO COPY" NOTICE] } { } { This program reads a PC Paintbrush PCX file and shows it on the screen. } { The picture must be a 2 color CGA, 4 color CGA, or a 16 color EGA picture. } { The picture will be displayed until a key is pressed. } { } { This program can be run at the DOS prompt - 'SHOW_PCX SAMPLE.PCX'. } { } {****************************************************************************} { } { Since this program is provided as a service, you are on your own when } { when you modify it to work with your own programs. } { } { We strive to make every program bug-free. If you find any bugs in this } { program, please contact us on Compuserve (76702,1207) } { However, this program is provided AS IS and we are not responsible for any } { problems you might discover. } { } {****************************************************************************} { } { Remember, some computers and video adapters are NOT 100% compatible, no } { matter what their marketing department may say. This shows up when your } { program runs on everyone's computer EXCEPT a particular clone. } { Unfortunately, there is not much you can do to correct it. } { } { For example, some early VGA cards do not support the BIOS calls to set up } { a VGA palette - so the PCX image may come up all black, or with the wrong } { colors. } { } { Also, if you use code that attempts to determine what kind of video card } { is attached to the computer it may lock-up... } { } {****************************************************************************} { } { The PCX file format was originally developed in 1982, when there were only } { three video addapters: CGA, Hercules, and the Tecmar Graphics Master. Over } { the years, as new hardware became available (EGA, VGA, etc.), we had to } { modify the format. Wherever posible, we insure downward compatiblity. This } { means, if you follow the suggestions in this program, your own program } { should be able to read 'new' PCX files in the future. } { } {****************************************************************************} {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~} { NEEDED ADDITIONS: CGA palette - read old and new palette - set screen palette } {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~} USES Crt, Dos; CONST MAX_WIDTH = 4000; { arbitrary - maximum width (in bytes) of a PCX image } COMPRESS_NUM = $C0; { this is the upper two bits that indicate a count } MAX_BLOCK = 4096; RED = 0; GREEN = 1; BLUE = 2; CGA4 = $04; { video modes } CGA2 = $06; EGA = $10; VGA = $12; MCGA = $13; type Str80 = STRING [80]; FILE_buffer = ARRAY [0..127] OF Byte; block_ARRAY = ARRAY [0..MAX_BLOCK] OF Byte; pal_ARRAY = ARRAY [0..255, RED..BLUE] OF Byte; ega_ARRAY = ARRAY [0..16] OF Byte; line_ARRAY = ARRAY [0..MAX_WIDTH] OF Byte; pcx_header = RECORD Manufacturer: Byte; { Always 10 for PCX file } Version: Byte; { 2 - old PCX - no palette (NOT used anymore), 3 - no palette, 4 - Microsoft Windows - no palette (only in old files, New Windows version USES 3), 5 - WITH palette } Encoding: Byte; { 1 is PCX, it is possible that we may add additional encoding methods IN the future } Bits_per_pixel: Byte; { Number of bits to represent a pixel (per plane) - 1, 2, 4, or 8 } Xmin: INTEGER; { Image window dimensions (inclusive) } Ymin: INTEGER; { Xmin, Ymin are usually zero (not always) } Xmax: INTEGER; Ymax: INTEGER; Hdpi: INTEGER; { Resolution of image (dots per inch) } Vdpi: INTEGER; { Set to scanner resolution - 300 is default } ColorMap: ARRAY [0..15, RED..BLUE] OF Byte; { RGB palette data (16 colors or less) 256 color palette is appended to END OF FILE } Reserved: Byte; { (used to contain video mode) now it is ignored - just set to zero } Nplanes: Byte; { Number of planes } Bytes_per_line_per_plane: INTEGER; { Number of bytes to allocate for a scanline plane. MUST be an an EVEN number! DO NOT calculate from Xmax-Xmin! } PaletteInfo: INTEGER; { 1 = black & white or color image, 2 = grayscale image - ignored IN PB4, PB4+ palette must also be set to shades of gray! } HscreenSize: INTEGER; { added for PC Paintbrush IV Plus ver 1.0, } VscreenSize: INTEGER; { PC Paintbrush IV ver 1.02 (and later) } { I know it is tempting to use these fields to determine what video mode should be used to display the image - but it is not recommended since the fields will probably just contain garbage. It is better to have the user install for the graphics mode he wants to use... } Filler: ARRAY [74..127] OF Byte; { Just set to zeros } END; VAR Name: Str80; { Name of PCX file to load } ImageName: Str80; { Name of PCX file - used by ReadError } BlockFile: FILE; { file for reading block data } BlockData: block_ARRAY; { 4k data buffer } Header: pcx_header; { PCX file header } Palette256: pal_ARRAY; { place to put 256 color palette } PaletteEGA: ega_ARRAY; { place to put 17 EGA palette values } PCXline: line_ARRAY; { place to put uncompressed data } Ymax: INTEGER; { maximum Y value on screen } NextByte: INTEGER; { index into file buffer in ReadByte } Index: INTEGER; { PCXline index - where to put Data } Data: Byte; { PCX compressed data byte } PictureMode: INTEGER; { Graphics mode number } Reg: Registers; { Register set - used for int 10 calls } { ================================= Error ================================== } PROCEDURE Error (s: Str80 ); { Print out the error message and wait, then halt } VAR c: CHAR; i: INTEGER; BEGIN TextMode (C80); WriteLn ('ERROR'); WriteLn (s); Halt; END; { Error } { =============================== ReadError =============================== } PROCEDURE ReadError (msg: INTEGER); { Check for an i/o error } BEGIN IF IOResult <> 0 THEN CASE msg OF 1: Error ('Can''t open file - ' + ImageName); 2: Error ('Error closing file - ' + ImageName + ' - disk may be full'); 3: Error ('Error reading file - ' + ImageName); ELSE Error ('Error doing file I/O - ' + ImageName); END; { case } END; { ReadError } { =========================== VideoMode =============================== } PROCEDURE VideoMode (n: INTEGER); { Do a BIOS call to set the video mode } { In Turbo Pascal, a '$' means the number is hexadeximal. } BEGIN Reg.ah := $00; Reg.al := n; { mode number } intr ($10, Reg); { call interrupt } END; { VideoMode } { =========================== EGApalette =============================== } PROCEDURE EGApalette (n, R, G, B: INTEGER); { Set a single EGA's palette register. n is the index of the palette register. R, G, and B are 0..255. } { This code is never called - it is here as an example } { In Turbo Pascal, a '$' means the number is hexadeximal. } VAR i: INTEGER; BEGIN R := R SHR 6; { R, G, and B are now 0..3 } G := G SHR 6; B := B SHR 6; i := (R SHL 4) + (G SHL 2) + B; Reg.ah := $10; Reg.al := 0; { set individual palette register } Reg.bh := i; { value } Reg.bl := n; { palette register number } intr ($10, Reg); { call interrupt } END; { EGApalette } { =========================== VGApalette =============================== } PROCEDURE VGApalette (n, R, G, B: INTEGER); { Set a single VGA palette and DAC register pair. n is the index of the palette register. R, G, and B are 0..255. } { This code is never called - it is here as an example } { In Turbo Pascal, a '$' means the number is hexadeximal. } BEGIN R := R SHR 2; { R, G, and B are now 0..63 } G := G SHR 2; B := B SHR 2; Reg.ah := $10; { Set Palette Call } Reg.al := $0; { set individual palette register } Reg.bl := n; { palette register number 0..15, 0..255 } Reg.bh := n; { palette register value } intr ($10, Reg); { call interrupt } Reg.ah := $10; { Set DAC Call } Reg.al := $10; { set individual DAC register } Reg.bx := n; { DAC register number 0..15, 0..255 } Reg.dh := R; { red value 0..63 } Reg.ch := G; { green value 0..63 } Reg.cl := B; { blue value 0..63 } intr ($10, Reg); { call interrupt } END; { VGApalette } { =========================== EGA16palette =============================== } PROCEDURE EGA16palette; { Set the EGA's entire 16 color palette. } { In Turbo Pascal, a '$' means the number is hexadeximal. } VAR i, r, g, b: INTEGER; BEGIN FOR i := 0 TO 15 DO BEGIN r := Header.ColorMap [i, RED] SHR 6; { r, g, and b are now 0..3 } g := Header.ColorMap [i, GREEN] SHR 6; b := Header.ColorMap [i, BLUE] SHR 6; PaletteEGA [i] := (r SHL 4) + (g SHL 2) + b; END; PaletteEGA [16] := 0; { border color } Reg.ah := $10; { Set Palette Call } Reg.al := $02; { set a block of palette registers } Reg.dx := ofs (PaletteEGA); { offset of block } Reg.es := seg (PaletteEGA); { segment of block } intr ($10, Reg); { call interrupt } END; { EGA16palette } { =========================== VGA16palette =============================== } PROCEDURE VGA16palette; { Set the VGA's entire 16 color palette. } { In Turbo Pascal, a '$' means the number is hexadeximal. } VAR i: INTEGER; BEGIN FOR i := 0 TO 15 DO PaletteEGA [i] := i; PaletteEGA [16] := 0; { border color } Reg.ah := $10; { Set Palette Call } Reg.al := $02; { set a block of palette registers } Reg.dx := ofs (PaletteEGA); { offset of block } Reg.es := seg (PaletteEGA); { segment of block } intr ($10, Reg); { call interrupt } FOR i := 0 TO 15 DO BEGIN { R, G, and B must be 0..63 } Palette256 [i, RED] := Header.ColorMap [i, RED] SHR 2; Palette256 [i, GREEN] := Header.ColorMap [i, GREEN] SHR 2; Palette256 [i, BLUE] := Header.ColorMap [i, BLUE] SHR 2; END; Reg.ah := $10; { Set DAC Call } Reg.al := $12; { set a block of DAC registers } Reg.bx := 0; { first DAC register number } Reg.cx := 255; { number of registers to update } Reg.dx := ofs (Palette256); { offset of block } Reg.es := seg (Palette256); { segment of block } intr ($10, Reg); { call interrupt } END; { VGA16palette } { =========================== EntireVGApalette =============================== } PROCEDURE EntireVGApalette; { Set the VGA's entire 256 color palette. } { In Turbo Pascal, a '$' means the number is hexadeximal. } VAR i: INTEGER; BEGIN FOR i := 0 TO 255 DO BEGIN { R, G, and B must be 0..63 } Palette256 [i, RED] := Palette256 [i, RED] SHR 2; Palette256 [i, GREEN] := Palette256 [i, GREEN] SHR 2; Palette256 [i, BLUE] := Palette256 [i, BLUE] SHR 2; END; Reg.ah := $10; { Set DAC Call } Reg.al := $12; { set a block of DAC registers } Reg.bx := 0; { first DAC register number } Reg.cx := 255; { number of registers to update } Reg.dx := ofs (Palette256); { offset of block } Reg.es := seg (Palette256); { segment of block } intr ($10, Reg); { call interrupt } END; { EntireVGApalette } { =========================== SetPalette =============================== } PROCEDURE SetPalette; { Set up the entire graphics palette } VAR i: INTEGER; BEGIN IF PictureMode = MCGA THEN EntireVGApalette ELSE IF PictureMode = VGA THEN VGA16palette ELSE EGA16palette; END; { SetPalette } { =========================== ShowCGA =============================== } PROCEDURE ShowCGA (Y: INTEGER); { Put a line of CGA data on the screen } { In Turbo Pascal, a '$' means the number is hexadeximal. } VAR i, j, l, m, t: INTEGER; Yoffset: INTEGER; CGAScreen: ARRAY [0..32000] OF Byte ABSOLUTE $B800:$0000; BEGIN i := 8 DIV Header.Bits_per_pixel; { i is pixels per byte } IF (i = 8) THEN { 1 bit per pixel } j := 7 ELSE { 2 bits per pixel } j := 3; t := (Header.Xmax - Header.Xmin + 1); { width in pixels } m := t AND j; { left over bits } l := (t + j) DIV i; { compute number of bytes to display } IF l > 80 THEN BEGIN l := 80; { don't overrun screen width } m := 0; END; IF (m <> 0) THEN { we need to mask unseen pixels } BEGIN m := $FF SHL (8 - (m * Header.Bits_per_pixel)); { m = mask } t := l - 1; PCXline [t] := PCXline [t] AND m; { mask off unseen pixels } END; Yoffset := 8192 * (Y AND 1); Move (PCXline [0], CGAScreen [((Y SHR 1) * 80) + Yoffset], l); END; { ShowCGA } { =========================== ShowEGA =============================== } PROCEDURE ShowEGA (Y: INTEGER); { Put a line of EGA (or VGA) data on the screen } { In Turbo Pascal, a '$' means the number is hexadeximal. } VAR i, j, l, m, t: INTEGER; EGAplane: INTEGER; EGAscreen: ARRAY [0..32000] OF Byte ABSOLUTE $A000:$0000; BEGIN EGAplane := $0100; { the first plane to update } PortW [$3CE] := $0005; { use write mode 0 } { PortW [$3CE] := $0005; does port I/O by words. It is the same as: Out 03CEh,05h Out 03CFh,00h } t := (Header.Xmax - Header.Xmin + 1); { width in pixels } m := t AND 7; { left over bits } l := (t + 7) SHR 3; { compute number of bytes to display } IF (l >= 80) THEN BEGIN l := 80; { don't overrun screen width } m := 0; END; IF (m <> 0) THEN m := $FF SHL (8 - m) { m = mask for unseen pixels } ELSE m := $FF; FOR i := 0 TO Header.Nplanes-1 DO BEGIN j := i * Header.Bytes_per_line_per_plane; t := j + l - 1; PCXline [t] := PCXline [t] AND m; { mask off unseen pixels } PortW [$3C4] := EGAplane + 2; { set plane number } Move (PCXline [j], EGAscreen [Y * 80], l); EGAplane := EGAplane SHL 1; END; PortW [$3C4] := $0F02; { default plane mask } END; { ShowEGA } { =========================== ShowMCGA =============================== } PROCEDURE ShowMCGA (Y: INTEGER); { Put a line of MCGA data on the screen } { In Turbo Pascal, a '$' means the number is hexadeximal. } VAR l: INTEGER; MCGAscreen: ARRAY [0..64000] OF Byte ABSOLUTE $A000:$0000; BEGIN l := Header.XMax - Header.Xmin; { compute number of bytes to display } IF l > 320 THEN l := 320; { don't overrun screen width } Move (PCXline [0], MCGAScreen [Y * 320], l); END; { ShowMCGA } { =========================== Read256palette =============================== } PROCEDURE Read256palette; { Read in a 256 color palette at end of PCX file } VAR i: INTEGER; b: Byte; BEGIN Seek (BlockFile, FileSize (BlockFile) - 769); BlockRead (BlockFile, b, 1); { read indicator byte } ReadError (3); IF b <> 12 THEN { no palette here... } Exit; BlockRead (BlockFile, Palette256, 3*256); ReadError (3); Seek (BlockFile, 128); { go back to start of PCX data } END; { Read256palette } { =========================== ReadHeader =============================== } PROCEDURE ReadHeader; { Load a picture header from a PC Paintbrush PCX file } LABEL WrongFormat; BEGIN {$I-} BlockRead (BlockFile, Header, 128); { read 128 byte PCX header } ReadError (3); { Is it a PCX file? } IF (Header.Manufacturer <> 10) OR (Header.Encoding <> 1) THEN BEGIN Close (BlockFile); Error ('This is not a valid PCX image file.'); END; IF (Header.Nplanes = 4) AND (Header.Bits_per_pixel = 1) THEN BEGIN IF (Header.Ymax - Header.Ymin) <= 349 THEN begin PictureMode := EGA; Ymax := 349; end ELSE begin PictureMode := VGA; Ymax := 479; END; END ELSE IF (Header.Nplanes = 1) THEN BEGIN Ymax := 199; IF (Header.Bits_per_pixel = 1) THEN PictureMode := CGA2 ELSE IF (Header.Bits_per_pixel = 2) THEN PictureMode := CGA4 ELSE IF (Header.Bits_per_pixel = 8) THEN begin PictureMode := MCGA; IF Header.Version = 5 then Read256palette; end ELSE GOTO WrongFormat; END ELSE BEGIN WrongFormat: Close (BlockFile); Error ('PCX file is in wrong format - It must be a CGA, EGA, VGA, or MCGA image'); END; Index := 0; NextByte := MAX_BLOCK; { indicates no data read in yet... } END; { ReadHeader } { =========================== ReadByte =============================== } PROCEDURE ReadByte; { read a single byte of data - use BlockRead because it is FAST! } VAR NumBlocksRead: INTEGER; BEGIN IF NextByte = MAX_BLOCK THEN BEGIN BlockRead (BlockFile, BlockData, MAX_BLOCK, NumBlocksRead); NextByte := 0; END; data := BlockData [NextByte]; Inc (NextByte); { NextByte++; } END; { ReadByte } { =========================== Read_PCX_Line =============================== } PROCEDURE Read_PCX_Line; { Read a line from a PC Paintbrush PCX file } VAR count: INTEGER; bytes_per_line: INTEGER; BEGIN {$I-} bytes_per_line := Header.Bytes_per_line_per_plane * Header.Nplanes; { bring in any data that wrapped from previous line } { usually none - this is just to be safe } IF Index <> 0 THEN FillChar (PCXline [0], Index, data); { fills a contiguous block of data } WHILE (Index < bytes_per_line) DO { read 1 line of data (all planes) } BEGIN ReadByte; IF (data AND $C0) = compress_num THEN begin count := data AND $3F; ReadByte; FillChar (PCXline [Index], count, data); { fills a contiguous block } Inc (Index, count); { Index += count; } end ELSE begin PCXline [Index] := data; Inc (Index); { Index++; } END; END; ReadError (3); Index := Index - bytes_per_line; {$I+} END; { Read_PCX_Line } { =========================== Read_PCX =============================== } PROCEDURE Read_PCX (name: Str80); { Read PC Paintbrush PCX file and put it on the screen } VAR k, kmax: INTEGER; BEGIN {$I-} ImageName := name; { used by ReadError } Assign (BlockFile, name); Reset (BlockFile, 1); { use 1 byte blocks } ReadError (1); ReadHeader; { read the PCX header } { >>>>> No checking is done to see if the user has the correct hardware <<<<< >>>>> to load the image. Your program sure verify the video mode is <<<<< >>>>> supported. Otherwise, the computer may lock-up. <<<<< } VideoMode (PictureMode); { switch to graphics mode } IF Header.Version = 5 THEN SetPalette; { set the screen palette, if available } { >>>>> Note: You should compute the height of the image as follows. <<<<< >>>>> Do NOT just read until End-Of-File! <<<<< } kmax := Header.Ymin + Ymax; IF Header.Ymax < kmax THEN { don't show more than the screen can display } kmax := Header.ymax; IF (PictureMode = EGA) OR (PictureMode = VGA) THEN BEGIN FOR k := Header.Ymin TO kmax DO { each loop is separate for speed } begin Read_PCX_Line; ShowEGA (k); END; END ELSE IF (PictureMode = MCGA) THEN BEGIN FOR k := Header.Ymin TO kmax DO begin Read_PCX_Line; ShowMCGA (k); END; END ELSE { it's a CGA picture } BEGIN FOR k := Header.Ymin TO kmax DO begin Read_PCX_Line; ShowCGA (k); END; END; Close (BlockFile); ReadError (2); {$I+} END; { Read_PCX } { =========================== DISPLAY_PCX =============================== } PROCEDURE display_pcx (name: Str80); { Display a PCX picture } VAR c: CHAR; BEGIN Read_PCX (name); { read and display the file } WHILE (NOT KeyPressed) DO { wait for any key to be pressed } ;{ nothing } c := ReadKey; { now get rid of the key that was pressed } IF c = #0 THEN { handle function keys } c := ReadKey; END; { display_pcx } { *************************** MAIN ******************************* } BEGIN ClrScr; writeln (' SHOW_PCX - read and display a PC Paintbrush (R) picture'); WriteLn; WriteLn (' PERMISSION TO COPY:'); writeln (' SHOW_PCX -- (C) Copyright 1989 ZSoft, Corporation.'); WriteLn; writeln ('You are licensed to freely copy SHOW_PCX and incorporate it into your'); WriteLn ('own programs, provided that:'); WriteLn (' IF YOU COPY SHOW_PCX WITHOUT CHANGING IT:'); writeln (' (1) You must retain this "Permission to Copy" notice, and'); writeln (' (2) You must not charge for the SHOW_PCX software or documentaion;'); writeln (' however, you may charge a service fee for disk duplication and'); writeln (' distribution, so long as such fee is not more than $5.00'); writeln (' IF YOU MODIFY SHOW_PCX AND/OR INCORPORATE SHOW_PCX INTO YOUR OWN PROGRAMS'); writeln (' (1) You must include the following notice in the appropriate places:'); writeln (' Includes portions of SHOW_PCX. Used by permission of ZSoft Corporation.'); WriteLn; writeln (' ZSoft Corporation reserves all rights to SHOW_PCX except as stated herein.'); writeln (' ZSoft Corporation, 450 Franklin Road, Suite 100, Marietta, GA 30067'); WriteLn (' (404) 428-0008'); writeln (' [END OF "PERMISSION TO COPY" NOTICE]'); WriteLn; IF (ParamCount = 0) THEN { no DOS command line parameters } BEGIN writeln ('The image must be a 2 or 4 color CGA, 16 color EGA or VGA,'); WriteLn ('or a 256 color MCGA picture'); WriteLn; Write ('Enter name of picture file to display: '); ReadLn (name); WriteLn; END ELSE Name := ParamStr (1); { get filename from DOS command line } IF (Pos ('.', Name) = 0) THEN { make sure the filename has PCX extension } Name := Concat (Name, '.pcx'); display_pcx (Name); TextMode (co80); { back to text mode } END. { Show_PCX }