[comp.graphics] MacPaint format

cameron@symcom.math.uiuc.EDU (12/03/87)

While we're on the subject of file formats used by this or that
computer, program, or printer, could someone please tell me what
the format of a MacPaint document is?

I have obtained some MacPaintings from a friend who has a Mac 
and a modem.  I have a program obtained from a PC-oriented BBS 
which will read these files and display them on my (CGA-compatible)
screen, but the aspect ratio is wrong, and anyway, I want to be 
able to manipulate the images (crop, rotate, scale, &c.), not just 
look at them.  When I view the file as straight text I see some
recognizable character strings which I assume are things like 
file name, creator ID, and such.  I want to be able to extract
the bitmap and throw the Finder-oriented stuff away.

Thanks in advance.  Email is OK, or post here if you think it's of
general interest -- I'm a regular reader of this newsgroup
(notwithstanding the fact that a lot of it is Greek to me).
--------
Cameron Smith
Symbolic Computation Lab      "You knew the job was dangerous
Math Dept.                               when you took it, Fred!"
University of Illinois                                 -- SuperChicken
Urbana IL 61801
(217) 333-4654        cameron@symcom.math.uiuc.edu

wel@ncsuvx.ncsu.edu (Warren E. Lewis) (12/08/87)

________________________________________________________________________________

Macintosh Technical Notes		 p

#86: MacPaint Document Format

See also:	MacPaint User Manual
		ToolBox Utilities

Written by:	Bill Atkinson	1983
Modified by:	Bryan Johnson	August 19, 1986

________________________________________________________________________________


This technical note describes the internal format of a MacPaint document.  It
is the same as the description in the Macintosh Miscellaneous section of
early versions of Inside Macintosh.  It has been verified to be correct for
version 1.5 of MacPaint.
________________________________________________________________________________

MacPaint documents are easy to read and write and have become a standard
interchange format for full-page images on Macintosh.  Their internal format is
described here to help developers generate and read MacPaint documents.

Documents created by MacPaint have a file type of PNTG and creator ID of MPNT.
MacPaint documents use only the data fork; the resource fork is not used and may
ignored. The data fork contains a 512 byte header and then the compressed data
representing a single bit map of 576 pixels wide by 720 pixels tall.  At 72 pixe
inch, this bit map occupies the full 8 by 10 inch printable area of the standard
ImageWriter printer page.

Header
The first 512 bytes of the document form a header with a 4 byte version number
(default = 2), then 38*8 = 304 bytes of patterns, then 204 unused bytes reserved
future expansion.  As a Pascal record it could look like:

	MPHeader = RECORD
		Version:	LONGINT;
		PatArray:	ARRAY [1..38] of Pattern;
		Future:	PACKED ARRAY [1..204] of SignedByte;
	END;


If the version number is zero, the rest of the header block is ignored and defau
are used, so programs generating MacPaint documents can simply write out 512 byt
zero as the document header.  Most programs which read MacPaint documents can
simply skip over the header when reading.


BitMap
Following the header are 720 compressed scanlines of data which form the 576 pix
wide by 720 pixel tall bit map. Without compression, this bit map would occupy 5
bytes and chew up disk space pretty fast; typical MacPaint documents compress to
10 Kbytes using the PackBits procedure in the Macintosh ROM to compress runs of
equal bytes within each scanline.  The bit map part of a MacPaint document is si
output of PackBits called 720 times, with 72 bytes input.


Reading Sample

{ ReadMPFile:
This is a small example program written in TML Pascal that demonstrates how
	to read MacPaint files.  As a final step, it takes the data that was read
and  displays it on the screen to show that it worked. Caveat: This is not
	intended to be an example of good programming practice, in that the possible
errors merely cause the program to exit. This is VERY uninformative, and
there should be some sort of error handler to explain what happened.  For
simplicity, and thus clarity, those types of things were deliberately not
included.  This example will not work on a 128K Macintosh, since memory
allocation is done too simplistically.

}

PROGRAM ReadMPFile;


{$I 'HD:Pascal System:MemTypes.ipas'  }
{$I 'HD:Pascal System:QuickDraw.ipas' }
{$I 'HD:Pascal System:OSIntf.ipas' }
{$I 'HD:Pascal System:ToolIntf.ipas' }


CONST	DefaultVolume = 0;
			MaxFileSize = 51840; { maximum MacPaint file size, 720*72. }

VAR		srcPtr: 			Ptr;
			dstPtr: 			Ptr;
			saveDstPtr:		Ptr;
			srcFileName:		Str255;
			srcFile: 			INTEGER;
			srcSize: 			LONGINT;
			errCode:			INTEGER;
			scanLine:			INTEGER;
			aPort:				GrafPort;
			theBitMap:		BitMap;

