libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

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 }