about summary refs log tree commit diff
path: root/converter/pbm/pbmtomacp.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/pbm/pbmtomacp.c')
-rw-r--r--converter/pbm/pbmtomacp.c655
1 files changed, 401 insertions, 254 deletions
diff --git a/converter/pbm/pbmtomacp.c b/converter/pbm/pbmtomacp.c
index 600cc407..df5cbb0c 100644
--- a/converter/pbm/pbmtomacp.c
+++ b/converter/pbm/pbmtomacp.c
@@ -1,296 +1,443 @@
-/* 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 "runlength.h"
 #include "macp.h"
 
-#define TRUE		1
-#define FALSE		0
-#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 * inputFileName;  /* File name 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;
+};
 
-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,
+                 const 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, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    cmdlineP->norle = norleSpec;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+        if (argc-1 > 1)
+            pm_error("Program takes zero or one argument (filename).  You "
+                     "specified %d", argc-1);
     }
-    ++argn;
-  }
 
-  if ( argn == argc )
-  { ifp = stdin;
-  }
-  else
-  { ifp = pm_openr( argv[argn] );
-    ++argn;
-  }
+    free(option_def);
+}
 
-  if ( argn != argc )
-    pm_usage( usage );
 
-  bitsr = pbm_readpbm( ifp, &cols, &rows );
 
-  pm_close( ifp );
+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;
+};
 
-  bits = pbm_allocarray( MAX_COLS, MAX_LINES );
 
-  if( !lflg )
-    left = 0;
 
-  if( rflg )
-    right = MIN3( right, cols - 1, left + MAX_COLS - 1 );
-  else
-    right = MIN( cols - 1,  left + MAX_COLS - 1 );
+static void
+calculateCropPad(struct CmdlineInfo         const cmdline,
+                 unsigned int               const cols,
+                 unsigned int               const rows,
+                 struct CropPadDimensions * const cropPadP) {
+/*--------------------------------------------------------------------------
+  Validate -left -right -top -bottom from command line.
+
+  Determine what rows, columns to take from input if any of these are
+  specified and return it as *cropPadP.
+
+  'cols and 'rows' are the dimensions of the input image.
+
+  Center image if it is smaller than the fixed Macpaint format size.
+----------------------------------------------------------------------------*/
+    unsigned int const left = cmdline.leftSpec ? cmdline.left : 0;
+    unsigned int const top  = cmdline.topSpec  ? cmdline.top  : 0;
+
+    unsigned int right, bottom, width, height;
+
+    if (cmdline.leftSpec) {
+        if (cmdline.rightSpec && left >= cmdline.right)
+            pm_error("-left value must be smaller than -right value");
+        else if (left + 1 > cols)
+            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 + 1 > rows)
+            pm_error("Specified -top value is beyond bottom edge "
+                     "of input image");
+    }
+    if (cmdline.rightSpec) {
+        if (cmdline.right + 1 > cols)
+            pm_message("Specified -right value %u is beyond edge of "
+                       "input image", cmdline.right);
+
+        right = MIN3(cmdline.right, cols - 1, left + MACP_COLS - 1);
+    } else
+        right = MIN(cols - 1,  left + MACP_COLS - 1);
 
-  if( !tflg )
-    top = 0;
+    if (cmdline.bottomSpec) {
+        if (cmdline.bottom + 1 > rows)
+            pm_message("Specified -bottom value %u is beyond edge of "
+                       "input image", cmdline.bottom);
 
-  if( bflg )
-    bottom = MIN3( bottom, rows - 1, top + MAX_LINES - 1);
-  else
-    bottom = MIN( rows - 1, top + MAX_LINES - 1 );
+            bottom = MIN3(cmdline.bottom, rows - 1, top + MACP_ROWS - 1);
+    } else
+        bottom = MIN(rows - 1, top + MACP_ROWS - 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 );
+    cropPadP->leftCrop = left;
+    cropPadP->topCrop  = top;
 
