Raster Graphics

One of the common questions I have gotten over the years is how to handle other graphics file formats in PostScript. People often ask about including GIFs, JPEGs, and the like in a PostScript file. While this sort of thing is really beyond the scope of this guide, the basics of handling so-called raster graphics is not.

Raster Graphics Basics

Raster graphics is that style of graphics in which the image is broken up into a matrix of picture elements (pixels). The matrix will have a certain number of rows, each containing a certain number of pixels. Each pixel can be assigned any of a number of colors. The number of colors depending upon the “depth” of the image, often expressed as the number of bits needed to encode all the colors. Typical bit depths used today are: 1 (two colors, usually black and white), 2 (four colors, usually shades of gray), 4 (16 colors), 8 (256 colors), 16 (65,536 colors), 24 (so-called true color), and 32 (more colors than you can shake a stick at).

The numbers of rows and columns gives you the resolution. If there are c columns and r rows, the image is referred to as a r x c image. If the image is to have a certain physical size, this size combined with the number of pixels give you the number of dots-per-inch (DPI) of the image, which is a measure of its resolution. The higher the DPI, the smaller the dots, and the harder it is to see them as individuals.

Raster graphics are convenient in that they can represent photo-realistic images quite easily, but they have limitations. Because the pixels are arranged in a regular pattern, weird moire patterns can appear if they are displayed on a monitor incorrectly, or if they represent an image with a regular pattern that interacts badly with the pattern of the pixels. Likewise, if the resolution is too low and the contrast is too high, certain pixels can stand out and leave the image with the “jaggies.”

Raster Graphics in PostScript

Most graphics work in PostScript is done in vector graphics style. This style of graphics is where the image is composed of lines and curves that are described mathematically. It is the style of graphics we have used throughout the guide. The advantage of vector graphics is that you can do all sorts of mathematical operations on the image (rotate, scale, etc.) and still get a decent image. An implication of this is that vector graphics are device independent, since they do not care about the display resolution of the display device. Still, PostScript recognizes the need for support of raster graphics, and so it provides a set of operators just to display raster graphics.

The main operator is image, and it is fairly complex. Go grab yourself a cup of coffee, stretch your legs, and prepare to tuck in.

image takes five arguments that describe the image to be displayed and paints that image in a square with one corner at (0, 0) and the other at (1, 1) in the current coordinate frame. All the operands describe the image data and how it should be used to fill up that square. Of course, you probably do not want to draw images in the unit square at (0, 0) all the time, so you must use scale, rotate, and translate to move the unit square to the desired location (and size).

The image operator is used in the following way: width height depth matrix data image -. The operands width and height define the size and shape of image matrix in terms of pixels (in the image data, not the display results). The operand depth describes the number of bits per pixels and, hence, the number of shades of gray. Legal values here are 1 (black and white), 2 (four shades), 4 (16 shades), 8 (256 shades), and 12 (4,096 shades). The operand matrix is a PostScript transform matrix that maps from the unit square to the image’s pixel coordinates. The image’s coordinates go from (0, 0) in the lower left to (width, height) in the upper right. The last operand, data is the source of the actual image data. In Level 1 PostScript, this is a procedure, but it can be any number of things in Level 2 and Level 3. Just sticking with Level 1 for now, this procedure is called to fetch all the data for the image, as needed. The procedure returns a string, and the bits within the string are taken and dismantled to create the image. If the procedure does not return enough data to cover the whole image, it is called repeatedly until all the pixels are accounted for. The order at which the pixels are handled is left to right, bottom to top.

Clear? I didn’t think so. Let’s take a look at a simple example that will show you the basics. Let’s draw a simple smiley face. First, let’s take a look at how the smiley face will be laid out in the matrix:

..XXXX..C3
.X....X.BD
X.X..X.X5A
X......X7E
X.X..X.X5A
X..XX..X66
.X....X.BD
..XXXX..C3

The left eight columns are the eight columns of the image. The “X” represents a black pixel, and the “.” represents a white pixel. Since black is represented by a 0 in PostScript, and white by a 1, we can convert this 8x8 matrix into an eight byte sequence. The ninth column is the hexadecimal encoding of the row... taking the others columns as a binary number with the dots representing 1’s and the X’s representing 0’s.

Now, let’s take this image data and try to build up an actual image. First, we need to map the unit square to the location we want to show the image in. Let’s make the image a 1 inch square image with the lower left corner at (72, 72).

gsave                  % We're mucking about with graphics state... save the original
  72 72 translate      % position the lower left at (72, 72)
  72 72 scale          % make the image 1 inch square

