Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Translating PCX Files


AUG89: TRANSLATING PCX FILES

Kent is a free-lance writer and president of Totel Systems, Inc., a company that develops device drivers, custom software, and embedded systems. Kent can be reached at 489 Groton Rd., Westford, MA 01886 or on BIX or MCI Mail as kquirk.


PCX files have become somewhat of a standard these days. Developed by ZSoft (Marietta, Georgia) for use with their PC Paintbrush graphics editor, PCX is a reasonably compact, yet simple, method of storing graphics data. The PCX file format gained popularity because PC Paintbrush was packaged with the Microsoft Mouse, and most mouse users tinkered with it. Nowadays, it seems most scanners, fax boards, and desktop publishing systems process PCX files. I found, however, that I had no handy way of printing these images on a PostScript printer except by reading them into Lotus Manuscript first, which wasn't always convenient (particularly when the image came from a fax card).

PostScript, on the other hand, is a page-definition language (PDL), a programming language enhanced with a set of operators that allow the specification of marks on a page, including images, line-drawing, and text. It is a stack-oriented language whose most salient (and interesting) feature is that it can be read and processed in linear order, without ever backing up. This makes it ideal for use in a printer, which typically has a unidirectional connection to a computer. PostScript is probably the most powerful way currently available for specifying the description of an output page in a printer.

The problem with PostScript is that everything a printer does has to be written in PostScript. You can't send it straight text (PrintScreen doesn't work at all), and you certainly can't send it an image without working pretty hard.

So how do you print an image in PostScript? Using the image operator is most common. This function takes a stream of data and converts it to a rectangular bitmap of arbitrary size, with 1, 2, 4, or 8 bits per pixel. But PostScript can't accept binary data (it reserves and traps certain ASCII control characters), so all its input must be converted to hex or some other printable form, which then has to have PostScript code added to it to make it print.

In the course of my work, I needed a program to perform a PCX-to-PostScript translation. Because C is my language of choice for most projects, I decided to write the program, PRPCX, in C. In the process of writing PRPCX, I learned a lot about PCX files and imaging in PostScript, and codified some of my thinking about C.

C Idioms

In my seven years of programming in C, I've developed certain idioms -- common bits of code which fairly type themselves into the program. These are not subroutines or include files; they are more like mental templates that guide the generation of code specific to the application I'm working on. I find that good C programmers tend to develop many such idioms, perhaps in self-defense.

Because of the expressive power of C, there are usually several different ways to perform a given task. C has been accused of being a write-only language. I feel this is true only for programmers who fail to develop and follow the particular idioms of the language. C can be written (badly) to look like Pascal or any of a number of other languages. A good programmer learns and develops a "C-ish" programming style, that becomes a standard code that requires a little effort to write or understand. A classic example is the traditional counting loop:

  for(i=0; i<last; i++) do_something( );

I've developed larger idioms as well, several of which appear in this program.

The first problem in writing the translation is developing a set of tools for reading and manipulating the PCX files. The two files PCX.C (Listing One) and PCX.H (Listing Two) provide enough facilities to read PCX files, but not to create them. They are written for generality and clarity, not efficiency. Application of better buffering or converting the data to final form on the fly, could speed up the process greatly, but would complicate matters unnecessarily for our purposes. Michael Swaine would point out a Programming Paradigm: First get it right, then get it fast. Although there are exceptions to any rule, it certainly applies in this case.

PCX File Characteristics

A PCX file has a 128-byte header that describes the contents of the rest of the file. ZSoft has not been lazy -- there have been at least five versions of PC Paintbrush, and PCX files have changed to keep up. What's good is that the same header format applies to all PCX versions to date. What's bad is that the data formats that follow the header vary wildly -- unfortunately for portability, PCX files are tied fairly closely to the video board on which they were created.

