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