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