Some video boards (such as the EGA and VGA in certain modes) arrange their memory in the form of bit planes. This is a way of looking at graphics memory three-dimensionally. The x and y dimensions define an array of bits that corresponds to the dimensions of the video display. The z axis defines the number of bits per pixel for the video board. One pixel is spread among several different memory banks. Provided you have the help of a graphics controller, this memory organization allows for great flexibility in graphics processing.

In the PCX format, if the video board uses bit planes, the data is stored in bit plane form. One pixel is stored across multiple bytes of data. Rearranging these bit planes into single-pixel values, then cross-referencing them so they can be printed sensibly on a monochrome printer might be an interesting exercise, but is beyond the scope of this article.

Monochrome boards (or monochrome modes on color boards) just store the data as a stream of bits. Since the printer is monochrome anyway, I chose to limit PRPCX to using only files created in monochrome mode.

The first routine (pcx_read_header) reads the PCX header into a data area. It allocates a data area if one is not specified. Here's the first idiom: When passing a pointer to a structure as an argument to a subroutine, I take a NULL pointer to mean that the user is requesting automatic allocation of the data area.

The pcx_header fields include information on the version of PCX used to create the file, the data compression method (only RLE is supported to date), and the number of bits per pixel in the image. The PCX file defines a rectangle of data which always contains an even number of bytes per line and a number of lines that is a multiple of eight. The upper-left and lower-right parameters define the actual size and position of the image data within that rectangle. If the video board uses a palette, it is stored in the palette area if it fits (it fits for up to 16 colors). If it doesn't fit, it is stored at the end of the file, with the type of palette indicated by paletteinfo.

The print_pcx_header function prints the data to a given file in user-readable form. For someone trying to use sizing and scaling to position an image, this can be helpful.

I broke out the pcx_alloc_line() routine because it might be desirable to call it individually in the case where you want to build an array of data lines for faster response or for image editing.

The pcx_next_line() function is the key to the whole process. It reads the next line from the PCX file and returns it, in unpacked raw data form, to the caller. Understanding it requires an understanding of the PCX file format and its method of data compression.

PCX files use run length encoding (RLE) for compression -- basically, any consecutive data bytes which have the same value are replaced by a count byte followed by the value. Count bytes are distinguished from data bytes by setting the top two bits; the bottom six bits represent the number of repetitions, from 0 to 63. If a data byte needs to use a value with data in this range, it can do so by specifying a repeat count of 1.

RLE is sometimes an excellent way to store image data, especially if (like most pictures generated with PC Paintbrush) the image contains large areas of solid colors. However, on random data or gray scale images, where there are few repeated data bytes, the PCX format is likely to be some 25 percent larger than raw data size because of the escape code necessary for data bytes with the top two bits set. For more information on RLE, see "Run Length Encoding" by Robert Zigon (DDJ, February 1989) and "RLE Revisited" by Phil Daley (DDJ, May 1989).

The file PRPCX.C (Listing Three) contains the rest of the code while Listing Four is the Make file. The general flow is to first initialize the map structure to default values. This structure is used to contain the scaling and positioning data for placing the PCX image on the PostScript page. Next, process the command line and set up the values for scaling and sizing. The .PS file is then copied to the output. The scaling and sizing variables are emitted, and finally the .PCX file is read and emitted as hex data.

The command line parser is another idiom that just flies from my fingertips whenever I need it. Basically, it's a simple checker that looks for a leading hyphen (-) or slash (/) in each argument. If it sees either one, it does a switch on the second character. Matches generate either string assignments or a conversion, as in x = atoi(argv[i]+2);.

If no leading slash is seen, assume that the argument is a filename and assign the filename variable to it. This is easy, but limits the program to one filename per run. Because there are good reasons not to generate more than one image per PostScript file, this isn't really a problem.

I've used this command line parser so often that it just writes itself, with no concentration required to make it work. Although it's not as flexible as a general-purpose parsing subroutine, it's small, fast and easy. Only rarely have I had to get more sophisticated.

