From 20eb10649881bf266600612b436ee706a48decf7 Mon Sep 17 00:00:00 2001 From: giraffedata Date: Sun, 26 Apr 2015 03:06:48 +0000 Subject: 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 --- converter/pbm/macp.h | 10 +- converter/pbm/macptopbm.c | 426 ++++++++++++++++++++++------- converter/pbm/pbmtomacp.c | 677 +++++++++++++++++++++++++++++----------------- 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 +#include #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 ); +} + -- cgit 1.4.1