|
Packit |
78deda |
/* macptopbm.c - read a MacPaint file and produce a portable bitmap
|
|
Packit |
78deda |
**
|
|
Packit |
78deda |
** Copyright (C) 1988 by Jef Poskanzer.
|
|
Packit |
78deda |
** Some code of ReadMacPaintFile() is based on the work of
|
|
Packit |
78deda |
** Patrick J. Naughton. (C) 1987, All Rights Reserved.
|
|
Packit |
78deda |
**
|
|
Packit |
78deda |
** Permission to use, copy, modify, and distribute this software and its
|
|
Packit |
78deda |
** documentation for any purpose and without fee is hereby granted, provided
|
|
Packit |
78deda |
** that the above copyright notice appear in all copies and that both that
|
|
Packit |
78deda |
** copyright notice and this permission notice appear in supporting
|
|
Packit |
78deda |
** documentation. This software is provided "as is" without express or
|
|
Packit |
78deda |
** implied warranty.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
** Apr 2015 afu
|
|
Packit |
78deda |
** Changed code style (ANSI-style function definitions, etc.)
|
|
Packit |
78deda |
** Added automatic detection of MacBinary header.
|
|
Packit |
78deda |
** Added diagnostics for corruptions.
|
|
Packit |
78deda |
** Replaced byte-wise operations with bit-wise ones.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#include "pbm.h"
|
|
Packit |
78deda |
#include "pm_c_util.h"
|
|
Packit |
78deda |
#include "macp.h"
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static bool
|
|
Packit |
78deda |
validateMacPaintVersion( const unsigned char * const rBuff,
|
|
Packit |
78deda |
const int offset ) {
|
|
Packit |
78deda |
/*---------------------------------------------------------------------------
|
|
Packit |
78deda |
Macpaint (or PNTG) files have two headers.
|
|
Packit |
78deda |
The 512 byte MacPaint header is mandatory.
|
|
Packit |
78deda |
The newer 128 byte MacBinary header is optional. If it exists, it comes
|
|
Packit |
78deda |
before the MacPaint header.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Here we examine the first four bytes of the MacPaint header to get
|
|
Packit |
78deda |
the version number.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Valid version numbers are 0, 2, 3.
|
|
Packit |
78deda |
We also allow 1.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
|
|
Packit |
78deda |
bool retval;
|
|
Packit |
78deda |
const unsigned char * const vNum = rBuff + offset;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if ( ( ( vNum[0] | vNum[1] | vNum[2] ) != 0x00 ) || vNum[3] > 3 )
|
|
Packit |
78deda |
retval = FALSE;
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
retval = TRUE;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pm_message("MacPaint version (at offset %u): %02x %02x %02x %02x (%s)",
|
|
Packit |
78deda |
offset, vNum[0], vNum[1], vNum[2], vNum[3],
|
|
Packit |
78deda |
retval == TRUE ? "valid" : "not valid" );
|
|
Packit |
78deda |
|
|
Packit |
78deda |
return( retval );
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static bool
|
|
Packit |
78deda |
scanMacBinaryHeader( const unsigned char * rBuff ) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
We check byte 0 and 1, and then the MacPaint header version assuming it
|
|
Packit |
78deda |
starts at offset 128.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Byte 0: must be 0x00.
|
|
Packit |
78deda |
Byte 1: (filename length) must be 1-63.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Other fields that may be of interest:
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Bytes 2 through 63: (Internal Filename)
|
|
Packit |
78deda |
See Apple Charmap for valid characters.
|
|
Packit |
78deda |
Unlike US-Ascii, 8-bit characters (range 0x80 - 0xFF) are valid.
|
|
Packit |
78deda |
0x00-0x1F and 0x7F are control characters. 0x00 appears in some files.
|
|
Packit |
78deda |
Colon ':' (0x3a) should be avoided in Mac environments but in practice
|
|
Packit |
78deda |
does appear.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Bytes 65 through 68: (File Type)
|
|
Packit |
78deda |
Four Ascii characters. Should be "PNTG".
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Bytes 82 to 85: (SizeOfDataFork)
|
|
Packit |
78deda |
uint32 value. It seems this is file size (in bytes) / 256 + N, N <= 4.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Bytes 100 through 124:
|
|
Packit |
78deda |
Should be all zero if the header is MacBinary I.
|
|
Packit |
78deda |
Defined and used in MacBinary II.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Bytes 124,125: CRC
|
|
Packit |
78deda |
(MacBinary II only) CRC value of bytes 0 through 123.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
All multi-byte values are big-endian.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Reference:
|
|
Packit |
78deda |
http://www.fileformat.info/format/macpaint/egff.htm
|
|
Packit |
78deda |
Fully describes the fields. However, the detection method described
|
|
Packit |
78deda |
does not work very well.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Also see:
|
|
Packit |
78deda |
http://fileformats.archiveteam.org/wiki/MacPaint
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
bool foundMacBinaryHeader;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Examine byte 0. It should be 0x00. Note that the first
|
|
Packit |
78deda |
byte of a valid MacPaint header should also be 0x00.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
if ( rBuff[0] != 0x00 ) {
|
|
Packit |
78deda |
foundMacBinaryHeader = FALSE;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Examine byte 1, the length of the filename.
|
|
Packit |
78deda |
It should be in the range 1 - 63.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
else if( rBuff[1] == 0 || rBuff[1] > 63 ) {
|
|
Packit |
78deda |
foundMacBinaryHeader = FALSE;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Check the MacPaint header version starting at offset 128. */
|
|
Packit |
78deda |
else if ( validateMacPaintVersion ( rBuff, MACBIN_HEAD_LEN ) == FALSE) {
|
|
Packit |
78deda |
foundMacBinaryHeader = FALSE;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
foundMacBinaryHeader = TRUE;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if( foundMacBinaryHeader == TRUE)
|
|
Packit |
78deda |
pm_message("Input file contains a MacBinary header "
|
|
Packit |
78deda |
"followed by a MacPaint header.");
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
pm_message("Input file does not start with a MacBinary header.");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
return ( foundMacBinaryHeader );
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
skipHeader( FILE * const ifP ) {
|
|
Packit |
78deda |
/*--------------------------------------------------------------------------
|
|
Packit |
78deda |
Determine whether the MacBinary header exists.
|
|
Packit |
78deda |
If it does, read off the initial 640 (=128 + 512) bytes of the file.
|
|
Packit |
78deda |
If it doesn't, read off 512 bytes.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
In the latter case we check the MacHeader version number, but just issue
|
|
Packit |
78deda |
a warning if the value is invalid. This is for backward comaptibility.
|
|
Packit |
78deda |
---------------------------------------------------------------------------*/
|
|
Packit |
78deda |
unsigned int re;
|
|
Packit |
78deda |
const unsigned int buffsize = MAX( MACBIN_HEAD_LEN, MACP_HEAD_LEN );
|
|
Packit |
78deda |
unsigned char * const rBuff = malloc(buffsize);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if( rBuff == NULL )
|
|
Packit |
78deda |
pm_error("Out of memory.");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Read 512 bytes.
|
|
Packit |
78deda |
See if MacBinary header exists in the first 128 bytes and
|
|
Packit |
78deda |
the next 4 bytes signal the start of a MacPaint header. */
|
|
Packit |
78deda |
re = fread ( rBuff, MACP_HEAD_LEN, 1, ifP);
|
|
Packit |
78deda |
if (re < 1)
|
|
Packit |
78deda |
pm_error("EOF/error while reading header.");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if ( scanMacBinaryHeader( rBuff ) == TRUE ) {
|
|
Packit |
78deda |
/* MacBinary header found. Read another 128 bytes to complete the
|
|
Packit |
78deda |
MacPaint header, but don't conduct any further analysis. */
|
|
Packit |
78deda |
re = fread ( rBuff, MACBIN_HEAD_LEN, 1, ifP);
|
|
Packit |
78deda |
if (re < 1)
|
|
Packit |
78deda |
pm_error("EOF/error while reading MacPaint header.");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
/* MacBinary header not found. We assume file starts with
|
|
Packit |
78deda |
MacPaint header. Check MacPaint version but dismiss error. */
|
|
Packit |
78deda |
if (validateMacPaintVersion( rBuff, 0 ) == TRUE)
|
|
Packit |
78deda |
pm_message("Input file starts with valid MacPaint header.");
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
pm_message(" - Ignoring invalid version number.");
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
free( rBuff );
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
skipExtraBytes( FILE * const ifP,
|
|
Packit |
78deda |
int const extraskip) {
|
|
Packit |
78deda |
/*--------------------------------------------------------------------------
|
|
Packit |
78deda |
This function exists for backward compatibility. Its purpose is to
|
|
Packit |
78deda |
manually delete the MacBinary header.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
We check the MacHeader version number, but just issue a warning if the
|
|
Packit |
78deda |
value is invalid.
|
|
Packit |
78deda |
---------------------------------------------------------------------------*/
|
|
Packit |
78deda |
unsigned int re;
|
|
Packit |
78deda |
unsigned char * const rBuff = malloc(MAX (extraskip, MACP_HEAD_LEN));
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if( rBuff == NULL )
|
|
Packit |
78deda |
pm_error("Out of memory.");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
re = fread ( rBuff, 1, extraskip, ifP);
|
|
Packit |
78deda |
if (re < extraskip)
|
|
Packit |
78deda |
pm_error("EOF/error while reading off initial %u bytes"
|
|
Packit |
78deda |
"specified by -extraskip.", extraskip);
|
|
Packit |
78deda |
re = fread ( rBuff, MACP_HEAD_LEN, 1, ifP);
|
|
Packit |
78deda |
if (re < 1)
|
|
Packit |
78deda |
pm_error("EOF/error while reading MacPaint header.");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Check the MacPaint version number. Dismiss error. */
|
|
Packit |
78deda |
if (validateMacPaintVersion( rBuff, 0 ) == TRUE)
|
|
Packit |
78deda |
pm_message("Input file starts with valid MacPaint header.");
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
pm_message(" - Ignoring invalid version number.");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
free( rBuff );
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static unsigned char
|
|
Packit |
78deda |
readChar( FILE * const ifP ) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
int const ch = getc( ifP );
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (ch ==EOF)
|
|
Packit |
78deda |
pm_error("EOF encountered while unpacking image data.");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* else */
|
|
Packit |
78deda |
return ((unsigned char) ch);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
ReadMacPaintFile( FILE * const ifP,
|
|
Packit |
78deda |
int * outOfSyncP,
|
|
Packit |
78deda |
int * pixelCntP ) {
|
|
Packit |
78deda |
/*---------------------------------------------------------------------------
|
|
Packit |
78deda |
Unpack image data. Compression method is called "Packbits".
|
|
Packit |
78deda |
This run-length encoding scheme has also been adopted by
|
|
Packit |
78deda |
Postscript and TIFF. See source: converter/other/pnmtops.c
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Unpacked raster array is raw PBM. No conversion is required.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
One source says flag byte should not be 0xFF (255), but we don't reject
|
|
Packit |
78deda |
the value, for in practice, it is widely used.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Sequences should never cross row borders.
|
|
Packit |
78deda |
Violations of this rule are recorded in outOfSync.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Note that pixelCnt counts bytes, not bits, so it is the number of pixels
|
|
Packit |
78deda |
multiplied by 8. This counter exists to detect corruptions.
|
|
Packit |
78deda |
---------------------------------------------------------------------------*/
|
|
Packit |
78deda |
int pixelCnt = 0; /* Initial value */
|
|
Packit |
78deda |
int outOfSync = 0; /* Initial value */
|
|
Packit |
78deda |
unsigned int flag; /* Read from input */
|
|
Packit |
78deda |
unsigned int i;
|
|
Packit |
78deda |
unsigned char * const bitrow = pbm_allocrow_packed(MACP_COLS);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
while ( pixelCnt < MACP_BYTES ) {
|
|
Packit |
78deda |
flag = (unsigned int) readChar( ifP ); /* Flag (count) byte */
|
|
Packit |
78deda |
if ( flag < 0x80 ) {
|
|
Packit |
78deda |
/* Unpack next (flag + 1) chars as is */
|
|
Packit |
78deda |
for ( i = 0; i <= flag; i++ )
|
|
Packit |
78deda |
if( pixelCnt < MACP_BYTES) {
|
|
Packit |
78deda |
int const colChar = pixelCnt % MACP_COLCHARS;
|
|
Packit |
78deda |
pixelCnt++;
|
|
Packit |
78deda |
bitrow[colChar] = readChar( ifP );
|
|
Packit |
78deda |
if (colChar == MACP_COLCHARS-1)
|
|
Packit |
78deda |
pbm_writepbmrow_packed( stdout, bitrow, MACP_COLS, 0 );
|
|
Packit |
78deda |
if (colChar == 0 && i > 0 )
|
|
Packit |
78deda |
outOfSync++;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
else {
|
|
Packit |
78deda |
/* Repeat next char (2's complement of flagCnt) times */
|
|
Packit |
78deda |
unsigned int const flagCnt = 256 - flag;
|
|
Packit |
78deda |
unsigned char const ch = readChar( ifP );
|
|
Packit |
78deda |
for ( i = 0; i <= flagCnt; i++ )
|
|
Packit |
78deda |
if( pixelCnt < MACP_BYTES) {
|
|
Packit |
78deda |
int const colChar = pixelCnt % MACP_COLCHARS;
|
|
Packit |
78deda |
pixelCnt++;
|
|
Packit |
78deda |
bitrow[colChar] = ch;
|
|
Packit |
78deda |
if (colChar == MACP_COLCHARS-1)
|
|
Packit |
78deda |
pbm_writepbmrow_packed( stdout, bitrow, MACP_COLS, 0 );
|
|
Packit |
78deda |
if (colChar == 0 && i > 0 )
|
|
Packit |
78deda |
outOfSync++;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
pbm_freerow_packed ( bitrow );
|
|
Packit |
78deda |
*outOfSyncP = outOfSync;
|
|
Packit |
78deda |
*pixelCntP = pixelCnt;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
int
|
|
Packit |
78deda |
main( int argc, char * argv[]) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
FILE * ifp;
|
|
Packit |
78deda |
int argn, extraskip;
|
|
Packit |
78deda |
const char * const usage = "[-extraskip N] [macpfile]";
|
|
Packit |
78deda |
int outOfSync;
|
|
Packit |
78deda |
int pixelCnt;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pbm_init( &argc, argv );
|
|
Packit |
78deda |
|
|
Packit |
78deda |
argn = 1; /* initial value */
|
|
Packit |
78deda |
extraskip = 0; /* initial value */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Check for flags. */
|
|
Packit |
78deda |
if ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
|
|
Packit |
78deda |
if ( pm_keymatch( argv[argn], "-extraskip", 2 ) ) {
|
|
Packit |
78deda |
argn++;
|
|
Packit |
78deda |
if ( argn == argc || sscanf( argv[argn], "%d", &extraskip ) != 1 )
|
|
Packit |
78deda |
pm_usage( usage );
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
pm_usage( usage );
|
|
Packit |
78deda |
argn++;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if ( argn < argc ) {
|
|
Packit |
78deda |
ifp = pm_openr( argv[argn] );
|
|
Packit |
78deda |
argn++;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
ifp = stdin;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if ( argn != argc )
|
|
Packit |
78deda |
pm_usage( usage );
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if ( extraskip > 256 * 1024 )
|
|
Packit |
78deda |
pm_error("-extraskip value too large");
|
|
Packit |
78deda |
else if ( extraskip > 0 )
|
|
Packit |
78deda |
skipExtraBytes( ifp, extraskip);
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
skipHeader( ifp );
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pbm_writepbminit( stdout, MACP_COLS, MACP_ROWS, 0 );
|
|
Packit |
78deda |
|
|
Packit |
78deda |
ReadMacPaintFile( ifp, &outOfSync, &pixelCnt );
|
|
Packit |
78deda |
/* We may not be at EOF.
|
|
Packit |
78deda |
Macpaint files often have extra bytes after image data. */
|
|
Packit |
78deda |
pm_close( ifp );
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if ( pixelCnt == 0 )
|
|
Packit |
78deda |
pm_error("No image data.");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
else if ( pixelCnt < MACP_BYTES )
|
|
Packit |
78deda |
pm_error("Compressed image data terminated prematurely.");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
else if ( outOfSync > 0 )
|
|
Packit |
78deda |
pm_message("Warning: Corrupt image data. %d rows misaligned.",
|
|
Packit |
78deda |
outOfSync);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pm_close( stdout );
|
|
Packit |
78deda |
exit( 0 );
|
|
Packit |
78deda |
}
|