Normally, this program sends its output to STDOUT, so it can be piped to a disk file, another filter, or directed to the printer. Direct to the printer, however, may be the most commonly used form. Because people's printers don't change names, it made sense to allow the user to put the printer name in the environment. Once set, it stays set until a reboot or it is explicitly changed. This is ideal for a utility likely to use the same arguments over and over. Those of you who prefer to use the command line can redirect the output, or use the -o switch (which makes the most sense in batch files).

If no arguments are specified, there's nothing to do, so a usage message is printed. This too is important. I believe that a user should always be able to figure out something about what the program does and how to get started without having to read the manual (or the article). Now that everything is set up, the program calls dofile(), which does the rest of the job.

The first function dofile() does is open the PCX file. Another idiom: If the filename doesn't contain a period, concatenate .PCX to the end of the name. This way the user doesn't have to type the obvious, but can override any assumptions.

dofile() opens the file, and if the user asks for it, dumps the header and returns without doing further work. Next read the PostScript prologue file. Many programmers aren't aware that the first element in the argv array (for DOS versions later than 3.0) is a pointer to the complete path and filename of the program. For earlier versions of DOS, the program name is available (put there by the compiler), but the path from which it is run is not. Use this information to find the prologue file -- and simply require it to have the same path and name as the executable file, but with the .PS extension. Again a simple rule that doesn't restrict the user.

The PostScript Code

I'm using a fairly standard PostScript technique. First, create a prologue file PRPCX.PS (Listing Five) that contains PostScript code that defines all the functions needed to handle the image generation. Copy this file to the beginning of the output file. This way, the C program doesn't have to know much about PostScript, and an experienced user can modify the prologue to do a different job without having to recompile the software.

The prologue in this case contains functions for building the transform matrix, reading the image data from the file, doing scaling and image generation, and printing the page after the image is complete.

Adobe (Mountain View, Calif.) has defined a comment convention for PostScript files. Files conforming to the specification can be specially handled by print servers, or read into desktop publishing programs as Encapsulated PostScript (EPS) files. The specification is laid out in Adobe's Red Book, Appendix C. Basically, minimally conforming programs contain information that defines the fonts used and image size.

Normally, this information is placed at the beginning of the file. However, Adobe allowed the instruction (atend) to mean that the actual data is found at the end of the file. My original intention for PRPCX was to place the BoundingBox item (which describes the boundaries of all the black marks on a page) at the end of the file. Unfortunately, I discovered that Ventura Publisher, in violation of the EPS specifications, requires BoundingBox to be placed at the beginning of the file.

So for ease of programming, PRPCX is limited to processing one file at a time, and there is a special hack to fill in the BoundingBox entry in PRPCX.PS. The file is copied to the output until it sees the line containing BoundingBox. It then generates a new BoundingBox line based on the header read from the data file, and continues with the file copy.

The calculations used are the same as those used by PostScript's image operator, which is capable of scaling and flipping bitmaps. The syntax of the image operator is as follows:

  width height bpp transform readproc image

For those who don't know PostScript, the first thing to learn is that it's a stack-oriented language. Any operator takes its arguments on the stack, so they come before the operator does. In this case, the width and height operators are found deepest on the stack; they are the width and height of the bitmap in pixels. The bpp operand tells the number of bits per pixel in the image. 1, 2, 4, and 8 are allowed; this program only handles images with 1 bit per pixel.

PostScript allows objects of any type on the stack. The [] (mark) operators define an array, which (like lists in Lisp) simply collect everything between them into a single object. In the case of the transform operand, image expects a six-element array containing the transform matrix, which is used as a multiplier to convert the image into the output area.

Finally, image accepts a procedure (again on the stack) which it calls to get image data. Each time this procedure is called it should return a string containing image data. When width * height data elements have been consumed, image is finished processing. Returning a zero-length string aborts image early. Use the readhexstring operator (see the readproc function in PRPCX.PS) to read a hex string of data from the input and convert it to binary.

