Paint Files
Volume Number: 3
Issue Number: 5
Column Tag: Pascal Procedures
Reading Paint Files
By Gary Palmer, University of Nevada
Paint files have become generic on the Macintosh as a way of transferring bit
mapped type graphics information between applications. Several commercial programs
have come out that can read and write MacPaint file formats, making support of this
type of Macintosh object an important design consideration. FullPaint by Ann Arbor
Softworks is one of the most popular MacPaint alternatives because it is the most
faithful to the original design in simplicity and function, yet improves on the obvious
limitations of MacPaint without introducing any new wrinkles or problems to get in
the way. Thunderscan by Thunderware & Andy Hertzfeld opens, reads and writes paint
files with the added feature that you can use Andy's wonderful "moving window
scroller to select any part or all of a paint drawing for alteration. As such, it is a
useful paint editor. Paint Cutter by Silicon Beach Software has some important
features including the ability to make large selections and rotate large selections of a
paint diagram. This is e specially useful in combination with Thunderscan drawings
that must be rotated. SuperPaint also by Silicon Beach Software offers a
"MacDraw-MacPaint" combo that can be very powerful in addition to reading and
writing paint files. As a result of all this developer support for paint documents, the
ability to read and display a paint type document could be an important design element
in your application.
As figure 1 shows, our program this month illustrates how to open and read a
MacPaint type document, displaying it in a window at 3/8's of it's normal size. Add this
to a paint type program and you can expand to editing and any number of other
quickdraw functions.
MacPaint documents are described in technical note number 86 released last
August, and according to the note header, the note was written by Bill Atkinson in
1983! Figure 2 summarizes the format of the MacPaint document.
The beginning of a MacPaint file is a 512 byte header block, which contains the
version number, pattern array, and empty space. The header matches the following
record example:
MPHeader = RECORD
version: LongInt;
PatArray: Array [1..39] of Pattern;
Future: PACKED ARRAY [1..204] OF SignedByte;
END;
Typically, the version number is zero, in which case the patterns are ignored and
MacPaint uses the default patterns instead. Applications can ignore the header by
skipping it when reading a document or by writing out 512 bytes of zero when writing
a paint document. (Recall that a Pattern is 8 bytes so the PatArray is 38*8 = 304
bytes.)
Paint documents are a screen dump at 72 dots per inch, of the bit map, which
represents a 576 pixel wide (72 bytes times 8 bits per byte) by 720 pixel tall
array, thus covering an 8 by 10 inch document. Each line of 72 bytes, representing
576 pixels is shoved through the trap PackBits to output a single pixel line, until all
720 lines have been packed. Therefore, the maximum size of an unpacked bit map is
720 lines by 72 bytes per line or 51,840 bytes. With the PackBits routine, this
compresses down to about 10,000 bytes normally.
Fig. 1 Our Paint Reader Program
Program Details
To read the file, we call the standard file routine to get a file name and reference
number, then call FSOpen to open the file. We can skip the 512 byte header by
positioning the file marker past the first 512 bytes with SetFPos. We determine the
size of the file by calling GetEOF, and then subtracting the 512 header bytes to
determine the number of bytes making up the bit map, which is what we want to read
and display. We then use FSRead to read in the bit map into the buffer and close the file.
(See figure 3 above for flowchart.)
To prepare the bit map for display, we have to unpack it. This can be done by
calling UnPackBits in a loop, unpacking 72 bytes at a time until all the lines of the
document (720) are done. The nice thing about MacPaint is that it doesn't try to do
anything fancy with variable size files. All files have the same 720 lines to unpack.
Sometimes simplicity is a great virtue! In our program, we just divide the bit map in
two and call UnPackBits twice.
Fig. 2 Format of Paint File Data Fork
Once the bit map is unpacked, we can copy the bit map to an off-screen bit map
we have allocated, and then to our window to display the document in a destination
rectangle. By making the destination rectangle 3/8's the size of the document, we can
nicely display the entire drawing at a reduced size. In our program, we have scaled
everything to ScreenBits.bounds, so on a larger display, a bigger proportional picture
would also be displayed. This is a good habit to get into in preparation for Macintosh II.
Our main program is fairly simple. We perform the standard init stuff and open a
window. Then we begin our display loop where we continue to call standard file until
the user clicks cancel. This is done by calling our procedure GetPaintImage, which in
turn calls standard file, and then attempts to open the file and unpack the bit map,
followed by DisplayPaintFile, which copies the off-screen bit map to our window. A
simple event loop is used to allow a cmd-shift-3 to capture the window contents and
save it to disk to help write this article! The real work is in our paint file manager
unit, where the actual reading and displaying of the file takes place.
Standard File Dialog
Figure 4 shows our standard file dialog from which we get the name of the file.
We call it with an allowed file type of PNTG so that we get all MacPaint type files. The
standard file dialog fills in a Reply record from which we can extract the file name and
reference number for the FSOpen call. After opening the file we call our ReadPaintFile
routine to read in the packed bit map and return to us a pointer to the bit map. Using
the pointer, we call UnpackBits twice to unpack and copy the bit map to our off-screen
bit map from which we will copy the image to the window, as shown in figure 3.
Fig. 3 Program Flowchart
Debugging Aid
A useful feature when dealing with the file manager is our doMessage procedure.
This little routine takes four string arguments and stuffs them into the low memory
globals with ParamText trap. A simple dialog is displayed that reads the four low
memory parameters and displays them in the dialog box. This is useful for a quick and
dirty output device, both for the user, and for debugging. Since every file manager call
returns an error code that must be checked, it can be a real pain while you are writing
and testing code, to deal with a formal exception handler procedure. Our little dialog
box lets you know what happened and where and by using NumToString, can be an easy
way to find out the values of parameters in a hurry. Whenever you need some info, just
stick in a doMessage() line in your code. Of course, this violates the resource manager
thinking of Apple by putting human readable text in your program rather than in your
resource fork! But, when you are done with development, a simple search on doMessage
would find all the strings which could then be transferred to a string resource for the
final product.
Fig. 4 Calling Standard File
Each time we get a file, our doMessage proc displays a dialog box showing how
many bytes there are in the packed bit map. It also shows if the number of bytes is odd.
MacPaint files will always return even, so the dialog in figure 5 is shown. But
sometimes another program will return an odd number of bytes, in which case, the
dialog in figure 6 is displayed. Our program will attempt to read any paint type file,
and other error checking traps will catch any problem and return the user to the
desktop.
Fig. 5 Paint file has even bytes
Fig. 6 Other files may have odd bytes
Combining this program with the C column this month and adding in Joel West's
article on printing a few months back, you have the making of the next FullPaint
application!
PROGRAM ReadPaint;
{ Reads paint files and displays them in 3/8 normal size in }
{ center of large window. After reading a file, press }
{ the mouse button to read another file. Choosing cancel }
{ in the dialog box quits the program. }
{ Lightspeed Pascal version, but very generic! }
USES
PaintFileMgr;
VAR
theWindow : WindowPtr;
theWindowRec : WindowRecord;
WindowRect : Rect;
MaskEvents : Integer;
theImagePtr : Ptr;
DoIt : boolean;
Event : event record;
{ procedures start here }
PROCEDURE crash;
BEGIN
ExitToShell;
END;
PROCEDURE StandardInit;
BEGIN
InitGraf(@thePort);
InitFonts;
MaskEvents := EveryEvent - keyUpMask;
FlushEvents(MaskEvents, 0);
InitWindows;
InitMenus;
TEInit;
InitDialogs(@crash);
InitCursor;
PenNormal;
END;{StandardInit}
PROCEDURE OpenWindow;
CONST
mBarHeightGlobal = $BAA;
VAR
screen : rect;
mBarHeight : Integer;
MemoryPtr : ^Integer;
BEGIN
MemoryPtr := pointer(mBarHeightGlobal);
mBarHeight := MemoryPtr^;
screen := screenBits.bounds;
SetRect(WindowRect, screen.left + 4, screen.top + mBarHeight + 20,
screen.right - 4, screen.bottom - 4);
theWindow := NewWindow(@theWindowRec, WindowRect, 'PaintFile', True,
0, Pointer(-1), False, 0);
SetPort(theWindow);
END;
{ main program }
BEGIN
MaxApplZone;
MoreMasters;
MoreMasters;
StandardInit;
OpenWindow;
REPEAT {on theImagePtr}
GetPaintImage(theImagePtr);
DisplayPaintFile(theImagePtr);
IF theImagePtr <> NIL THEN
BEGIN
DisposPtr(theImagePtr);
REPEAT {on button }
systemtask;
DoIt := GetNextEvent(KeyDownMask, Event);
IF DoIt THEN
CASE Event.what OF
KeyDown, Autokey :
BEGIN
sysbeep(5);
END;
OTHERWISE
BEGIN
END;
END;
UNTIL button;
END;
UNTIL theImagePtr = NIL;
END.
{___________________________________________________________}
{PAINTFILEMGR Unit }
{ }
{ Procedures for opening and displaying Paint files with }
{ high level routines from Toolbox file manager. }
{ might not work in a 128K Mac, but could probably be made}
{ to work by reading and unpacking the file in smaller }
{ chunks. }
{AUTHOR }
{ Gary B. Palmer. Public domain. October 25, 1986. }
{ Author reserves right to use in own programs. }
{___________________________________________________________}
UNIT PaintFileMgr;
INTERFACE
PROCEDURE GetPaintImage (VAR ImagePtr : Ptr);
PROCEDURE DisplayPaintFile (ImagePtr : Ptr);
IMPLEMENTATION
{--------- Internal routines --------}
PROCEDURE doMessage (mes0 : str255;
mes1 : str255;
mes2 : str255;