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