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