about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2015-04-26 03:06:48 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2015-04-26 03:06:48 +0000
commit20eb10649881bf266600612b436ee706a48decf7 (patch)
treeb0445b62100fdb8d68beac78df1b350cef42be22
parentbaaf2d79c0e44c6ab399062fe417105acd932472 (diff)
downloadnetpbm-mirror-20eb10649881bf266600612b436ee706a48decf7.tar.gz
netpbm-mirror-20eb10649881bf266600612b436ee706a48decf7.tar.xz
netpbm-mirror-20eb10649881bf266600612b436ee706a48decf7.zip
Use packed PBM facilities and shhopt command line processing. Add -norle
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@2467 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--converter/pbm/macp.h10
-rw-r--r--converter/pbm/macptopbm.c426
-rw-r--r--converter/pbm/pbmtomacp.c677
3 files changed, 750 insertions, 363 deletions
diff --git a/converter/pbm/macp.h b/converter/pbm/macp.h
index 26a720a2..bc1231cd 100644
--- a/converter/pbm/macp.h
+++ b/converter/pbm/macp.h
@@ -4,9 +4,11 @@
 #ifndef MACP_H_INCLUDED
 #define MACP_H_INCLUDED
 
-#define	HEADER_LENGTH	512
-#define	MAX_LINES	720
-#define	BYTES_WIDE	72
-#define MAX_COLS	576	/* = BYTES_WIDE * 8 */
+#define	MACBIN_HEAD_LEN	128
+#define	MACP_HEAD_LEN	512
+#define	MACP_ROWS	720
+#define	MACP_COLCHARS	72
+#define MACP_COLS	((MACP_COLCHARS) * 8)
+#define MACP_BYTES	((MACP_COLCHARS) * (MACP_ROWS))
 
 #endif
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;
-		    }
-	    }
-	}
-    }
+}
diff --git a/converter/pbm/pbmtomacp.c b/converter/pbm/pbmtomacp.c
index 4dd819db..ebaf3acb 100644
--- a/converter/pbm/pbmtomacp.c
+++ b/converter/pbm/pbmtomacp.c
@@ -1,297 +1,466 @@
-/* pbmtomacp.c - read a portable bitmap and produce a MacPaint bitmap file
-**
-** Copyright (C) 1988 by Douwe vand der Schaaf.
-**
-** 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.  This software is provided "as is" without express or
-** implied warranty.
+/*=============================================================================
+                                  pbmtomacp
+===============================================================================
+  Read a PBM file and produce a MacPaint bitmap file
+
+  Copyright (C) 2015 by Akira Urushibata ("douso").
+
+  Replacement of a previous program of the same name written in 1988
+  by Douwe van der Schaaf (...!mcvax!uvapsy!vdschaaf).
+
+  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.  This software is provided "as is" without express or implied
+  warranty.
+=============================================================================*/
+
+/*
+
+  Implemention notes
+
+  Header size is 512 bytes.  There is no MacBinary header.
+
+  White margin which is added for input files with small dimensions
+  is treated separately from the active image raster.  The margins
+  are directly coded based on the number of rows/columns.
+
+  Output file size never exceeds 53072 bytes.  When -norle is specified,
+  output is always 53072 bytes.  It is conceivable that decoders which
+  examine the size of Macpaint files (for general validation or for
+  determination of header type and size) do exist.
+
+  The uncompressed output (-norle case) fully conforms to Macpaint
+  specifications.  No special treatment by the decoder is required.
 */
 
-#include <string.h>
+#include <assert.h>
 
 #include "pm_c_util.h"
 #include "pbm.h"
+#include "shhopt.h"
+#include "mallocvar.h"
 #include "macp.h"
 
-#define EQUAL		1
-#define UNEQUAL		0
+#define MIN3(a,b,c)     (MIN((MIN((a),(b))),(c)))
 