Now, we set up the actual image data.

  8                    % 8 columns in the image
  8                    % 8 rows in the image
  1                    % 1-bit per pixel: black and white
  [8 0 0 8 0 0]        % map the unit square to (0, 0) - (8, 8)
  {<c3bd665a7e5abdc3>} % the image data as a hex-encoded string
  image                % actually draw the image
grestore

Note that the pixel data maps left to right the same way the bits do when you write them in binary. That is, the left-most pixel for a given byte in the data maps to the left-most bit in the byte. Also, note the funny way the string is specified. It is written in hexadecimal using the < and > notation instead of the more usual parenthesis notation. This notation indicates that what is contained between the < and > is a string of 8-bit data bytes encoded in hex. You will see this notation fairly often in working PostScript, since it is a convenient way to store binary data in an ASCII format.

A Gradient

This example is all well and good, but it is just black and white. How do you deal with gray scale images? The procedure is similar, you just specify a different bit depth and lay out your data in a slightly different manner (instead of a single bit per pixel, you will now need to map multiple bits per pixel. As an example, let’s try to make a horizontal gradient fill that goes through sixteen different shades:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Sixteen different shades implies 4 bits per pixel, which means that this data can be represented in 8 bytes again (2 pixels per byte). Encoded in hexadecimal again, the data for the above gradient is: 0123456789ABCDEF. Now, we just need to do with this data what we did before, with one exception. The image data no longer represents an 8x8 image, instead it represents a 16x1 image that is 4 bits deep... we need to modify the settings accordingly. Also, instead of scaling this image to a 1 inch square, let’s make it 2 inches high by 1 wide and set it next to the smiley face:

gsave
  216 72 translate     % lower-left of images at (216, 72)
  72 144 scale         % size of rendered image is 72 points by 72 points
  16                   % 16 pixels wide
  1                    % 1 pixel high
  4                    % 4 bits per pixel
  [16 0 0 1 0 0]       % transform array... maps unit square to pixels
  {<0123456789ABCDEF>} % the image data itself
  image                % let's draw!
grestore

Note that, even though the image data is for a one line image, we can scale that single line to fill just about any area. By the way, drawing with the image operator is very much like drawing with any other operator. In particular, clipping can be used to control what parts of the page can be filled by image, so you can use it to do interesting effects like shapes (or text) with gradient fills or with a photographic image as a fill pattern.

The Basics of Color

So, these examples are nice and all, but they are in dull monochrome. What if you want to do color? PostScript does include a handy operator for color images called, creatively enough colorimage. The colorimage operator adds a number of operands to handle the addition of color and to provide for a number of ways of supplying the color data.

The first thing to consider is how you want to specify the colors. Is your image grayscale? RGB? CMYK? This information will indicate the number of color channels you need. Grayscale takes one. RGB takes three (one for red, one for green, and one for blue). Finally, CMYK takes four: cyan, magenta, yellow, and black. Next, you need to think about how these channels are going to be provided to the colorimage operator; will they be interleaved into a single data source string, or will they be broken out into separate data sources. These two decisions will determine the number of additional operands to colorimage: one specifies the number of channels, one specifies whether the channels are separate or interleaved, and then there is one for each channel data source.

To make this a bit more concrete, let’s take a look at a color gradient, which will be a variant of the previous gradient:

Gradient: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Red: 00000000 00000000
Green: 15141312111098 76543210
Blue: 01234567 89101112131415

So, we will now lay this information out in a very similar manner to the original gradient. The example, however, will use three color channels, and they will be provided by separate data sources:

gsave
  360 72 translate     % set lower left of image at (360, 72)
  72 144 scale         % size of rendered image is 72 points by 72 points
  16                   % number of columns per row
  1                    % number of rows
  4                    % bits per color channel (1, 2, 4, or 8)
  [16 0 0 1 0 0]       % transform array... maps unit square to pixels
  {<0000000000000000>} % the red image data
  {<FEDCBA9876543210>} % the green image data
  {<0123456789ABCDEF>} % the blue image data
  true                 % pull channels from separate sources
  3                    % 3 color channels (RGB)
  colorimage
grestore

Note that the bits-per-pixel operand is really a bits-per-pixel-per-channel operand. This is really no different than for image, it is just that image always works with a single channel. In fact the call:

  w h bpp matrix data image

is exactly equivalent to:

  w h bpp matrix data true 1 colorimage

Wrap-up

You have now seen a basic example of three different types of raster graphics in PostScript (you can view a complete example of all three). This is only a start, however.