The Adobe Red Book talks of a convention that speeds up the generation of an image; if the image conversions yield one input pixel per device pixel, all scaling and transforming is turned off, for a large increase in speed. The default behavior of PRPCX is to generate the bitmap at this size.

Adobe recommends handling the transformation in two parts. First, use the transform matrix operand (part of the image operator) to convert the image to a 1 x 1 unit square with the image upright. Then use translate and scale to move the image to the shape and location desired.

Translating to the unit square is slightly complicated by the fact that a PostScript is measured from the bottom of the page, while a PCX image starts at the top. Fortunately, the transform matrix takes care of this neatly. A normal transformation to the unit square uses the following matrix:

  [width 0 0 height 0 0]

The transform to invert the Y axis is:

  [width 0 0 -height 0 height]

In PostScript, that last matrix is written:

  [width 0 0 height neg 0 height]

The neg (negate) operator inverts the sign of the object on top of the stack (which is height, in this case). The next step is to convert to device resolution. This requires that you first translate the coordinate axes to the desired position using standard resolution, which is 72 dpi (one point). This way the position of the image is independent of its size.

Next, make the image its original shape (assuming square pixels). To do this, scale the unit square to the width and height of the image in pixels. Then scale the coordinate system to device resolution, using the command

  72 res div 72 res div scale

The scale operator takes two operands, the scale factor in X and Y. Divide 72 by the actual resolution of the device. This makes one unit in device space equivalent to one unit in user space. Finally, to allow the user to change the scaling, add one more scale factor. All of these transformations take place in the scaleit routine within the prologue.

The image operator takes an argument which is a procedure to supply it with data. Because the PostScript data stream has certain reserved characters, use the readhexstring operator to accept hex data. This doubles the number of bytes sent to the printer, but that's the way it's generally done; it's certainly the easiest to code. The readhexstring operator fills a string with data from the input file. In order to ensure that it doesn't read past the end of the file, you have to build a string just large enough to hold one line of input. The imagedata routine in PRPCX.PS contains code to do this.

Now we start generating PostScript code. First define and give values to all the variables used by the prologue. Then tell PostScript to actually do the scaling and translation calculations. Finally, tell it to start accepting an image.

The image is processed by expanding each line to raw data using the PCX routines, inverting it if necessary, then emitting the data as a stream of hex bytes. Each line of input data is separated by a newline in the output file.

At the end of the file, emit the code to get PostScript to generate the image (the single operator showpage), and you're done. The grestore operator restores the image context that existed before you started this whole process. This is simply good manners; a good idea when generating EPS files.

The result of all this work has been the conversion of a PCX file, a useful and standard data format for images, into PostScript, another useful and standard format, with C as the medium of exchange. In the process, we've learned to make efficient use of all three.

Acknowledgments

I'd like to thank ZSoft for its booklet of information on PCX files, and Laser-Go for the use of GoScript, a program which allows the use of PostScript code with printers that don't support it in native mode.

Availability

All source code for articles in this issue is available on a single disk. To order, send $14.95 (Calif. residents add sales tax) to Dr. Dobb's Journal, 501 Galveston Dr., Redwood City, CA 94063, or call 800-356-2002 (from inside Calif.) or 800-533-4372 (from outside Calif.). Please specify the issue number and format (MS-DOS, Macintosh, Kaypro).

TRANSLATING PCX FILES by Kent Quirk

[LISTING ONE]

<a name="016e_000a">

/*+
    Name:       pcx.c
    Author:     Kent J. Quirk
    Abstract:   This file contains subroutines to read PCX files.
-*/

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include "pcx.h"

/**** p c x _ r e a d _ h e a d e r ****
    Abstract:   Reads the header of a PCX file.
    Parameters: A data storage area for the header, an open file.
    Returns:    The pointer to the data storage area passed, or NULL if error.
    Comments:   The file should be opened in binary mode.
****************************/
PCX_HDR *pcx_read_header(PCX_HDR *hdr, FILE *f)
{
    fseek(f, 0L, SEEK_SET);     /* header is at top of file */
    if (fread(hdr, 1, sizeof(PCX_HDR), f) != sizeof(PCX_HDR))
        return(NULL);
    else
        return(hdr);
}

