1 // Copyright 2017 Google Inc. All Rights Reserved.
2 //
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the COPYING file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS. All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 // -----------------------------------------------------------------------------
9 //
10 // (limited) PNM decoder
11 
12 #include "./pnmdec.h"
13 
14 #include <assert.h>
15 #include <ctype.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 
20 #include "webp/encode.h"
21 #include "./imageio_util.h"
22 
23 typedef enum {
24   WIDTH_FLAG      = 1 << 0,
25   HEIGHT_FLAG     = 1 << 1,
26   DEPTH_FLAG      = 1 << 2,
27   MAXVAL_FLAG     = 1 << 3,
28   TUPLE_FLAG      = 1 << 4,
29   ALL_NEEDED_FLAGS = WIDTH_FLAG | HEIGHT_FLAG | DEPTH_FLAG | MAXVAL_FLAG
30 } PNMFlags;
31 
32 typedef struct {
33   const uint8_t* data;
34   size_t data_size;
35   int width, height;
36   int bytes_per_px;
37   int depth;          // 1 (grayscale), 2 (grayscale + alpha), 3 (rgb), 4 (rgba)
38   int max_value;
39   int type;           // 5, 6 or 7
40   int seen_flags;
41 } PNMInfo;
42 
43 // -----------------------------------------------------------------------------
44 // PNM decoding
45 
46 #define MAX_LINE_SIZE 1024
47 static const size_t kMinPNMHeaderSize = 3;
48 
ReadLine(const uint8_t * const data,size_t off,size_t data_size,char out[MAX_LINE_SIZE+1],size_t * const out_size)49 static size_t ReadLine(const uint8_t* const data, size_t off, size_t data_size,
50                        char out[MAX_LINE_SIZE + 1], size_t* const out_size) {
51   size_t i = 0;
52   *out_size = 0;
53  redo:
54   for (i = 0; i < MAX_LINE_SIZE && off < data_size; ++i) {
55     out[i] = data[off++];
56     if (out[i] == '\n') break;
57   }
58   if (off < data_size) {
59     if (i == 0) goto redo;         // empty line
60     if (out[0] == '#') goto redo;  // skip comment
61   }
62   out[i] = 0;   // safety sentinel
63   *out_size = i;
64   return off;
65 }
66 
FlagError(const char flag[])67 static size_t FlagError(const char flag[]) {
68   fprintf(stderr, "PAM header error: flags '%s' already seen.\n", flag);
69   return 0;
70 }
71 
72 // inspired from http://netpbm.sourceforge.net/doc/pam.html
ReadPAMFields(PNMInfo * const info,size_t off)73 static size_t ReadPAMFields(PNMInfo* const info, size_t off) {
74   char out[MAX_LINE_SIZE + 1];
75   size_t out_size;
76   int tmp;
77   int expected_depth = -1;
78   assert(info != NULL);
79   while (1) {
80     off = ReadLine(info->data, off, info->data_size, out, &out_size);
81     if (off == 0) return 0;
82     if (sscanf(out, "WIDTH %d", &tmp) == 1) {
83       if (info->seen_flags & WIDTH_FLAG) return FlagError("WIDTH");
84       info->seen_flags |= WIDTH_FLAG;
85       info->width = tmp;
86     } else if (sscanf(out, "HEIGHT %d", &tmp) == 1) {
87       if (info->seen_flags & HEIGHT_FLAG) return FlagError("HEIGHT");
88       info->seen_flags |= HEIGHT_FLAG;
89       info->height = tmp;
90     } else if (sscanf(out, "DEPTH %d", &tmp) == 1) {
91       if (info->seen_flags & DEPTH_FLAG) return FlagError("DEPTH");
92       info->seen_flags |= DEPTH_FLAG;
93       info->depth = tmp;
94     } else if (sscanf(out, "MAXVAL %d", &tmp) == 1) {
95       if (info->seen_flags & MAXVAL_FLAG) return FlagError("MAXVAL");
96       info->seen_flags |= MAXVAL_FLAG;
97       info->max_value = tmp;
98     } else if (!strcmp(out, "TUPLTYPE RGB_ALPHA")) {
99       expected_depth = 4;
100       info->seen_flags |= TUPLE_FLAG;
101     } else if (!strcmp(out, "TUPLTYPE RGB")) {
102       expected_depth = 3;
103       info->seen_flags |= TUPLE_FLAG;
104     } else if (!strcmp(out, "TUPLTYPE GRAYSCALE_ALPHA")) {
105       expected_depth = 2;
106       info->seen_flags |= TUPLE_FLAG;
107     } else if (!strcmp(out, "TUPLTYPE GRAYSCALE")) {
108       expected_depth = 1;
109       info->seen_flags |= TUPLE_FLAG;
110     } else if (!strcmp(out, "ENDHDR")) {
111       break;
112     } else {
113       static const char kEllipsis[] = " ...";
114       int i;
115       if (out_size > 20) sprintf(out + 20 - strlen(kEllipsis), kEllipsis);
116       for (i = 0; i < (int)strlen(out); ++i) {
117         // isprint() might trigger a "char-subscripts" warning if given a char.
118         if (!isprint((int)out[i])) out[i] = ' ';
119       }
120       fprintf(stderr, "PAM header error: unrecognized entry [%s]\n", out);
121       return 0;
122     }
123   }
124   if (!(info->seen_flags & ALL_NEEDED_FLAGS)) {
125     fprintf(stderr, "PAM header error: missing tags%s%s%s%s\n",
126             (info->seen_flags & WIDTH_FLAG) ? "" : " WIDTH",
127             (info->seen_flags & HEIGHT_FLAG) ? "" : " HEIGHT",
128             (info->seen_flags & DEPTH_FLAG) ? "" : " DEPTH",
129             (info->seen_flags & MAXVAL_FLAG) ? "" : " MAXVAL");
130     return 0;
131   }
132   if (expected_depth != -1 && info->depth != expected_depth) {
133     fprintf(stderr, "PAM header error: expected DEPTH %d but got DEPTH %d\n",
134             expected_depth, info->depth);
135     return 0;
136   }
137   return off;
138 }
139 
ReadHeader(PNMInfo * const info)140 static size_t ReadHeader(PNMInfo* const info) {
141   size_t off = 0;
142   char out[MAX_LINE_SIZE + 1];
143   size_t out_size;
144   if (info == NULL) return 0;
145   if (info->data == NULL || info->data_size < kMinPNMHeaderSize) return 0;
146 
147   info->width = info->height = 0;
148   info->type = -1;
149   info->seen_flags = 0;
150   info->bytes_per_px = 0;
151   info->depth = 0;
152   info->max_value = 0;
153 
154   off = ReadLine(info->data, off, info->data_size, out, &out_size);
155   if (off == 0 || sscanf(out, "P%d", &info->type) != 1) return 0;
156   if (info->type == 7) {
157     off = ReadPAMFields(info, off);
158   } else {
159     off = ReadLine(info->data, off, info->data_size, out, &out_size);
160     if (off == 0 || sscanf(out, "%d %d", &info->width, &info->height) != 2) {
161       return 0;
162     }
163     off = ReadLine(info->data, off, info->data_size, out, &out_size);
164     if (off == 0 || sscanf(out, "%d", &info->max_value) != 1) return 0;
165 
166     // finish initializing missing fields
167     info->depth = (info->type == 5) ? 1 : 3;
168   }
169   // perform some basic numerical validation
170   if (info->width <= 0 || info->height <= 0 ||
171       info->type <= 0 || info->type >= 9 ||
172       info->depth <= 0 || info->depth > 4 ||
173       info->max_value <= 0 || info->max_value >= 65536) {
174     return 0;
175   }
176   info->bytes_per_px = info->depth * (info->max_value > 255 ? 2 : 1);
177   return off;
178 }
179 
ReadPNM(const uint8_t * const data,size_t data_size,WebPPicture * const pic,int keep_alpha,struct Metadata * const metadata)180 int ReadPNM(const uint8_t* const data, size_t data_size,
181             WebPPicture* const pic, int keep_alpha,
182             struct Metadata* const metadata) {
183   int ok = 0;
184   int i, j;
185   uint64_t stride, pixel_bytes, sample_size, depth;
186   uint8_t* rgb = NULL, *tmp_rgb;
187   size_t offset;
188   PNMInfo info;
189 
190   info.data = data;
191   info.data_size = data_size;
192   offset = ReadHeader(&info);
193   if (offset == 0) {
194     fprintf(stderr, "Error parsing PNM header.\n");
195     goto End;
196   }
197 
198   if (info.type < 5 || info.type > 7) {
199     fprintf(stderr, "Unsupported P%d PNM format.\n", info.type);
200     goto End;
201   }
202 
203   // Some basic validations.
204   if (pic == NULL) goto End;
205   if (info.width > WEBP_MAX_DIMENSION || info.height > WEBP_MAX_DIMENSION) {
206     fprintf(stderr, "Invalid %dx%d dimension for PNM\n",
207                     info.width, info.height);
208     goto End;
209   }
210 
211   pixel_bytes = (uint64_t)info.width * info.height * info.bytes_per_px;
212   if (data_size < offset + pixel_bytes) {
213     fprintf(stderr, "Truncated PNM file (P%d).\n", info.type);
214     goto End;
215   }
216   sample_size = (info.max_value > 255) ? 2 : 1;
217   // final depth
218   depth = (info.depth == 1 || info.depth == 3 || !keep_alpha) ? 3 : 4;
219   stride = depth * info.width;
220   if (stride != (size_t)stride ||
221       !ImgIoUtilCheckSizeArgumentsOverflow(stride, info.height)) {
222     goto End;
223   }
224 
225   rgb = (uint8_t*)malloc((size_t)stride * info.height);
226   if (rgb == NULL) goto End;
227 
228   // Convert input.
229   // We only optimize for the sample_size=1, max_value=255, depth=1 case.
230   tmp_rgb = rgb;
231   for (j = 0; j < info.height; ++j) {
232     const uint8_t* in = data + offset;
233     offset += info.bytes_per_px * info.width;
234     assert(offset <= data_size);
235     if (info.max_value == 255 && info.depth >= 3) {
236       // RGB or RGBA
237       if (info.depth == 3 || keep_alpha) {
238         memcpy(tmp_rgb, in, info.depth * info.width * sizeof(*in));
239       } else {
240         assert(info.depth == 4 && !keep_alpha);
241         for (i = 0; i < info.width; ++i) {
242           tmp_rgb[3 * i + 0] = in[4 * i + 0];
243           tmp_rgb[3 * i + 1] = in[4 * i + 1];
244           tmp_rgb[3 * i + 2] = in[4 * i + 2];
245         }
246       }
247     } else {
248       // Unoptimized case, we need to handle non-trivial operations:
249       //   * convert 16b to 8b (if max_value > 255)
250       //   * rescale to [0..255] range (if max_value != 255)
251       //   * drop the alpha channel (if keep_alpha is false)
252       const uint32_t round = info.max_value / 2;
253       int k = 0;
254       for (i = 0; i < info.width * info.depth; ++i) {
255         uint32_t v = (sample_size == 2) ? 256u * in[2 * i + 0] + in[2 * i + 1]
256                    : in[i];
257         if (info.max_value != 255) v = (v * 255u + round) / info.max_value;
258         if (v > 255u) v = 255u;
259         if (info.depth > 2) {
260           if (!keep_alpha && info.depth == 4 && (i % 4) == 3) {
261             // skip alpha
262           } else {
263             tmp_rgb[k] = v;
264             k += 1;
265           }
266         } else if (info.depth == 1 || (i % 2) == 0) {
267           tmp_rgb[k + 0] = tmp_rgb[k + 1] = tmp_rgb[k + 2] = v;
268           k += 3;
269         } else if (keep_alpha && info.depth == 2) {
270           tmp_rgb[k] = v;
271           k += 1;
272         } else {
273           // skip alpha
274         }
275       }
276     }
277     tmp_rgb += stride;
278   }
279 
280   // WebP conversion.
281   pic->width = info.width;
282   pic->height = info.height;
283   ok = (depth == 4) ? WebPPictureImportRGBA(pic, rgb, (int)stride)
284                     : WebPPictureImportRGB(pic, rgb, (int)stride);
285   if (!ok) goto End;
286 
287   ok = 1;
288  End:
289   free((void*)rgb);
290 
291   (void)metadata;
292   (void)keep_alpha;
293   return ok;
294 }
295 
296 // -----------------------------------------------------------------------------
297