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