/**** p c x _ p r i n t _ h e a d e r ****
    Abstract:   Printf's a PCX file header data to a given file.
    Parameters: The PCX file header, the file to write the data to.
    Returns:    Nothing
****************************/
void pcx_print_header(PCX_HDR *hdr, FILE *f)
{
    char *t;
    if (hdr->pcx_id != 0x0A)
    {
        fprintf(f, "Not a PCX file.\n");
        return;
    }
    switch (hdr->version) {
    case 0: t="2.5"; break;
    case 2: t="2.8 with palette info."; break;
    case 3: t="2.8 without palette info."; break;
    case 5: t="3.0"; break;
    default: t="unknown."; break;
    }
    fprintf(f, "PCX Version %s\n", t);
    fprintf(f, "Compression: %s\n",
                hdr->encoding==1 ? "Run length" : "Unknown");
    fprintf(f, "%d bits per pixel\n", hdr->bpp);
    fprintf(f, "Image from (%d, %d) to (%d, %d) pixels.\n",
            hdr->upleftx, hdr->uplefty, hdr->lorightx, hdr->lorighty);
    fprintf(f, "Created on a device with %d x %d dpi resolution.\n",
            hdr->display_xres, hdr->display_yres);
    fprintf(f, "The image contains %d image planes.\n", hdr->nplanes);
    fprintf(f, "There are %d bytes per line of data after decompression.\n",
                hdr->bytesperline);
    fprintf(f, "There are %ld total bytes in the image.\n",
                ((long)hdr->lorighty - (long)hdr->uplefty + 1) *
                 (long)hdr->bytesperline);
    return;
}

/**** p c x _ a l l o c _ l i n e ****
    Abstract:   Allocates enough space to store a complete scan line from the
                current PCX file.
    Parameters: The header pointer.
    Returns:    A pointer to a "big enough" block of allocated space, or
                null if not enough space or an error occurred.
****************************/
BYTE *pcx_alloc_line(PCX_HDR *hdr)
{
    return(calloc(hdr->nplanes, hdr->bytesperline));
}

/**** p c x _ n e x t _ l i n e ****
    Abstract:   Reads the next line from the PCX file.
    Parameters: Pointer to the header, a pointer to the line area (or NULL
                for automatic allocation), and the open data file.
    Returns:    A pointer to the line area, or NULL if there was a problem.
    Comments:   To read the first line, call pcx_read_header() first.
                This sets the file position to the beginning of the data.
****************************/
BYTE *pcx_next_line(PCX_HDR *hdr, BYTE *line, FILE *f)
{
    int c, len;
    BYTE *dp;
    WORD linesize = hdr->nplanes * hdr->bytesperline;
    WORD i;
    if (line == NULL)
        if ((line = pcx_alloc_line(hdr)) == NULL)
            return(line);
    dp = line;                  /* point to data */
    for (i=0; i<linesize; )
    {
        if ((c = fgetc(f)) == EOF)
            return(NULL);
        if ((c & PCX_COMPRESSED) == PCX_COMPRESSED)

        {
            len = (c & PCX_MASK);
            if ((c = fgetc(f)) == EOF)
                return(NULL);
            memset(dp, (BYTE)c, len);
            dp += len;
            i += len;
        }
        else
        {
            *dp++ = (BYTE)c;
            i++;
        }
    }
    return(line);
}





<a name="016e_000b"><a name="016e_000b">
<a name="016e_000c">
[LISTING TWO]
<a name="016e_000c">

/*+
    Name:       pcx.h
    Author:     Kent J. Quirk
    Abstract:   This file contains information required when handling
                PCX files.
-*/

/********************
    Need these to handle the PCX data below.
*********************/
typedef unsigned char BYTE;
typedef unsigned int  WORD;

