1 /*============================================================================
2                                  pamfix
3 ==============================================================================
4   Salvage a Netpbm image that is corrupted in certain ways.
5 
6   By Bryan Henderson, January 2007.
7 
8   Contributed to the public domain by its author.
9 ============================================================================*/
10 
11 #include <setjmp.h>
12 
13 #include "pm_c_util.h"
14 #include "pam.h"
15 #include "shhopt.h"
16 #include "mallocvar.h"
17 
18 struct cmdlineInfo {
19     /* All the information the user supplied in the command line,
20        in a form easy for the program to use.
21     */
22     const char * inputFileName;   /* File name of input file */
23     unsigned int verbose;
24     unsigned int truncate;
25     unsigned int changemaxval;
26     unsigned int clip;
27 };
28 
29 
30 
31 static void
parseCommandLine(int argc,char ** const argv,struct cmdlineInfo * const cmdlineP)32 parseCommandLine(int argc, char ** const argv,
33                  struct cmdlineInfo * const cmdlineP) {
34 /*----------------------------------------------------------------------------
35    Note that the file spec array we return is stored in the storage that
36    was passed to us as the argv array.
37 -----------------------------------------------------------------------------*/
38     optEntry * option_def;
39 
40     optStruct3 opt;
41 
42     unsigned int option_def_index;
43 
44     MALLOCARRAY(option_def, 100);
45 
46     option_def_index = 0;   /* incremented by OPTENTRY */
47 
48     OPTENT3(0, "verbose",      OPT_FLAG, NULL, &cmdlineP->verbose,      0);
49     OPTENT3(0, "truncate",     OPT_FLAG, NULL, &cmdlineP->truncate,     0);
50     OPTENT3(0, "changemaxval", OPT_FLAG, NULL, &cmdlineP->changemaxval, 0);
51     OPTENT3(0, "clip",         OPT_FLAG, NULL, &cmdlineP->clip,         0);
52 
53     opt.opt_table = option_def;
54     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
55     opt.allowNegNum = FALSE;  /* We don't parms that are negative numbers */
56 
57     pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
58         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
59     free(option_def);
60 
61     if (argc-1 == 0)
62         cmdlineP->inputFileName = "-";
63     else if (argc-1 != 1)
64         pm_error("Program takes zero or one argument (filename).  You "
65                  "specified %d", argc-1);
66     else
67         cmdlineP->inputFileName = argv[1];
68 
69     if (cmdlineP->changemaxval && cmdlineP->clip)
70         pm_error("You cannot specify both -changemaxval and -clip");
71 }
72 
73 
74 
75 static unsigned int readErrRow;
76 static bool readErrVerbose;
77 
78 static pm_usererrormsgfn handleRowErrMsg;
79 
80 static void
handleRowErrMsg(const char * const msg)81 handleRowErrMsg(const char * const msg) {
82     if (readErrVerbose)
83         pm_message("Error reading row %u: %s", readErrRow, msg);
84 }
85 
86 
87 static sample
highestSampleInRow(const struct pam * const pamP,tuple * const tuplerow)88 highestSampleInRow(const struct pam * const pamP,
89                    tuple *            const tuplerow) {
90 
91     unsigned int col;
92     sample highestSoFar;
93 
94     for (col = 0, highestSoFar = 0; col < pamP->width; ++col) {
95         unsigned int plane;
96         for (plane = 0; plane < pamP->depth; ++plane)
97             highestSoFar = MAX(highestSoFar, tuplerow[col][plane]);
98     }
99     return highestSoFar;
100 }
101 
102 
103 
104 static void
analyzeRaster(const struct pam * const pamP,unsigned int * const goodRowCountP,sample * const highestSampleP,bool const mustAbortOnReadError,bool const verbose)105 analyzeRaster(const struct pam * const pamP,
106               unsigned int *     const goodRowCountP,
107               sample *           const highestSampleP,
108               bool               const mustAbortOnReadError,
109               bool               const verbose) {
110 /*----------------------------------------------------------------------------
111    Go through the raster at which the stream described by *tweakedPamP is
112    currently positioned and count how many rows can be successfully read
113    (including validating the samples against pamP->maxval) and determine the
114    highest sample value in those rows.
115 
116    Leave the stream positioned arbitrarily.
117 -----------------------------------------------------------------------------*/
118     tuple * tuplerow;
119     unsigned int row;
120     jmp_buf jmpbuf;
121     int rc;
122 
123     tuplerow = pnm_allocpamrow(pamP);
124 
125     pm_setusererrormsgfn(handleRowErrMsg);
126 
127     rc = setjmp(jmpbuf);
128     if (rc == 0) {
129         pm_setjmpbuf(&jmpbuf);
130 
131         readErrVerbose  = mustAbortOnReadError || verbose;
132         *goodRowCountP  = 0;  /* initial value */
133         *highestSampleP = 0;  /* initial value */
134 
135         for (row = 0; row < pamP->height; ++row) {
136             readErrRow = row;
137             pnm_readpamrow(pamP, tuplerow);
138             /* The above does not return if it can't read the next row from
139                the file.  Instead, it longjmps out of this loop.
140 
141                Update return stats now in case the next iteration longjmps out.
142             */
143             *highestSampleP =
144                 MAX(*highestSampleP,
145                     highestSampleInRow(pamP, tuplerow));
146             ++*goodRowCountP;
147         }
148     } else {
149         /* pnm_readpamrow() encountered an error and longjmped */
150         if (mustAbortOnReadError) {
151             /* handleRowErrMsg() has issued the error message */
152             exit(1);
153         }
154     }
155     pm_setjmpbuf(NULL);
156 
157     pm_setusererrormsgfn(NULL);
158 
159     pnm_freepamrow(tuplerow);
160 }
161 
162 
163 
164 static void
clipPamRow(const struct pam * const pamP,tuple * const tuplerow,unsigned int const row,bool const verbose)165 clipPamRow(const struct pam * const pamP,
166            tuple *            const tuplerow,
167            unsigned int       const row,
168            bool               const verbose) {
169 /*----------------------------------------------------------------------------
170    Clip every sample value in tuplerow[] to the maxval.
171 
172    If 'verbose' is true, issue messages about every clipping, indicating it
173    is in row 'row'.
174 -----------------------------------------------------------------------------*/
175     unsigned int col;
176 
177     for (col = 0; col < pamP->width; ++col) {
178         unsigned int plane;
179         for (plane = 0; plane < pamP->depth; ++plane) {
180             if (tuplerow[col][plane] > pamP->maxval) {
181                 if (verbose)
182                     pm_message("Clipping: Row %u Col %u Plane %u.  "
183                                "Sample value %lu exceeds the "
184                                "image maxval of %lu",
185                                row, col, plane, tuplerow[col][plane],
186                                pamP->maxval);
187                 tuplerow[col][plane] = pamP->maxval;
188             }
189         }
190     }
191 }
192 
193 
194 
195 static void
copyGoodRows(const struct pam * const inpamP,const struct pam * const outpamP,bool const verbose)196 copyGoodRows(const struct pam * const inpamP,
197              const struct pam * const outpamP,
198              bool               const verbose) {
199 /*----------------------------------------------------------------------------
200   Copy the raster of the input stream described by *inpamP to the output
201   stream described by *outpamP.  Copy only as many rows as *outpamP allows;
202   assume *outpamP specifies at most the number of rows of input.
203 -----------------------------------------------------------------------------*/
204     tuple * tuplerow;
205     unsigned int row;
206 
207     tuplerow = pnm_allocpamrow(inpamP);
208 
209     for (row = 0; row < outpamP->height; ++row) {
210         pnm_readpamrow(inpamP, tuplerow);
211         clipPamRow(outpamP, tuplerow, row, verbose);
212         pnm_writepamrow(outpamP, tuplerow);
213     }
214 
215     pnm_freepamrow(tuplerow);
216 }
217 
218 
219 
220 int
main(int argc,char * argv[])221 main(int argc, char * argv[]) {
222     struct cmdlineInfo cmdline;
223     struct pam inpam;
224     struct pam outpam;
225     struct pam tweakedPam;
226     FILE * ifP;
227     pm_filepos rasterPos;
228     unsigned int goodRowCount;
229     sample highestSample;
230 
231     pnm_init(&argc, argv);
232 
233     parseCommandLine(argc, argv, &cmdline);
234 
235     ifP = pm_openr_seekable(cmdline.inputFileName);
236 
237     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
238 
239     pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
240 
241     /* Tweak maxval to circumvent out-of-bounds sample value check
242        in libpam.  See function validatePamRow() in libpamread.c .
243 
244        Ideally we would like to set tweaked-maxval higher for the
245        plain (text) formats.  We make a compromise to keep things
246        simple.
247     */
248 
249     tweakedPam = inpam;  /* initial value */
250 
251     if ((cmdline.clip || cmdline.changemaxval)
252         && PNM_FORMAT_TYPE(inpam.format) != PBM_TYPE)
253         tweakedPam.maxval =
254             (((sample) 1) << tweakedPam.bytes_per_sample * 8) - 1;
255 
256     analyzeRaster(&tweakedPam, &goodRowCount, &highestSample,
257                   !cmdline.truncate, cmdline.verbose);
258 
259     if (goodRowCount == 0)
260         pm_error("Cannot read a single row from the image%s",
261                  cmdline.verbose ? "" : ".  Use -verbose to find out why");
262 
263     if (goodRowCount < inpam.height)
264         pm_message("Copying %u good rows; %u bottom rows missing%s",
265                    goodRowCount, inpam.height - goodRowCount,
266                    cmdline.verbose ? "" : ".  Use -verbose to find out why");
267 
268     pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
269 
270     outpam = inpam;  /* initial value */
271 
272     outpam.file = stdout;
273     outpam.height = goodRowCount;
274     if (cmdline.changemaxval && highestSample > outpam.maxval) {
275         pm_message("Raising maxval from %lu to %lu to legitimize "
276                    "all sample values",
277                     outpam.maxval, highestSample);
278         outpam.maxval = highestSample;
279     }
280 
281     pnm_writepaminit(&outpam);
282 
283     copyGoodRows(&tweakedPam, &outpam, cmdline.verbose);
284 
285     pm_close(inpam.file);
286 
287     return 0;
288 }
289 
290 
291 
292