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