Coverage Report

Created: 2025-11-11 06:35

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