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