/********************
    This is the definition of the PCX header.
*********************/
typedef struct {
    BYTE pcx_id;                        /* Always 0x0A for PCX files */
    BYTE version;                       /* Version of the PCX format */
    BYTE encoding;                      /* 1 = RLE (RLL) compression */
    BYTE bpp;                           /* Number of bits per pixel */
    WORD upleftx, uplefty;              /* position of upper left corner */
    WORD lorightx, lorighty;            /* lower right corner (inclusive) */
    WORD display_xres, display_yres;    /* resolution in dpi of display */
    BYTE palette[48];                   /* palette data if it fits */
    BYTE reserved;
    BYTE nplanes;                       /* number of bit planes of data */
    WORD bytesperline;                  /* # bytes in an uncompressed line */
    WORD palletteinfo;
    BYTE reserved2[58];
} PCX_HDR;

/********************
   These two definitions are used to decompress data in the PCX file.
   (The compressed count byte has the top two bits set).
*********************/
#define PCX_COMPRESSED 0xC0
#define PCX_MASK 0x3F

/********************
    These prototypes declare the PCX read subroutines.
*********************/
PCX_HDR *pcx_read_header(PCX_HDR *hdr, FILE *f);
BYTE *pcx_alloc_line(PCX_HDR *hdr);
BYTE *pcx_next_line(PCX_HDR *hdr, BYTE *line, FILE *f);
void pcx_print_header(PCX_HDR *hdr, FILE *f);





<a name="016e_000d"><a name="016e_000d">
<a name="016e_000e">
[LISTING THREE]
<a name="016e_000e">

/*+
    Name:       prpcx.c
    Author:     Kent J. Quirk
    Abstract:   This program prints .PCX files (as created by PC Paintbrush
                and other software) on a PostScript printer by converting
                them to a PS-compatible image.  The user can scale and
                position the image.
-*/

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>

#define BUFSIZE 100

#include "pcx.h"

typedef struct {
    int xpos;
    int ypos;
    int width;
    int height;
    int scale;
    int invert;
    int prt_res;
    int dumphdr;
} MAPPING;

/**** c o p y _ p s _ h e a d e r ****
    Abstract:   Opens the PS header file and copies it to the output.
    Parameters: Filename of the current file (the .PS extension is added
                here) and the output file pointer.
    Returns:    0 if successful, 1 if failure.
****************************/
char *copy_ps_header(char *name, FILE *outfile, char *stop)
{
    static char buf[BUFSIZE];
    char *bp;
    static FILE *f = NULL;
    if (f == NULL)
    {
        strcpy(buf, name);
        if ((bp = strchr(buf, '.')) != NULL)
            *bp = 0;
        strcat(buf, ".ps");             /* open file with this name but .ps ext */
        if ((f = fopen(buf, "r")) == NULL)
        {
            fprintf(stderr, "Unable to open PostScript header file '%s'\n",
                buf);
            return(NULL);
        }
    }
    else
    {
        fputs(buf, outfile);
    }
    while (fgets(buf, BUFSIZE, f) != NULL)
    {
        if ((stop != NULL) && (strncmp(buf, stop, strlen(stop)) == 0))
            return(buf);                /* bail out right now */
        fputs(buf, outfile);
    }
    fclose(f);
    f = NULL;
    return(NULL);
}