-  fillbits( bits, bitsr, top, left, bottom, right );
+    assert(right >= left);
 
-  writemacp( bits );
+    width = right - left + 1;
+    assert(width > 0 && width <= MACP_COLS);
 
-  exit( 0 );
+    cropPadP->leftMargin = (MACP_COLS - width) / 2;
 
+    if (width < cols)
+        pm_message("%u of %u input columns will be output", width, cols);
+
+    height = bottom - top + 1;
+    assert(height > 0 && height <= MACP_ROWS);
+
+    cropPadP->topMargin    = (MACP_ROWS - height) / 2;
+    cropPadP->bottomMargin = cropPadP->topMargin + height - 1;
+
+    if (height < rows)
+        pm_message("%u out of %u input rows will be output", height, rows);
+
+    cropPadP->imageWidth  = width;
+    cropPadP->imageHeight = height;
 }
 
-/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
-/* 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;
-  }
-
-  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 */
-      
-/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+writeMacpHeader(FILE * const ofP) {
+
+    char const ch = 0x00;    /* header contains nothing */
+
+    unsigned int i;
+
+    for (i = 0; i < MACP_HEAD_LEN; ++i)
+        fputc(ch, ofP);
+}
+
+
 
 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( charcount && (*srcb == save) ) { 
-          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 */
-
-/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+writeMacpRowUnpacked(const bit  * const imageBits,
+                     unsigned int const leftMarginCharCt,
+                     unsigned int const imageColCharCt,
+                     FILE *       const ofP) {
+/*--------------------------------------------------------------------------
+  Encode (without compression) and output one row.  The row comes divided into
+  three parts: left margin, image, right margin.
+----------------------------------------------------------------------------*/
+    char const marginByte = 0x00;  /* White bits for margin */
+    unsigned int const rightMarginCharCt =
+        MACP_COLCHARS - leftMarginCharCt - imageColCharCt;
+    
+    unsigned int i;
+
+    fputc(MACP_COLCHARS - 1, ofP);
+
+    for (i = 0; i < leftMarginCharCt; ++i)
+        fputc(marginByte, ofP);
+
+    if (imageColCharCt > 0)
+        fwrite(imageBits, 1, imageColCharCt, ofP);
+
+    for (i = 0; i < rightMarginCharCt; ++i)
+        fputc(marginByte, ofP);
+}
+
+
 
 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++;
+writeMacpRowPacked(const bit  * const packedBits,
+                   unsigned int const leftMarginCharCt,
+                   unsigned int const imageColCharCt,
+                   unsigned int const rightMarginCharCt,
+                   FILE *       const ofP) {
+/*--------------------------------------------------------------------------
+  Encode one row and write it to *ofP.
+
+  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 (leftMarginCharCt > 0) {
+        fputc(257 - leftMarginCharCt, ofP);
+        fputc(marginByte, ofP);
     }
-    *dest++ = ch;
-  }
-} /* filltemp */
 
-/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+    if (imageColCharCt > 0)
+        fwrite(packedBits, 1, imageColCharCt, ofP);
+
+    if (rightMarginCharCt > 0) {
+        fputc(257 - rightMarginCharCt, ofP);
+        fputc(marginByte, ofP);
+    }
+}
+
+
 
 static void
-sendbytes( pb, npb )
-bit *pb;
-register int npb;
-{ register bit *b;
+writeMacpRow(bit        * const imageBits,
+             unsigned int const leftMarginCharCt,
+             unsigned int const imageColCharCt,
+             bool         const norle,
+             FILE *       const ofP) {
+/*--------------------------------------------------------------------------
+  Write the row 'imageBits' to Standard Output.
+
+  Write it packed, unless packing would lead to unnecessary bloat or 'norle'
+  is true.
+----------------------------------------------------------------------------*/
+    if (norle)
+        writeMacpRowUnpacked(imageBits, leftMarginCharCt, imageColCharCt, ofP);
+    else {
+        unsigned int const rightMarginCharCt =
+            MACP_COLCHARS - leftMarginCharCt - imageColCharCt;
+        unsigned char packedBits[MACP_COLCHARS+1];
+        size_t packedImageLength;
+
+        if (pm_rlenc_maxbytes(MACP_COLCHARS, PM_RLE_PACKBITS)
+            > MACP_COLCHARS + 1)
+            pm_error("INTERNAL ERROR: RLE buffer too small");
+
+        pm_rlenc_compressbyte(imageBits, packedBits, PM_RLE_PACKBITS,
+                              imageColCharCt,  &packedImageLength);
+
+        if (packedImageLength +
+            (leftMarginCharCt  > 0 ? 1 : 0) * 2 +
+            (rightMarginCharCt > 0 ? 1 : 0) * 2
+            < MACP_COLCHARS) {
+            /* It's smaller compressed, so do that */
+            writeMacpRowPacked(packedBits, leftMarginCharCt,
+                               packedImageLength, rightMarginCharCt, ofP);
+        } else { /* Extremely rare */
+            /* It's larger compressed, so do it uncompressed.  See note
+               at top of file.
+            */
+            writeMacpRowUnpacked(imageBits, leftMarginCharCt, imageColCharCt,
+                                 ofP);
+        }
+    }
+}
 
-  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 */
+encodeRowsWithShift(bit *                    const bitrow,
+                    FILE *                   const ifP,
+                    int                      const inCols,
+                    int                      const format,
+                    bool                     const norle,
+                    struct CropPadDimensions const cropPad,
+                    FILE *                   const ofP) {
+/*--------------------------------------------------------------------------
+  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 const offset     =
+        (cropPad.leftMargin + 8 - cropPad.leftCrop % 8) % 8;
+    unsigned int const leftTrim   =
+        cropPad.leftMargin % 8;
+    unsigned int const rightTrim  =
+        (8 - (leftTrim + cropPad.imageWidth) % 8 ) % 8;
+    unsigned int const startChar  =
+        (cropPad.leftCrop + offset) / 8;
+    unsigned int const imageCharCt =
+        pbm_packed_bytes(leftTrim + cropPad.imageWidth);
+    unsigned int const leftMarginCharCt =
+        cropPad.leftMargin / 8;
+
+    unsigned int row;
+
+    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 + imageCharCt - 1] >>= rightTrim;
+            bitrow[startChar + imageCharCt - 1] <<= rightTrim;
+        }
+
+        writeMacpRow(&bitrow[startChar], leftMarginCharCt,
+                     imageCharCt, norle, ofP);
+    }
+}
+
+
+
+static void
+writeMacp(unsigned int             const cols,
+          unsigned int             const rows,
+          int                      const format,
+          FILE *                   const ifP,
+          bool                     const norle,
+          struct CropPadDimensions const cropPad,
+          FILE *                   const ofP) {
+
+    unsigned int row, skipRow;
+    bit * bitrow;
+
+    writeMacpHeader(ofP);
+
+    /* Write top padding */
+    for (row = 0; row < cropPad.topMargin; ++row)
+        writeMacpRow(NULL, MACP_COLCHARS, 0, norle, ofP);
+
+    /* 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, ofP);
+
+    pbm_freerow_packed(bitrow);
+
+    /* Add bottom padding */
+    for (row = cropPad.bottomMargin + 1; row < MACP_ROWS; ++row)
+        writeMacpRow(NULL, MACP_COLCHARS, 0, norle, ofP);
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+
+    FILE * ifP;
+    int rows, cols;
+    int format;
+    struct CmdlineInfo cmdline;
+    struct CropPadDimensions cropPad;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pbm_readpbminit(ifP, &cols, &rows, &format);
+
+    calculateCropPad(cmdline, cols, rows, &cropPad);
+
+    writeMacp(cols, rows, format, ifP, cmdline.norle, cropPad, stdout);
+
+    pm_close(ifP);
+
+    return 0;
+}
+