/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 | | // ----------------------------------------------------------------------------- |