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