/**** d o f i l e ****
    Abstract:   Processes a single PCX file.
    Parameters: char *filename - the input PCX filename (.PCX optional)
                MAPPING *map - the structure containing page position info
                char *psname - the PostScript prologue (.PS will be forced)
                FILE *outfile - the open output file
    Returns:    0 if successful, 1 if no file generated
****************************/
int dofile(char *filename, MAPPING *map, char *psname, FILE *outfile)
{
    FILE *f;
    PCX_HDR hdr;
    WORD i, j, xsize, ysize;
    BYTE *lineptr = NULL;
    char *t;
    char buf[BUFSIZE];
    long bbox_x, bbox_y;
    strcpy(buf, filename);
    if (strchr(buf, '.') == NULL)
        strcat(buf, ".pcx");     /* add .PCX if needed */
    if ((f = fopen(buf, "rb")) == NULL)
    {
        fprintf(stderr, "Unable to open '%s'\n", buf);
        return(1);
    }
    if (pcx_read_header(&hdr, f) == NULL)
    {
        fprintf(stderr, "Unable to read header for file '%s'.\n", buf);
        fclose(f);
        return(1);
    }
    if (map->dumphdr)
    {
        pcx_print_header(&hdr, stdout);
        return(1);
    }
    if (hdr.nplanes != 1)
    {
        fprintf(stderr, "Only able to read monochrome .PCX files.\n");
        fclose(f);
        return(1);
    }
    xsize = hdr.lorightx - hdr.upleftx + 1;
    ysize = hdr.lorighty - hdr.uplefty + 1;

    t = copy_ps_header(psname, outfile, "%%BoundingBox");
    bbox_x = (long)xsize * (long)map->width  * (long)map->scale / 10000L;
    bbox_y = (long)ysize * (long)map->height * (long)map->scale / 10000L;
    bbox_x += map->xpos;
    bbox_y += map->ypos;
    sprintf(t, "%%%%BoundingBox: %d %d %ld %ld\n", map->xpos, map->ypos,
            bbox_x, bbox_y);
    t = copy_ps_header(psname, outfile, NULL);

    fprintf(outfile, "/bmap_wid %d def\n", xsize);
    fprintf(outfile, "/bmap_hgt %d def\n", ysize);
    fprintf(outfile, "/bpp %d def\n", hdr.bpp);
    fprintf(outfile, "/res %d def\n\n", map->prt_res);
    fprintf(outfile, "/x %d def\n", map->xpos);
    fprintf(outfile, "/y %d def\n\n", map->ypos);
    fprintf(outfile, "/scy %d 100 div def\n", map->height);
    fprintf(outfile, "/scx %d 100 div def\n", map->width);
    fprintf(outfile, "/scg %d 100 div def\n\n", map->scale);
    fprintf(outfile, "scaleit\n");
    fprintf(outfile, "imagedata\n\n");

    for (i=0; i<ysize; i++)
    {
        lineptr = pcx_next_line(&hdr, lineptr, f);
        if (map->invert)                 /* invert if necessary */
            for (j=0; j < xsize/8; j++)
                lineptr[j] = ~lineptr[j];
        for (j=0; j < xsize/8; j++)
            fprintf(outfile, "%02X", lineptr[j]);
        fprintf(outfile, "\n");
    }
    fprintf(outfile, "\nshowit\n");
    free(lineptr);
    fclose(f);
    return(0);
}

/**** u s a g e ****
    Abstract:   Prints a usage message and dies.
    Parameters: None
    Returns:    Never returns.
****************************/
void usage()
{
    printf("PRPCX:  by Kent Quirk\n");
    printf("   Given a .PCX file, this program creates a PostScript file \n");
    printf("    which will print the image.\n");
    printf(" PRPCX [-wW] [-hH] [-xX] [-yY] [-sS] [-rR] [-d] [-i] filename\n");
    printf("   Options include:         (units) [default]\n");
    printf("   -sSCA  set overall scale factor (percent) [100]\n");
    printf("   -wWID  set horizontal scale factor (percent) [100]\n");
    printf("   -hHGT  set vertical scale factor (percent) [100]\n");
    printf("   -xPOS  set horizontal position (points from left) [0]\n");
    printf("   -yPOS  set vertical position (points from bottom) [0]\n");
    printf("   -rRES  set printer resolution (dpi) [300]\n");
    printf("   -d     dump PCX file info to stdout [off]\n");
    printf("   -i     invert image [off]\n");
    printf("   -oFIL  set output filename, or use SET PRPCX=filename\n");
    printf("   The defaults print the image at one pixel per device pixel\n");
    printf("   at the lower left corner of the page.\n");
    printf("   PRPCX.PS must be in the same directory as PRPCX.EXE.\n");
    exit(1);
}

