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