about summary refs log tree commit diff
path: root/converter/pbm/pbmtomacp.c
blob: ebaf3acba06e7e3848c1c36818379c2270ed2dca (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
/*=============================================================================
                                  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 <assert.h>

#include "pm_c_util.h"
#include "pbm.h"
#include "shhopt.h"
#include "mallocvar.h"
#include "macp.h"

#define MIN3(a,b,c)     (MIN((MIN((a),(b))),(c)))

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 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");
    }

    if( cmdline.rightSpec ) {
        if( cmdline.right > cols -1 )
            pm_message("Specified -right value %d 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( cmdline.bottomSpec ) {
        if( cmdline.bottom > rows -1 )
          pm_message("Specified -bottom value %d is beyond edge of "
                     "input image", cmdline.bottom);

            bottom = MIN3( cmdline.bottom, rows - 1, top + MACP_ROWS - 1);
    }
    else
        bottom = MIN( rows - 1, top + MACP_ROWS - 1 );

    cropPad->leftCrop = left;
    cropPad->topCrop  = top;

    width = right - left + 1;
    cropPad->leftMargin  = ( MACP_COLS - width ) / 2;

    assert(width > 0 && width <= MACP_COLS);
    if(width < cols)
        pm_message("%d of %d input columns will be output", width, cols);

    height = bottom - top + 1;
    cropPad->topMargin    = ( MACP_ROWS - height ) / 2;
    cropPad->bottomMargin = cropPad->topMargin + height - 1;

    assert(height > 0 && height <= MACP_ROWS);
    if(height < rows)
        pm_message("%d out of %d input rows will be output", height, rows);

    cropPad->imageWidth  = width;
    cropPad->imageHeight = height;

}



static void
writeMacpHeader( ) {

    int i;
    char const ch = 0x00;    /* header contains nothing */

    for(i = 0; i < MACP_HEAD_LEN; i++ )
        fputc( ch, stdout );
}



static void
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 );
}



static void
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
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;
        }
    }
    *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 );
  }
}



static void
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);
    }
}



static void
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 );
}