Blame converter/pbm/macptopbm.c

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
}