From 620ecbee2ed1cb478e0289722d86dd72717f1cb8 Mon Sep 17 00:00:00 2001 From: giraffedata Date: Sun, 28 Jun 2015 15:34:21 +0000 Subject: Release 10.71.00 git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@2588 9d0c8265-081b-0410-96cb-a4ca84ce46f8 --- converter/pbm/pbmtomacp.c | 707 +++++++++++++++++++++++++++++----------------- 1 file changed, 455 insertions(+), 252 deletions(-) (limited to 'converter/pbm/pbmtomacp.c') diff --git a/converter/pbm/pbmtomacp.c b/converter/pbm/pbmtomacp.c index 82ccce06..7ddb1ef5 100644 --- a/converter/pbm/pbmtomacp.c +++ b/converter/pbm/pbmtomacp.c @@ -1,294 +1,497 @@ -/* 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 * 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); +} + + + +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; +}; + + - if ( argn != argc ) - pm_usage( usage ); +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); - bitsr = pbm_readpbm( ifp, &cols, &rows ); + right = MIN3(cmdline.right, cols - 1, left + MACP_COLS - 1); + } else + right = MIN(cols - 1, left + MACP_COLS - 1); - pm_close( ifp ); + if (cmdline.bottomSpec) { + if (cmdline.bottom + 1 > rows) + pm_message("Specified -bottom value %u is beyond edge of " + "input image", cmdline.bottom); - bits = pbm_allocarray( MAX_COLS, MAX_LINES ); + bottom = MIN3(cmdline.bottom, rows - 1, top + MACP_ROWS - 1); + } else + bottom = MIN(rows - 1, top + MACP_ROWS - 1); - if( !lflg ) - left = 0; + cropPadP->leftCrop = left; + cropPadP->topCrop = top; - if( rflg ) - right = MIN3( right, cols - 1, left + MAX_COLS - 1 ); - else - right = MIN( cols - 1, left + MAX_COLS - 1 ); + assert(right >= left); - if( !tflg ) - top = 0; + width = right - left + 1; + assert(width > 0 && width <= MACP_COLS); - if( bflg ) - bottom = MIN3( bottom, rows - 1, top + MAX_LINES - 1); - else - bottom = MIN( rows - 1, top + MAX_LINES - 1 ); + cropPadP->leftMargin = (MACP_COLS - width) / 2; - 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 ); + if (width < cols) + pm_message("%u of %u input columns will be output", width, cols); - fillbits( bits, bitsr, top, left, bottom, right ); + height = bottom - top + 1; + assert(height > 0 && height <= MACP_ROWS); - writemacp( bits ); + cropPadP->topMargin = (MACP_ROWS - height) / 2; + cropPadP->bottomMargin = cropPadP->topMargin + height - 1; - exit( 0 ); + 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; +packit(const bit * const sourceBits, + unsigned int const imageColCharCt, + 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. +----------------------------------------------------------------------------*/ + int charcount, packcount; + enum {EQUAL, UNEQUAL} status; + bit * count; + + for (packcount = 0, charcount = 0, status = EQUAL; + charcount < imageColCharCt; + ) { + bit const save = sourceBits[charcount++]; + + int newcount; + + newcount = 1; + while (charcount < imageColCharCt && 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; + } + } + *packedImageLengthP = packcount; +} + + + +static void +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 * const packedBits = malloc(MACP_COLCHARS+1); + + unsigned int packedImageLength; + + if (packedBits == NULL) + pm_error("Failed to get memory for a %u-column row buffer", + MACP_COLCHARS); + + packit(imageBits, imageColCharCt, packedBits, &packedImageLength); + + /* Check if we are we better off with compression. If not, send row + unpacked. See note at top of file. + */ + + if (packedImageLength + + (leftMarginCharCt > 0 ? 1 : 0) * 2 + + (rightMarginCharCt > 0 ? 1 : 0) * 2 + < MACP_COLCHARS) + writeMacpRowPacked(packedBits, leftMarginCharCt, + packedImageLength, rightMarginCharCt, ofP); + else /* Extremely rare */ + writeMacpRowUnpacked(imageBits, leftMarginCharCt, imageColCharCt, + ofP); + + free(packedBits); + } +} - 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; +} + -- cgit 1.4.1