/**** m a i n ****
    The main routine for PRPCX.  Sets defaults, parses command line,
    and calls dofile().
****************************/
int main(int argc, char *argv[])
{
    int i;
    MAPPING map;
    FILE *outfile = stdout;
    char *outfname = NULL;
    char *filename = NULL;
    map.xpos = map.ypos = 0;
    map.width = map.height = map.scale = 100;
    map.invert = 0;
    map.prt_res = 300;
    map.dumphdr = 0;
    if (argc < 2)
        usage();
    for (i=1; i<argc; i++)
    {
        if (argv[i][0] == '-' || argv[i][0] == '/')
        {
            switch (argv[i][1])
            {
            case 'x':  case 'X':
                map.xpos = atoi(argv[i]+2);
                break;
            case 'y':  case 'Y':
                map.ypos = atoi(argv[i]+2);
                break;
            case 'h':  case 'H':
                map.height = atoi(argv[i]+2);
                break;
            case 'w':  case 'W':
                map.width = atoi(argv[i]+2);
                break;
            case 's':  case 'S':
                map.scale = atoi(argv[i]+2);
                break;
            case 'r': case 'R':
                map.prt_res = atoi(argv[i]+2);
                break;
            case 'i': case 'I':
                map.invert = !map.invert;
                break;
            case 'd': case 'D':
                map.dumphdr = 1;
                break;
            case 'o': case 'O':
                outfname = argv[i]+2;
                break;
            case '?':
                usage();
                break;
            default:
                fprintf(stderr, "Unknown option %s\n", argv[i]);
                usage();
                break;
            }
        }
        else                    /* process a file */
        {
            filename = argv[i];
        }
    }
    if ((outfname != NULL) || ((outfname = getenv("PRPCX")) != NULL))
    {
        if ((outfile = fopen(outfname, "w")) == NULL)
        {
            fprintf(stderr,"Unable to open output file %s", outfname);
            exit(1);
        }
    }
    i = dofile(filename, &map, argv[0], outfile);
    fclose(outfile);
    return(i);
}






<a name="016e_000f"><a name="016e_000f">
<a name="016e_0010">
[LISTING FOUR]
<a name="016e_0010">

#
# Program: PRPCX
#

.c.obj:
        cl -c  -W2 -Zid -Od -AS $*.c

pcx.obj : pcx.c pcx.h

prpcx.obj : prpcx.c pcx.h

prpcx.exe : prpcx.obj pcx.obj
        echo prpcx.obj+ >prpcx.lnk
        echo pcx.obj >>prpcx.lnk
        echo prpcx.exe >>prpcx.lnk
        echo nul >>prpcx.lnk
        link /NOI $(LDFLAGS) @prpcx.lnk;






<a name="016e_0011"><a name="016e_0011">
<a name="016e_0012">
[LISTING FIVE]
<a name="016e_0012">

%!PS-Adobe 1.0
%%Title:
%%Creator:
%%Pages: 1
%%BoundingBox:
%%EndComments

gsave

       % the next item translates the image from
       % top-to-bottom .PCX format to PS bottom-to-top
/xform
{
    [ bmap_wid 0 0 bmap_hgt neg 0 bmap_hgt ]
} def

/readproc
{
    { currentfile picstr readhexstring pop }
} def

/scaleit
{
    x y translate
    bmap_wid bmap_hgt scale
    72 res div 72 res div scale
    scx scg mul scy scg mul scale

    /picstr bmap_wid 8 idiv string def

} def

/imagedata
{
    bmap_wid bmap_hgt bpp xform readproc image
} def

/showit
{
    grestore
    showpage
} def

% Will generate <bmap_hgt> lines of image data,
% each with <bmap_wid>/8 bytes of data (in hex).
%%EndProlog











Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.