BEGIN
	{ Initialize QuickDraw. }
	InitGraf(@thePort);
	{ Make a name of a file to read. }
	srcFileName := 'MP TestFile';
	{ Make a buffer that is the largest picture we can expect.  This
 		could be done in a more memory efficient manner. }
	srcPtr := NewPtr(MaxFileSize);
	IF srcPtr = NIL  THEN ExitToShell;

	{ Open the data file. }
	errCode := FSOpen(srcFileName,DefaultVolume,srcFile);

	IF errCode <> noErr  THEN ExitToShell;
	
	{ Skip the header. }
	srcSize := 512;
	errCode := FSRead(srcFile,srcSize,srcPtr);
	IF errCode <> noErr  THEN ExitToShell;

	{ Find out how big the file is, and figure out source size. }
	errCode := GetEOF(srcFile,srcSize);
	IF errCode <> noErr  THEN ExitToShell;
	srcSize := srcSize - 512;   { Remove the header from count. }

	
	{ Read the data into the buffer. The file mark is already past the header. }
	errCode := FSRead(srcFile,srcSize,srcPtr);
	IF errCode <> noErr THEN ExitToShell;

	{ Close the file we just read. }
	errCode := FSClose(srcFile);
	IF errCode <> noErr THEN ExitToShell;

	{ Create a buffer that will be used for the Destination BitMap.  This also
		has a maximum size possible, the same as the full MacPaint picture size. }

	dstPtr := NewPtr(MaxFileSize);
	IF dstPtr = NIL  THEN ExitToShell;
	saveDstPtr := dstPtr;

	{ Unpack each scanline into the buffer.  Note that 720 scanlines are
		guaranteed to be in the file.  (They may be blank lines)  In the
		UnPackBits call, the 72 is the count of bytes done when the file was
		created.  MacPaint does one scan line at a time when creating the file. }

	FOR scanLine := 1 to 720 DO
		BEGIN
    		UnPackBits(srcPtr,dstPtr,72);   {bumps both ptrs}
		END;

	{ The buffer has been fully unpacked. Create a port that we can draw into. }
	OpenPort(@aPort);

	{ Create a BitMap out of our saveDstPtr that can be copied to the screen. }
	theBitMap.baseAddr := saveDstPtr;
	theBitMap.rowBytes := 72;          { width of MacPaint picture }
	SetPt(theBitMap.bounds.topLeft, 0,0);
	SetPt(theBitMap.bounds.botRight, 72*8, 720); {maximum rectangle}

	{ Now use that BitMap and draw the piece of it to the screen.
		Only draw the piece that is full screen size (portRect). }

	CopyBits(theBitMap, aPort.portBits, aPort.portRect,
						aPort.portRect, srcCopy, NIL);

	{ That's it.  Now wait for the mouse button to leave.  Pause. }

	REPEAT
	UNTIL Button;

END.

Writing Sample

{ WriteMPFile:

	This is a small example program written in TML Pascal that demonstrates how
	to write MacPaint files.  It will use the screen as a handy BitMap to be
	written to a file.

}


PROGRAM WriteMPFile;


{$I 'HD:Pascal System:MemTypes.ipas'  }
{$I 'HD:Pascal System:QuickDraw.ipas' }
{$I 'HD:Pascal System:OSIntf.ipas' }
{$I 'HD:Pascal System:ToolIntf.ipas' }


CONST	DefaultVolume = 0;
			MaxFileSize = 51840; { maximum MacPaint file size, 720*72. }
			MaxDiv2 = 25920;	{ divided by 2 for half buffer size. }
			MaxDiv4 = 12960;	{ divided by 4 for clearing. }
			MaxDiv8 = 6480;	{ divided by 8 for bug avoidance. }


TYPE		ByteBuffer = PACKED ARRAY [1..512] of SignedByte;
			BigBuffer = PACKED ARRAY [1..MaxDiv4] of LONGINT;
			BigPtr = ^BigBuffer;
			

VAR		srcPtr: 			Ptr;
			dstPtr: 			Ptr;
			dstFileName:		Str255;
			dstFile: 			INTEGER;
			dstSize: 			LONGINT;
			errCode:			INTEGER;
			scanLine:			INTEGER;
			aPort:				GrafPort;
			dstBuffer:		ByteBuffer;
			I:						LONGINT;
			picturePtr:		BigPtr;
			tempPtr:			BigPtr;
			theBitMap:		BitMap;


BEGIN

	{ Initialize QuickDraw. }
	InitGraf(@thePort);

	{ Make a buffer that is the picture size. }
	picturePtr := NewPtr(MaxFileSize);
	IF  picturePtr = NIL  THEN ExitToShell;

	{ Now clear the buffer since it is a picture, and we want it to
	  start as a blank page.  Clear four bytes at a time.  This is done
	  in two steps since there is a bug with TML that won't allow us
	  to index an array larger than 32768.  This would normally be
	  done by setting the CLEAR bit for the NewPtr used in assembly,
	  but that would make this even more complex. }

	FOR  I := 1 to MaxDiv8  DO
		picturePtr^[I] := 0;
		

	{ Create the address half way through the block.  This keeps our
	  index of I smaller than 32768. }
	tempPtr := BigPtr (ORD4 (picturePtr) + MaxDiv2);

	{ Clear the rest of the block, 4 bytes at a time. }

	FOR I := 1 to MaxDiv8  DO
		tempPtr^[I] := 0;

	{ Open a port so we can get to the screen's BitMap easily. }
	OpenPort(@aPort);

	{ Create a BitMap out of our dstPtr that can be copied to the screen. }
	theBitMap.baseAddr := picturePtr;
	theBitMap.rowBytes := 72;  { width of MacPaint picture }
	SetPt(theBitMap.bounds.topLeft, 0,0);
	SetPt(theBitMap.bounds.botRight,72*8,720); {maximum rectangle}

	{ Draw the screen over into our picture buffer. }
	CopyBits(aPort.portBits, theBitMap, aPort.portRect,
 						aPort.portRect, srcCopy, NIL);


	{ Make a name of a file to write. }
	dstFileName := 'MP TestFile';

	{ Create the file, giving it the right Creator and File type.}
	errCode := Create(dstFileName, DefaultVolume, 'MPNT', 'PNTG');
	IF errCode <> noErr  THEN ExitToShell;
	
	{ Open the data file to be written. }
	errCode := FSOpen(dstFileName,DefaultVolume,dstFile);
	IF errCode <> noErr  THEN ExitToShell;
	

	{ Write the header as all zeros. }
	FOR I := 1 to 512 DO
		dstBuffer[I] := 0;
	dstSize := 512;

	errCode := FSWrite(dstFile,dstSize,@dstBuffer);
	IF errCode <> noErr THEN ExitToShell;


	{ Now go into a loop where we pack each line of data into the buffer, then
		write that data to the file.   We are using the line count of 72 in order
		to make the file readable by MacPaint.  Note that the Pack/UnPackBits can
		be used for other purposes. }
	srcPtr := theBitMap.baseAddr; { point at our picture BitMap }

	FOR scanLine := 1 to 720 DO
		BEGIN
			dstPtr := @dstBuffer; { reset the pointer to bottom }
			PackBits(srcPtr,dstPtr,72);  { bumps both ptrs}
			dstSize := ORD(dstPtr)-ORD(@dstBuffer);{calc packed size}
			errCode := FSWrite(dstFile,dstSize,@dstBuffer);
			IF errCode <> noErr THEN ExitToShell;
		END;

	{ Close the file we just wrote. }

	errCode := FSClose(dstFile);
	IF errCode <> noErr THEN ExitToShell;

END.

wew@naucse.UUCP (Bill Wilson) (12/08/87)

I just picked up a program called "The Graphics Link" that
allows the conversion of Graphics files from one format to another.
It even works with Mac files.  It can be purchased for $99
from PC Quick Art.  Call them at 1-800-523-1796.

  

ks26+@andrew.cmu.edu (Kenneth Sykes) (12/09/87)

The file description posted by Warren E. Lewis (8-Dec-87) is complete,
with the exception of what the PackBits/UnpackBits procedures do.  

The packed information consists of a control byte (8 bit signed integer)
followed by 
a set of 'appropriate' data.  If the control byte is greater than zero (n =
c), then the
next n bytes are to be copied directly to the output bitmap (this is called a
MIXED
block).  If the control byte is less than zero, then negate the control byte
and add 1
(n = -c + 1).  Then make n copies of the next byte in the output bit map
(this is called
a REPEAT block).  After the control byte has been processed and the output
bitmap
updated, the next control byte is immediately after the data associated with
the
current control byte.

Hope this helps any non-Mac programmers get Mac pictures up-and-running.

-- Ken Sykes

cameron@symcom.math.uiuc.EDU (12/10/87)

/* Written  8:03 pm  Dec  7, 1987 by wel@ncsuvx.ncsu.edu */
/* in symcom:comp.graphics */
/* ---------- "Re: MacPaint format" ---------- */

________________________________________________________________________________

Macintosh Technical Notes		 p

#86: MacPaint Document Format

See also:	MacPaint User Manual
		ToolBox Utilities

Written by:	Bill Atkinson	1983
Modified by:	Bryan Johnson	August 19, 1986
________________________________________________________________________________

[...Document deleted...]

/* End of text from symcom:comp.graphics */

I was the one who asked for that.  Thank you! Thank you! Thank you!
--------
Cameron Smith
Symbolic Computation Lab      "You knew the job was dangerous
Math Dept.                               when you took it, Fred!"
University of Illinois                                 -- SuperChicken
Urbana IL 61801
(217) 333-4654        cameron@symcom.math.uiuc.edu

ks26+@andrew.cmu.edu (Kenneth Sykes) (12/11/87)

One note on the description of Packbits/Unpackbits (8-Dec-87):

Mixed blocks need to add 1 to the control byte as well as Repeat blocks.

Old line:
>... If the control byte is greater than zero (n = c), then the next n bytes
...

Should read as

"If the control byte is greater than zero (n=c+1), the next n bytes..."