1 /*=============================================================================
2                                   pbmtomacp
3 ===============================================================================
4   Read a PBM file and produce a MacPaint bitmap file
5 
6   Copyright (C) 2015 by Akira Urushibata ("douso").
7 
8   Replacement of a previous program of the same name written in 1988
9   by Douwe van der Schaaf (...!mcvax!uvapsy!vdschaaf).
10 
11   Permission to use, copy, modify, and distribute this software and its
12   documentation for any purpose and without fee is hereby granted, provided
13   that the above copyright notice appear in all copies and that both that
14   copyright notice and this permission notice appear in supporting
15   documentation.  This software is provided "as is" without express or implied
16   warranty.
17 =============================================================================*/
18 
19 /*
20 
21   Implementation notes
22 
23   Header size is 512 bytes.  There is no MacBinary header.
24 
25   White margin which is added for input files with small dimensions
26   is treated separately from the active image raster.  The margins
27   are directly coded based on the number of rows/columns.
28 
29   Output file size never exceeds 53072 bytes.  When -norle is specified,
30   output is always 53072 bytes.  It is conceivable that decoders which
31   examine the size of Macpaint files (for general validation or for
32   determination of header type and size) do exist.
33 
34   The uncompressed output (-norle case) fully conforms to Macpaint
35   specifications.  No special treatment by the decoder is required.
36 */
37 
38 #include <assert.h>
39 
40 #include "pm_c_util.h"
41 #include "pbm.h"
42 #include "shhopt.h"
43 #include "mallocvar.h"
44 #include "runlength.h"
45 #include "macp.h"
46 
47 #define MIN3(a,b,c)     (MIN((MIN((a),(b))),(c)))
48 
49 struct CmdlineInfo {
50     /* All the information the user supplied in the command line, in a form
51        easy for the program to use.
52     */
53     const char * inputFileName;  /* File name of input file */
54     unsigned int left;
55     unsigned int right;
56     unsigned int top;
57     unsigned int bottom;
58     unsigned int leftSpec;
59     unsigned int rightSpec;
60     unsigned int topSpec;
61     unsigned int bottomSpec;
62     bool         norle;
63 };
64 
65 
66 
67 static void
parseCommandLine(int argc,const char ** const argv,struct CmdlineInfo * const cmdlineP)68 parseCommandLine(int                        argc,
69                  const char **        const argv,
70                  struct CmdlineInfo * const cmdlineP) {
71 /*----------------------------------------------------------------------------
72    Parse program command line described in Unix standard form by argc
73    and argv.  Return the information in the options as *cmdlineP.
74 -----------------------------------------------------------------------------*/
75     optEntry * option_def;  /* malloc'ed */
76         /* Instructions to OptParseOptions3 on how to parse our options.  */
77     optStruct3 opt;
78 
79     unsigned int norleSpec;
80 
81     unsigned int option_def_index;
82 
83     MALLOCARRAY_NOFAIL(option_def, 100);
84 
85     option_def_index = 0;   /* incremented by OPTENTRY */
86     OPTENT3(0, "left",     OPT_UINT,  &cmdlineP->left,
87             &cmdlineP->leftSpec,     0);
88     OPTENT3(0, "right",    OPT_UINT,  &cmdlineP->right,
89             &cmdlineP->rightSpec,    0);
90     OPTENT3(0, "top",      OPT_UINT,  &cmdlineP->top,
91             &cmdlineP->topSpec,      0);
92     OPTENT3(0, "bottom",   OPT_UINT,  &cmdlineP->bottom,
93             &cmdlineP->bottomSpec,   0);
94     OPTENT3(0, "norle", OPT_FLAG,  NULL,
95             &norleSpec, 0);
96 
97     opt.opt_table = option_def;
98     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
99     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
100 
101     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
102         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
103 
104     cmdlineP->norle = norleSpec;
105 
106     if (argc-1 < 1)
107         cmdlineP->inputFileName = "-";
108     else {
109         cmdlineP->inputFileName = argv[1];
110         if (argc-1 > 1)
111             pm_error("Program takes zero or one argument (filename).  You "
112                      "specified %d", argc-1);
113     }
114 
115     free(option_def);
116 }
117 
118 
119 
120 struct CropPadDimensions {
121     unsigned int imageWidth;   /* Active image content */
122     unsigned int imageHeight;
123     unsigned int leftCrop;     /* Cols cropped off from input */
124     unsigned int topCrop;      /* Rows cropped off from input */
125     unsigned int topMargin;    /* White padding for output */
126     unsigned int bottomMargin;
127     unsigned int leftMargin;
128 };
129 
130 
131 
132 static void
calculateCropPad(struct CmdlineInfo const cmdline,unsigned int const cols,unsigned int const rows,struct CropPadDimensions * const cropPadP)133 calculateCropPad(struct CmdlineInfo         const cmdline,
134                  unsigned int               const cols,
135                  unsigned int               const rows,
136                  struct CropPadDimensions * const cropPadP) {
137 /*--------------------------------------------------------------------------
138   Validate -left -right -top -bottom from command line.
139 
140   Determine what rows, columns to take from input if any of these are
141   specified and return it as *cropPadP.
142 
143   'cols and 'rows' are the dimensions of the input image.
144 
145   Center image if it is smaller than the fixed Macpaint format size.
146 ----------------------------------------------------------------------------*/
147     unsigned int const left = cmdline.leftSpec ? cmdline.left : 0;
148     unsigned int const top  = cmdline.topSpec  ? cmdline.top  : 0;
149 
150     unsigned int right, bottom, width, height;
151 
152     if (cmdline.leftSpec) {
153         if (cmdline.rightSpec && left >= cmdline.right)
154             pm_error("-left value must be smaller than -right value");
155         else if (left + 1 > cols)
156             pm_error("Specified -left value is beyond right edge "
157                      "of input image");
158     }
159     if (cmdline.topSpec) {
160         if (cmdline.bottomSpec && top >= cmdline.bottom)
161             pm_error("-top value must be smaller than -bottom value");
162         else if (top + 1 > rows)
163             pm_error("Specified -top value is beyond bottom edge "
164                      "of input image");
165     }
166     if (cmdline.rightSpec) {
167         if (cmdline.right + 1 > cols)
168             pm_message("Specified -right value %u is beyond edge of "
169                        "input image", cmdline.right);
170 
171         right = MIN3(cmdline.right, cols - 1, left + MACP_COLS - 1);
172     } else
173         right = MIN(cols - 1,  left + MACP_COLS - 1);
174 
175     if (cmdline.bottomSpec) {
176         if (cmdline.bottom + 1 > rows)
177             pm_message("Specified -bottom value %u is beyond edge of "
178                        "input image", cmdline.bottom);
179 
180         bottom = MIN3(cmdline.bottom, rows - 1, top + MACP_ROWS - 1);
181     } else
182         bottom = MIN(rows - 1, top + MACP_ROWS - 1);
183 
184     cropPadP->leftCrop = left;
185     cropPadP->topCrop  = top;
186 
187     assert(right >= left);
188 
189     width = right - left + 1;
190     assert(width > 0 && width <= MACP_COLS);
191 
192     cropPadP->leftMargin = (MACP_COLS - width) / 2;
193 
194     if (width < cols)
195         pm_message("%u of %u input columns will be output", width, cols);
196 
197     height = bottom - top + 1;
198     assert(height > 0 && height <= MACP_ROWS);
199 
200     cropPadP->topMargin    = (MACP_ROWS - height) / 2;
201     cropPadP->bottomMargin = cropPadP->topMargin + height - 1;
202 
203     if (height < rows)
204         pm_message("%u out of %u input rows will be output", height, rows);
205 
206     cropPadP->imageWidth  = width;
207     cropPadP->imageHeight = height;
208 }
209 
210 
211 
212 static void
writeMacpHeader(FILE * const ofP)213 writeMacpHeader(FILE * const ofP) {
214 
215     char const ch = 0x00;    /* header contains nothing */
216 
217     unsigned int i;
218 
219     for (i = 0; i < MACP_HEAD_LEN; ++i)
220         fputc(ch, ofP);
221 }
222 
223 
224 
225 static void
writeMacpRowUnpacked(const bit * const imageBits,unsigned int const leftMarginCharCt,unsigned int const imageColCharCt,FILE * const ofP)226 writeMacpRowUnpacked(const bit  * const imageBits,
227                      unsigned int const leftMarginCharCt,
228                      unsigned int const imageColCharCt,
229                      FILE *       const ofP) {
230 /*--------------------------------------------------------------------------
231   Encode (without compression) and output one row.  The row comes divided into
232   three parts: left margin, image, right margin.
233 ----------------------------------------------------------------------------*/
234     char const marginByte = 0x00;  /* White bits for margin */
235     unsigned int const rightMarginCharCt =
236         MACP_COLCHARS - leftMarginCharCt - imageColCharCt;
237 
238     unsigned int i;
239 
240     fputc(MACP_COLCHARS - 1, ofP);
241 
242     for (i = 0; i < leftMarginCharCt; ++i)
243         fputc(marginByte, ofP);
244 
245     if (imageColCharCt > 0)
246         fwrite(imageBits, 1, imageColCharCt, ofP);
247 
248     for (i = 0; i < rightMarginCharCt; ++i)
249         fputc(marginByte, ofP);
250 }
251 
252 
253 
254 static void
writeMacpRowPacked(const bit * const packedBits,unsigned int const leftMarginCharCt,unsigned int const imageColCharCt,unsigned int const rightMarginCharCt,FILE * const ofP)255 writeMacpRowPacked(const bit  * const packedBits,
256                    unsigned int const leftMarginCharCt,
257                    unsigned int const imageColCharCt,
258                    unsigned int const rightMarginCharCt,
259                    FILE *       const ofP) {
260 /*--------------------------------------------------------------------------
261   Encode one row and write it to *ofP.
262 
263   As in the unpacked case, the row comes divided into three parts: left
264   margin, image, right margin.  Unlike the unpacked case we need to know both
265   the size of the packed data and the size of the right margin.
266 ----------------------------------------------------------------------------*/
267     char const marginByte = 0x00;  /* White bits for margin */
268 
269     if (leftMarginCharCt > 0) {
270         fputc(257 - leftMarginCharCt, ofP);
271         fputc(marginByte, ofP);
272     }
273 
274     if (imageColCharCt > 0)
275         fwrite(packedBits, 1, imageColCharCt, ofP);
276 
277     if (rightMarginCharCt > 0) {
278         fputc(257 - rightMarginCharCt, ofP);
279         fputc(marginByte, ofP);
280     }
281 }
282 
283 
284 
285 static void
writeMacpRow(bit * const imageBits,unsigned int const leftMarginCharCt,unsigned int const imageColCharCt,bool const norle,FILE * const ofP)286 writeMacpRow(bit        * const imageBits,
287              unsigned int const leftMarginCharCt,
288              unsigned int const imageColCharCt,
289              bool         const norle,
290              FILE *       const ofP) {
291 /*--------------------------------------------------------------------------
292   Write the row 'imageBits' to Standard Output.
293 
294   Write it packed, unless packing would lead to unnecessary bloat or 'norle'
295   is true.
296 ----------------------------------------------------------------------------*/
297     if (norle)
298         writeMacpRowUnpacked(imageBits, leftMarginCharCt, imageColCharCt, ofP);
299     else {
300         unsigned int const rightMarginCharCt =
301             MACP_COLCHARS - leftMarginCharCt - imageColCharCt;
302         unsigned char packedBits[MACP_COLCHARS+1];
303         size_t packedImageLength;
304 
305         if (pm_rlenc_maxbytes(MACP_COLCHARS, PM_RLE_PACKBITS)
306             > MACP_COLCHARS + 1)
307             pm_error("INTERNAL ERROR: RLE buffer too small");
308 
309         pm_rlenc_compressbyte(imageBits, packedBits, PM_RLE_PACKBITS,
310                               imageColCharCt,  &packedImageLength);
311 
312         if (packedImageLength +
313             (leftMarginCharCt  > 0 ? 1 : 0) * 2 +
314             (rightMarginCharCt > 0 ? 1 : 0) * 2
315             < MACP_COLCHARS) {
316             /* It's smaller compressed, so do that */
317             writeMacpRowPacked(packedBits, leftMarginCharCt,
318                                packedImageLength, rightMarginCharCt, ofP);
319         } else { /* Extremely rare */
320             /* It's larger compressed, so do it uncompressed.  See note
321                at top of file.
322             */
323             writeMacpRowUnpacked(imageBits, leftMarginCharCt, imageColCharCt,
324                                  ofP);
325         }
326     }
327 }
328 
329 
330 
331 static void
encodeRowsWithShift(bit * const bitrow,FILE * const ifP,int const inCols,int const format,bool const norle,struct CropPadDimensions const cropPad,FILE * const ofP)332 encodeRowsWithShift(bit *                    const bitrow,
333                     FILE *                   const ifP,
334                     int                      const inCols,
335                     int                      const format,
336                     bool                     const norle,
337                     struct CropPadDimensions const cropPad,
338                     FILE *                   const ofP) {
339 /*--------------------------------------------------------------------------
340   Shift input rows to put only specified columns to output.  Add padding on
341   left and right if necessary.
342 
343   No shift if the input image is the exact size (576 columns) of the Macpaint
344   format.  If the input image is too wide and -left was not specified, extra
345   content on the right is discarded.
346 ----------------------------------------------------------------------------*/
347     unsigned int const offset     =
348         (cropPad.leftMargin + 8 - cropPad.leftCrop % 8) % 8;
349     unsigned int const leftTrim   =
350         cropPad.leftMargin % 8;
351     unsigned int const rightTrim  =
352         (8 - (leftTrim + cropPad.imageWidth) % 8 ) % 8;
353     unsigned int const startChar  =
354         (cropPad.leftCrop + offset) / 8;
355     unsigned int const imageCharCt =
356         pbm_packed_bytes(leftTrim + cropPad.imageWidth);
357     unsigned int const leftMarginCharCt =
358         cropPad.leftMargin / 8;
359 
360     unsigned int row;
361 
362     for (row = 0; row < cropPad.imageHeight; ++row) {
363         pbm_readpbmrow_bitoffset(ifP, bitrow, inCols, format, offset);
364 
365         /* Trim off fractional margin portion in first byte of image data */
366         if (leftTrim > 0) {
367             bitrow[startChar] <<= leftTrim;
368             bitrow[startChar] >>= leftTrim;
369         }
370         /* Do the same with bits in last byte of relevant image data */
371         if (rightTrim > 0) {
372             bitrow[startChar + imageCharCt - 1] >>= rightTrim;
373             bitrow[startChar + imageCharCt - 1] <<= rightTrim;
374         }
375 
376         writeMacpRow(&bitrow[startChar], leftMarginCharCt,
377                      imageCharCt, norle, ofP);
378     }
379 }
380 
381 
382 
383 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)384 writeMacp(unsigned int             const cols,
385           unsigned int             const rows,
386           int                      const format,
387           FILE *                   const ifP,
388           bool                     const norle,
389           struct CropPadDimensions const cropPad,
390           FILE *                   const ofP) {
391 
392     unsigned int row, skipRow;
393     bit * bitrow;
394 
395     writeMacpHeader(ofP);
396 
397     /* Write top padding */
398     for (row = 0; row < cropPad.topMargin; ++row)
399         writeMacpRow(NULL, MACP_COLCHARS, 0, norle, ofP);
400 
401     /* Allocate PBM row with one extra byte for the shift case. */
402     bitrow = pbm_allocrow_packed(cols + 8);
403 
404     for (skipRow = 0; skipRow < cropPad.topCrop; ++skipRow)
405         pbm_readpbmrow_packed(ifP, bitrow, cols, format);
406 
407     encodeRowsWithShift(bitrow, ifP, cols, format, norle, cropPad, ofP);
408 
409     pbm_freerow_packed(bitrow);
410 
411     /* Add bottom padding */
412     for (row = cropPad.bottomMargin + 1; row < MACP_ROWS; ++row)
413         writeMacpRow(NULL, MACP_COLCHARS, 0, norle, ofP);
414 }
415 
416 
417 
418 int
main(int argc,const char * argv[])419 main(int argc, const char *argv[]) {
420 
421     FILE * ifP;
422     int rows, cols;
423     int format;
424     struct CmdlineInfo cmdline;
425     struct CropPadDimensions cropPad;
426 
427     pm_proginit(&argc, argv);
428 
429     parseCommandLine(argc, argv, &cmdline);
430 
431     ifP = pm_openr(cmdline.inputFileName);
432 
433     pbm_readpbminit(ifP, &cols, &rows, &format);
434 
435     calculateCropPad(cmdline, cols, rows, &cropPad);
436 
437     writeMacp(cols, rows, format, ifP, cmdline.norle, cropPad, stdout);
438 
439     pm_close(ifP);
440 
441     return 0;
442 }
443 
444