pam-input.h (8312B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include <limits.h> 7 #include <stdint.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 12 bool error_msg(const char* message) { 13 fprintf(stderr, "%s\n", message); 14 return false; 15 } 16 #define return_on_error(X) \ 17 if (!X) return false; 18 19 size_t Log2(uint32_t value) { return 31 - __builtin_clz(value); } 20 21 struct HeaderPNM { 22 size_t xsize; 23 size_t ysize; 24 bool is_gray; // PGM 25 bool has_alpha; // PAM 26 size_t bits_per_sample; 27 }; 28 29 class Parser { 30 public: 31 explicit Parser(uint8_t* data, size_t length) 32 : pos_(data), end_(data + length) {} 33 34 // Sets "pos" to the first non-header byte/pixel on success. 35 bool ParseHeader(HeaderPNM* header, const uint8_t** pos) { 36 // codec.cc ensures we have at least two bytes => no range check here. 37 if (pos_[0] != 'P') return false; 38 const uint8_t type = pos_[1]; 39 pos_ += 2; 40 41 switch (type) { 42 case '5': 43 header->is_gray = true; 44 return ParseHeaderPNM(header, pos); 45 46 case '6': 47 header->is_gray = false; 48 return ParseHeaderPNM(header, pos); 49 50 case '7': 51 return ParseHeaderPAM(header, pos); 52 53 default: 54 return false; 55 } 56 } 57 58 // Exposed for testing 59 bool ParseUnsigned(size_t* number) { 60 if (pos_ == end_) return error_msg("PNM: reached end before number"); 61 if (!IsDigit(*pos_)) return error_msg("PNM: expected unsigned number"); 62 63 *number = 0; 64 while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { 65 *number *= 10; 66 *number += *pos_ - '0'; 67 ++pos_; 68 } 69 70 return true; 71 } 72 73 bool ParseSigned(double* number) { 74 if (pos_ == end_) return error_msg("PNM: reached end before signed"); 75 76 if (*pos_ != '-' && *pos_ != '+' && !IsDigit(*pos_)) { 77 return error_msg("PNM: expected signed number"); 78 } 79 80 // Skip sign 81 const bool is_neg = *pos_ == '-'; 82 if (is_neg || *pos_ == '+') { 83 ++pos_; 84 if (pos_ == end_) return error_msg("PNM: reached end before digits"); 85 } 86 87 // Leading digits 88 *number = 0.0; 89 while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { 90 *number *= 10; 91 *number += *pos_ - '0'; 92 ++pos_; 93 } 94 95 // Decimal places? 96 if (pos_ < end_ && *pos_ == '.') { 97 ++pos_; 98 double place = 0.1; 99 while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { 100 *number += (*pos_ - '0') * place; 101 place *= 0.1; 102 ++pos_; 103 } 104 } 105 106 if (is_neg) *number = -*number; 107 return true; 108 } 109 110 private: 111 static bool IsDigit(const uint8_t c) { return '0' <= c && c <= '9'; } 112 static bool IsLineBreak(const uint8_t c) { return c == '\r' || c == '\n'; } 113 static bool IsWhitespace(const uint8_t c) { 114 return IsLineBreak(c) || c == '\t' || c == ' '; 115 } 116 117 bool SkipBlank() { 118 if (pos_ == end_) return error_msg("PNM: reached end before blank"); 119 const uint8_t c = *pos_; 120 if (c != ' ' && c != '\n') return error_msg("PNM: expected blank"); 121 ++pos_; 122 return true; 123 } 124 125 bool SkipSingleWhitespace() { 126 if (pos_ == end_) return error_msg("PNM: reached end before whitespace"); 127 if (!IsWhitespace(*pos_)) return error_msg("PNM: expected whitespace"); 128 ++pos_; 129 return true; 130 } 131 132 bool SkipWhitespace() { 133 if (pos_ == end_) return error_msg("PNM: reached end before whitespace"); 134 if (!IsWhitespace(*pos_) && *pos_ != '#') { 135 return error_msg("PNM: expected whitespace/comment"); 136 } 137 138 while (pos_ < end_ && IsWhitespace(*pos_)) { 139 ++pos_; 140 } 141 142 // Comment(s) 143 while (pos_ != end_ && *pos_ == '#') { 144 while (pos_ != end_ && !IsLineBreak(*pos_)) { 145 ++pos_; 146 } 147 // Newline(s) 148 while (pos_ != end_ && IsLineBreak(*pos_)) pos_++; 149 } 150 151 while (pos_ < end_ && IsWhitespace(*pos_)) { 152 ++pos_; 153 } 154 return true; 155 } 156 157 bool MatchString(const char* keyword) { 158 const uint8_t* ppos = pos_; 159 const uint8_t* kw = reinterpret_cast<const uint8_t*>(keyword); 160 while (*kw) { 161 if (ppos >= end_) return error_msg("PAM: unexpected end of input"); 162 if (*kw != *ppos) return false; 163 ppos++; 164 kw++; 165 } 166 pos_ = ppos; 167 return_on_error(SkipWhitespace()); 168 return true; 169 } 170 171 bool ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) { 172 size_t num_channels = 3; 173 size_t max_val = 255; 174 while (!MatchString("ENDHDR")) { 175 return_on_error(SkipWhitespace()); 176 if (MatchString("WIDTH")) { 177 return_on_error(ParseUnsigned(&header->xsize)); 178 } else if (MatchString("HEIGHT")) { 179 return_on_error(ParseUnsigned(&header->ysize)); 180 } else if (MatchString("DEPTH")) { 181 return_on_error(ParseUnsigned(&num_channels)); 182 } else if (MatchString("MAXVAL")) { 183 return_on_error(ParseUnsigned(&max_val)); 184 } else if (MatchString("TUPLTYPE")) { 185 if (MatchString("RGB_ALPHA")) { 186 header->has_alpha = true; 187 } else if (MatchString("RGB")) { 188 } else if (MatchString("GRAYSCALE_ALPHA")) { 189 header->has_alpha = true; 190 header->is_gray = true; 191 } else if (MatchString("GRAYSCALE")) { 192 header->is_gray = true; 193 } else if (MatchString("BLACKANDWHITE_ALPHA")) { 194 header->has_alpha = true; 195 header->is_gray = true; 196 max_val = 1; 197 } else if (MatchString("BLACKANDWHITE")) { 198 header->is_gray = true; 199 max_val = 1; 200 } else { 201 return error_msg("PAM: unknown TUPLTYPE"); 202 } 203 } else { 204 return error_msg("PAM: unknown header keyword"); 205 } 206 } 207 if (num_channels != 208 (header->has_alpha ? 1 : 0) + (header->is_gray ? 1 : 3)) { 209 return error_msg("PAM: bad DEPTH"); 210 } 211 if (max_val == 0 || max_val >= 65536) { 212 return error_msg("PAM: bad MAXVAL"); 213 } 214 header->bits_per_sample = Log2(max_val + 1); 215 216 *pos = pos_; 217 return true; 218 } 219 220 bool ParseHeaderPNM(HeaderPNM* header, const uint8_t** pos) { 221 return_on_error(SkipWhitespace()); 222 return_on_error(ParseUnsigned(&header->xsize)); 223 224 return_on_error(SkipWhitespace()); 225 return_on_error(ParseUnsigned(&header->ysize)); 226 227 return_on_error(SkipWhitespace()); 228 size_t max_val; 229 return_on_error(ParseUnsigned(&max_val)); 230 if (max_val == 0 || max_val >= 65536) { 231 return error_msg("PNM: bad MaxVal"); 232 } 233 header->bits_per_sample = Log2(max_val + 1); 234 235 return_on_error(SkipSingleWhitespace()); 236 237 *pos = pos_; 238 return true; 239 } 240 241 const uint8_t* pos_; 242 const uint8_t* const end_; 243 }; 244 245 bool load_file(unsigned char** out, size_t* outsize, const char* filename) { 246 FILE* file; 247 file = fopen(filename, "rb"); 248 if (!file) return false; 249 if (fseek(file, 0, SEEK_END) != 0) { 250 fclose(file); 251 return false; 252 } 253 *outsize = ftell(file); 254 if (*outsize == LONG_MAX || *outsize < 9 || fseek(file, 0, SEEK_SET)) { 255 fclose(file); 256 return false; 257 } 258 *out = static_cast<unsigned char*>(malloc(*outsize)); 259 if (!(*out)) { 260 fclose(file); 261 return false; 262 } 263 size_t readsize; 264 readsize = fread(*out, 1, *outsize, file); 265 fclose(file); 266 if (readsize != *outsize) return false; 267 return true; 268 } 269 270 bool DecodePAM(const char* filename, uint8_t** buffer, size_t* w, size_t* h, 271 size_t* nb_chans, size_t* bitdepth) { 272 unsigned char* in_file; 273 size_t in_size; 274 if (!load_file(&in_file, &in_size, filename)) 275 return error_msg("Could not read input file"); 276 Parser parser(in_file, in_size); 277 HeaderPNM header = {}; 278 const uint8_t* pos = nullptr; 279 if (!parser.ParseHeader(&header, &pos)) return false; 280 281 if (header.bits_per_sample == 0 || header.bits_per_sample > 16) { 282 return error_msg("PNM: bits_per_sample invalid (can do at most 16-bit)"); 283 } 284 *w = header.xsize; 285 *h = header.ysize; 286 *bitdepth = header.bits_per_sample; 287 *nb_chans = (header.is_gray ? 1 : 3) + (header.has_alpha ? 1 : 0); 288 289 size_t pnm_remaining_size = in_file + in_size - pos; 290 size_t buffer_size = *w * *h * *nb_chans * (*bitdepth > 8 ? 2 : 1); 291 if (pnm_remaining_size < buffer_size) { 292 return error_msg("PNM file too small"); 293 } 294 *buffer = static_cast<uint8_t*>(malloc(buffer_size)); 295 memcpy(*buffer, pos, buffer_size); 296 return true; 297 }