-static void fillbits ARGS(( bit **bits, bit **bitsr, int top, int left, int bottom, int right ));
-static void writemacp ARGS(( bit **bits ));
-static int packit ARGS(( bit *pb, bit *bits ));
-static void filltemp ARGS(( bit *dest, bit *src ));
-static void sendbytes ARGS(( bit *pb, register int npb ));
-static void header ARGS(( void ));
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  /* Filespec of input file */
+    unsigned int left;
+    unsigned int right;
+    unsigned int top;
+    unsigned int bottom;
+    unsigned int leftSpec;
+    unsigned int rightSpec;
+    unsigned int topSpec;
+    unsigned int bottomSpec;
+    bool         norle;         /* If true do not pack data */
+};
 
-static FILE *fdout;
 
-int
-main(argc, argv)
-int argc;
-char *argv[];
-{ FILE *ifp;
-  register bit **bits, **bitsr;
-  int argn, rows, cols;
-  int left,bottom,right,top;
-  int lflg, rflg, tflg, bflg;
-  const char * const usage = "[-l left] [-r right] [-b bottom] [-t top] [pbmfile]";
-
-
-  pbm_init( &argc, argv );
-
-  argn = 1;
-  fdout = stdout;
-  lflg = rflg = tflg = bflg = 0;
-  left = right = top = bottom = 0;  /* To quiet compiler warning */
-
-  while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
-  { switch ( argv[argn][1] )
-    { case 'l':
-      lflg++;
-      argn++;
-      left = atoi( argv[argn] );
-      break;
-
-      case 'r':
-      rflg++;
-      argn++;
-      right = atoi( argv[argn] );
-      break;
-
-      case 't':
-      tflg++;
-      argn++;
-      top = atoi( argv[argn] );
-      break;
-
-      case 'b':
-      bflg++;
-      argn++;
-      bottom = atoi( argv[argn] );
-      break;
-
-      case '?':
-      default:
-      pm_usage( usage );
+
+static void
+parseCommandLine(int                        argc,
+                 char              ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;  /* malloc'ed */
+        /* Instructions to OptParseOptions3 on how to parse our options.  */
+    optStruct3 opt;
+
+    unsigned int norleSpec;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "left",     OPT_UINT,  &cmdlineP->left,
+            &cmdlineP->leftSpec,     0);
+    OPTENT3(0, "right",    OPT_UINT,  &cmdlineP->right,
+            &cmdlineP->rightSpec,    0);
+    OPTENT3(0, "top",      OPT_UINT,  &cmdlineP->top,
+            &cmdlineP->topSpec,      0);
+    OPTENT3(0, "bottom",   OPT_UINT,  &cmdlineP->bottom,
+            &cmdlineP->bottomSpec,   0);
+    OPTENT3(0, "norle", OPT_FLAG,  NULL,
+            &norleSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    free(option_def);
+
+    cmdlineP->norle = norleSpec;
+
+    if (argc-1 == 0)
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+struct CropPadDimensions {
+    unsigned int imageWidth;   /* Active image content */
+    unsigned int imageHeight;
+    unsigned int leftCrop;     /* Cols cropped off from input */
+    unsigned int topCrop;      /* Rows cropped off from input */
+    unsigned int topMargin;    /* White padding for output */
+    unsigned int bottomMargin;
+    unsigned int leftMargin;
+};
+
+
+
+static void
+calculateCropPad(struct cmdlineInfo       const cmdline,
+                 struct CropPadDimensions     * cropPad,
+                 int                      const cols,
+                 int                      const rows ) {
+/*--------------------------------------------------------------------------
+Validate -left -right -top -bottom from command line.  Determine
+what rows, columns to take from input if any of these are specified.
+
+Center image if it is smaller than the fixed Macpaint format size.
+----------------------------------------------------------------------------*/
+    int right, bottom, width, height;
+    int const left = cmdline.leftSpec ? cmdline.left : 0;
+    int const top  = cmdline.topSpec  ? cmdline.top  : 0;
+
+    if( cmdline.leftSpec ) {
+        if(cmdline.rightSpec && left >= cmdline.right )
+            pm_error("-left value must be smaller than -right value");
+        else if( left > cols -1 )
+            pm_error("Specified -left value is beyond right edge "
+                     "of input image");
+    }
+    if( cmdline.topSpec ) {
+        if(cmdline.bottomSpec && top >= cmdline.bottom )
+            pm_error("-top value must be smaller than -bottom value");
+        else if( top > rows -1 )
+            pm_error("Specified -top value is beyond bottom edge "
+                     "of input image");
     }
-    ++argn;
-  }
 
-  if ( argn == argc )
-  { ifp = stdin;
-  }
-  else
-  { ifp = pm_openr( argv[argn] );
-    ++argn;
-  }
+    if( cmdline.rightSpec ) {
+        if( cmdline.right > cols -1 )
+            pm_message("Specified -right value %d is beyond edge of "
+                       "input image", cmdline.right);
 
-  if ( argn != argc )
-    pm_usage( usage );
+            right = MIN3( cmdline.right, cols - 1, left + MACP_COLS - 1 );
+    }
+    else
+        right = MIN( cols - 1,  left + MACP_COLS - 1 );
 
-  bitsr = pbm_readpbm( ifp, &cols, &rows );
+    if( cmdline.bottomSpec ) {
+        if( cmdline.bottom > rows -1 )
+          pm_message("Specified -bottom value %d is beyond edge of "
+                     "input image", cmdline.bottom);
 
-  pm_close( ifp );
+            bottom = MIN3( cmdline.bottom, rows - 1, top + MACP_ROWS - 1);
+    }
+    else
+        bottom = MIN( rows - 1, top + MACP_ROWS - 1 );
 
-  bits = pbm_allocarray( MAX_COLS, MAX_LINES );
+    cropPad->leftCrop = left;
+    cropPad->topCrop  = top;
 
-  if( !lflg )
-    left = 0;
+    width = right - left + 1;
+    cropPad->leftMargin  = ( MACP_COLS - width ) / 2;
 
-  if( rflg )
-  { if( right - left >= MAX_COLS )
-      right = left + MAX_COLS - 1;
-  }
-  else
-    right = ( left + MAX_COLS > cols ) ? ( cols - 1 ) : ( left + MAX_COLS - 1 );
+    assert(width > 0 && width <= MACP_COLS);
+    if(width < cols)
+        pm_message("%d of %d input columns will be output", width, cols);
 
-  if( !tflg )
-    top = 0;
+    height = bottom - top + 1;
+    cropPad->topMargin    = ( MACP_ROWS - height ) / 2;
+    cropPad->bottomMargin = cropPad->topMargin + height - 1;
 
-  if( bflg )
-  { if( bottom - top >= MAX_LINES )
-      bottom = top + MAX_LINES - 1;
-  }
-  else
-    bottom = ( top + MAX_LINES > rows ) ?
-		   ( rows - 1 ) : ( top + MAX_LINES - 1 );
-  
-    if( right <= left || left < 0 || right - left + 1 > MAX_COLS )
-      pm_error("error in right (= %d) and/or left (=%d)",right,left );
-    if( bottom <= top || top < 0 || bottom - top + 1 > MAX_LINES )
-      pm_error("error in bottom (= %d) and/or top (=%d)",bottom,top );
+    assert(height > 0 && height <= MACP_ROWS);
+    if(height < rows)
+        pm_message("%d out of %d input rows will be output", height, rows);
 
-  fillbits( bits, bitsr, top, left, bottom, right );
+    cropPad->imageWidth  = width;
+    cropPad->imageHeight = height;
 
-  writemacp( bits );
+}
 
-  exit( 0 );
 
+
+static void
+writeMacpHeader( ) {
+
+    int i;
+    char const ch = 0x00;    /* header contains nothing */
+
+    for(i = 0; i < MACP_HEAD_LEN; i++ )
+        fputc( ch, stdout );
 }
 
-/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
-/* centreer het over te zenden plaatje in het MacPaint document
- *
- * Het plaatje wordt vanaf al of niet opgegeven (left, bottom)
- * in een pbm bitmap van de juist macpaint afmetingen gezet,
- * en eventueel afgekapt.
- */
+
 static void
-fillbits( bits, bitsr, top, left, bottom, right )
-bit **bits, **bitsr;
-int top, left, bottom, right;
-{ register bit *bi, *bir;
-  register int i, j;
-  register int bottomr, leftr, topr, rightr;
-  int width, height;
-
-  width = right - left + 1;
-  leftr = (MAX_COLS - width) / 2;
-  rightr = leftr + width - 1;
-
-  height = bottom - top + 1;
-  topr = ( MAX_LINES - height ) / 2;
-  bottomr = topr + height - 1;
-
-  for( i = 0; i < topr; i++ )
-  { bi = bits[i];
-    for( j = 0; j < MAX_COLS; j++ )
-      *bi++ = 0;
-  }
+writeMacpRowUnpacked( unsigned int const leftMarginChars,
+                      const bit  * const imageBits,
+                      unsigned int const imageColChars) {
+/*--------------------------------------------------------------------------
+Encode (without compression) and output one row.
+The row comes divided into three parts: left margin, image, right margin.
+----------------------------------------------------------------------------*/
+    int i;
+    char const marginByte = 0x00;  /* White bits for margin */
+    unsigned int const rightMarginChars =
+                       MACP_COLCHARS - leftMarginChars - imageColChars;
+
+    fputc( MACP_COLCHARS - 1, stdout );
+
+    for(i = 0; i < leftMarginChars; ++i)
+        fputc( marginByte, stdout );
+
+    if(imageColChars > 0)
+        fwrite(imageBits, 1, imageColChars, stdout);
+
+    for(i = 0; i < rightMarginChars; ++i)
+        fputc( marginByte, stdout );
+}
+
 
-  for( i = topr; i <= bottomr; i++ )
-  { bi = bits[i];
-    { for( j = 0; j < leftr; j++ )
-	*bi++ = 0;
-      bir = bitsr[ i - topr + top ];
-      for( j = leftr; j <= rightr; j++ )
-	*bi++ = bir[j - leftr + left];
-      for( j = rightr + 1; j < MAX_COLS; j++ )
-	*bi++ = 0;
-  } }
-
-  for( i = bottomr + 1; i < MAX_LINES; i++ )
-  { bi = bits[i];
-    for( j = 0; j < MAX_COLS; j++ )
-      *bi++ = 0;
-  }
-} /* fillbits */
-      
-/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
 static void
-writemacp( bits )
-bit **bits;
-{ register int i;
-  bit pb[MAX_COLS * 2];
-  int npb;
-
-  header();
-  for( i=0; i < MAX_LINES; i++ )
-  { npb = packit( pb, bits[i] );
-    sendbytes( pb, npb );
-  }
-} /* writemacp */
-
-/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
-/* pack regel van MacPaint doc in Apple's format
- * return value = # of bytes in pb 
- */
-static int
-packit( pb, bits )
-     bit *pb, *bits;
-{ register int charcount, npb, newcount, flg;
-  bit temp[72];
-  bit *count, *srcb, *destb, save;
-
-  srcb = bits; destb = temp;
-  filltemp( destb, srcb );
-  srcb = temp;
-  destb = pb;
-  npb = 0;
-  charcount = BYTES_WIDE;
-  flg = EQUAL;
-  while( charcount ) { 
-      save = *srcb++;
-      charcount--;
-      newcount = 1;
-      while( (*srcb == save) && charcount ) { 
-          srcb++;
-          newcount++;
-          charcount--;
-      }
-      if( newcount > 2 ) { 
-          count = destb++;
-          *count = 257 - newcount;
-          *destb++ = save;
-          npb += 2;
-          flg = EQUAL;
-      } else { 
-          if( flg == EQUAL ) { 
-              count = destb++;
-              *count = newcount - 1;
-              npb++;
-          } else
-            *count += newcount;
-          while( newcount-- ) { 
-              *destb++ = save;
-              npb++;
-          }
-          flg = UNEQUAL;
-      } 
-  }
-  return npb;
-} /* packit */
+writeMacpRowPacked( unsigned int const leftMarginChars,
+                    const bit  * const packedBits,
+                    unsigned int const imageColChars,
+                    unsigned int const rightMarginChars) {
+/*--------------------------------------------------------------------------
+Encode and output one row.
+As in the unpacked case, the row comes divided into three parts:
+left margin, image, right margin.  Unlike the unpacked case we need to
+know both the size of the packed data and the size of the right margin.
+----------------------------------------------------------------------------*/
+    char const marginByte = 0x00;  /* White bits for margin */
+
+    if( leftMarginChars > 0 ) {
+        fputc( 257 - leftMarginChars, stdout );
+        fputc( marginByte, stdout );
+    }
+
+    if( imageColChars > 0)
+        fwrite( packedBits, 1, imageColChars, stdout);
+
+    if( rightMarginChars > 0 ) {
+        fputc( 257 - rightMarginChars, stdout );
+        fputc( marginByte, stdout );
+    }
+}
+
 
-/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
 static void
-filltemp( dest, src )
-bit *dest, *src;
-{ register unsigned char ch, zero, acht;
-  register int i, j;
-
-  zero = '\0';
-  acht = 8;
-  i = BYTES_WIDE;
-  while( i-- )
-  { ch = zero; 
-    j = acht;
-    while( j-- )
-    { ch <<= 1;
-      if( *src++ )
-	ch++;
+packit (const bit *     const sourceBits,
+        unsigned int    const imageColChars,
+        unsigned char * const packedBits,
+        unsigned int  * const packedImageLengthP ) {
+/*--------------------------------------------------------------------------
+Compress according to packbits algorithm, a byte-level run-length
+encoding scheme.
+
+Each row is encoded separately.
+
+The following code does not produce optimum output when there are 2-byte
+long sequences between longer ones: the 2-byte run in between does not
+get packed, using up 3 bytes where 2 would do.
+----------------------------------------------------------------------------*/
+#define EQUAL           1
+#define UNEQUAL         0
+
+    int charcount, newcount, packcount;
+    bool status;
+    bit * count;
+    bit save;
+
+    packcount = charcount = 0;  /* Initial values */
+    status = EQUAL;
+    while( charcount < imageColChars ) {
+        save = sourceBits[charcount++];
+        newcount = 1;
+        while( charcount < imageColChars && sourceBits[charcount] == save ) {
+            charcount++;
+            newcount++;
+        }
+        if( newcount > 2 ) {
+             count = (unsigned char *) &packedBits[packcount++];
+             *count = 257 - newcount;
+             packedBits[packcount++] = save;
+             status = EQUAL;
+        } else {
+             if( status == EQUAL ) {
+                  count = (unsigned char *) &packedBits[packcount++];
+                  *count = newcount - 1;
+             } else
+                  *count += newcount;
+
+             for( ; newcount > 0; newcount-- ) {
+                 packedBits[packcount++] = save;
+             }
+             status = UNEQUAL;
+        }
     }
-    *dest++ = ch;
+    *packedImageLengthP = packcount;
+}
+
+
+
+static void
+writeMacpRow( unsigned int const leftMarginChars,
+              bit        * const imageBits,
+              unsigned int const imageColChars,
+              bool         const norle) {
+/*--------------------------------------------------------------------------
+Determine whether a row should be packed (compressed) or not.
+
+If packing leads to unnecessary bloat, discard the packed data and write
+in unpacked mode.
+----------------------------------------------------------------------------*/
+  if (norle)
+    writeMacpRowUnpacked( leftMarginChars, imageBits, imageColChars );
+
+  else {
+    unsigned int packedImageLength;
+    unsigned int const rightMarginChars =
+        MACP_COLCHARS - leftMarginChars - imageColChars;
+    unsigned char * const packedBits = malloc(MACP_COLCHARS+1);
+    if(packedBits == NULL)
+        pm_error("Out of memory");
+
+    packit( imageBits, imageColChars, packedBits, &packedImageLength );
+    /* Check if we are we better off with compression.
+       If not, send row unpacked.  See note at top of file.
+    */
+    if ( packedImageLength + (!!(leftMarginChars  > 0)) *2 +
+         (!!(rightMarginChars > 0)) *2 < MACP_COLCHARS )
+        writeMacpRowPacked( leftMarginChars, packedBits,
+                            packedImageLength, rightMarginChars);
+    else /* Extremely rare */
+        writeMacpRowUnpacked( leftMarginChars, imageBits, imageColChars );
+
+    free( packedBits );
   }
-} /* filltemp */
+}
+
 
-/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
 static void
-sendbytes( pb, npb )
-bit *pb;
-register int npb;
-{ register bit *b;
+encodeRowsWithShift(bit              * const bitrow,
+                    FILE             * const ifP,
+                    int                const inCols,
+                    unsigned int       const format,
+                    bool               const norle,
+                    struct CropPadDimensions const cropPad ) {
+/*--------------------------------------------------------------------------
+Shift input rows to put only specified columns to output.
+Add padding on left and right if necessary.
+
+No shift if the input image is the exact size (576 columns) of the Macpaint
+format.  If the input image is too wide and -left was not specified, extra
+content on the right is discarded.
+----------------------------------------------------------------------------*/
+    unsigned int row;
+
+    int const offset     = (cropPad.leftMargin + 8 - cropPad.leftCrop % 8) % 8;
+    int const leftTrim   = cropPad.leftMargin % 8;
+    int const rightTrim  = ( 8 - (leftTrim + cropPad.imageWidth) % 8 ) % 8;
+    int const startChar  = (cropPad.leftCrop + offset) / 8;
+    int const imageChars = pbm_packed_bytes(leftTrim + cropPad.imageWidth);
+    int const leftMarginChars = cropPad.leftMargin / 8;
+
+    for(row = 0; row < cropPad.imageHeight; ++row ) {
+        pbm_readpbmrow_bitoffset(ifP, bitrow, inCols, format, offset);
+
+        /* Trim off fractional margin portion in first byte of image data */
+        if(leftTrim > 0) {
+            bitrow[startChar] <<= leftTrim;
+            bitrow[startChar] >>= leftTrim;
+        }
+        /* Do the same with bits in last byte of relevant image data */
+        if(rightTrim > 0) {
+            bitrow[startChar + imageChars -1] >>= rightTrim;
+            bitrow[startChar + imageChars -1] <<= rightTrim;
+            }
+
+        writeMacpRow( leftMarginChars,
+                      &bitrow[startChar], imageChars, norle);
+    }
+}
 
-  b = pb;
-  while( npb-- )
-    (void) putc( *b++, fdout );
-} /* sendbytes */
 
-/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
 static void
-header()
-{ register int i;
-  register char ch;
-
-  /* header contains nothing ... */
-  ch = '\0';
-  for(i = 0; i < HEADER_LENGTH; i++ )
-    (void) putc( ch, fdout );
-} /* header */
+writeMacp( int                      const cols,
+           int                      const rows,
+           unsigned int             const format,
+           FILE *                   const ifP,
+           bool                     const norle,
+           struct CropPadDimensions const cropPad ) {
+
+    unsigned int row, skipRow;
+    bit * bitrow;
+
+    writeMacpHeader( );
+
+    for( row = 0; row < cropPad.topMargin; ++row )
+        writeMacpRow( MACP_COLCHARS, NULL, 0, norle);
+
+    /* Allocate PBM row with one extra byte for the shift case. */
+    bitrow = pbm_allocrow_packed(cols + 8);
+
+    for(skipRow = 0; skipRow < cropPad.topCrop; ++skipRow )
+         pbm_readpbmrow_packed(ifP, bitrow, cols, format);
+
+    encodeRowsWithShift(bitrow, ifP, cols, format, norle, cropPad);
+
+    pbm_freerow_packed(bitrow);
+
+    for(row = cropPad.bottomMargin + 1 ; row < MACP_ROWS ; ++row )
+        writeMacpRow( MACP_COLCHARS, NULL, 0, norle);
+}
+
+
+
+int
+main( int argc, char *argv[] ) {
+    FILE * ifP;
+    int rows, cols;
+    int format;
+    struct cmdlineInfo cmdline;
+    struct CropPadDimensions cropPad;
+
+    pbm_init( &argc, argv );
+
+    parseCommandLine(argc, argv, &cmdline);
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pbm_readpbminit(ifP, &cols, &rows, &format);
+
+    calculateCropPad(cmdline, &cropPad, cols, rows);
+
+    writeMacp( cols, rows, format, ifP, cmdline.norle, cropPad );
+
+    pm_close( ifP );
+    exit( 0 );
+}
+