libjxl

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

pgx.cc (6317B)


      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 "lib/extras/dec/pgx.h"
      7 
      8 #include <string.h>
      9 
     10 #include "lib/extras/size_constraints.h"
     11 #include "lib/jxl/base/bits.h"
     12 #include "lib/jxl/base/compiler_specific.h"
     13 
     14 namespace jxl {
     15 namespace extras {
     16 namespace {
     17 
     18 struct HeaderPGX {
     19   // NOTE: PGX is always grayscale
     20   size_t xsize;
     21   size_t ysize;
     22   size_t bits_per_sample;
     23   bool big_endian;
     24   bool is_signed;
     25 };
     26 
     27 class Parser {
     28  public:
     29   explicit Parser(const Span<const uint8_t> bytes)
     30       : pos_(bytes.data()), end_(pos_ + bytes.size()) {}
     31 
     32   // Sets "pos" to the first non-header byte/pixel on success.
     33   Status ParseHeader(HeaderPGX* header, const uint8_t** pos) {
     34     // codec.cc ensures we have at least two bytes => no range check here.
     35     if (pos_[0] != 'P' || pos_[1] != 'G') return false;
     36     pos_ += 2;
     37     return ParseHeaderPGX(header, pos);
     38   }
     39 
     40   // Exposed for testing
     41   Status ParseUnsigned(size_t* number) {
     42     if (pos_ == end_) return JXL_FAILURE("PGX: reached end before number");
     43     if (!IsDigit(*pos_)) return JXL_FAILURE("PGX: expected unsigned number");
     44 
     45     *number = 0;
     46     while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
     47       *number *= 10;
     48       *number += *pos_ - '0';
     49       ++pos_;
     50     }
     51 
     52     return true;
     53   }
     54 
     55  private:
     56   static bool IsDigit(const uint8_t c) { return '0' <= c && c <= '9'; }
     57   static bool IsLineBreak(const uint8_t c) { return c == '\r' || c == '\n'; }
     58   static bool IsWhitespace(const uint8_t c) {
     59     return IsLineBreak(c) || c == '\t' || c == ' ';
     60   }
     61 
     62   Status SkipSpace() {
     63     if (pos_ == end_) return JXL_FAILURE("PGX: reached end before space");
     64     const uint8_t c = *pos_;
     65     if (c != ' ') return JXL_FAILURE("PGX: expected space");
     66     ++pos_;
     67     return true;
     68   }
     69 
     70   Status SkipLineBreak() {
     71     if (pos_ == end_) return JXL_FAILURE("PGX: reached end before line break");
     72     // Line break can be either "\n" (0a) or "\r\n" (0d 0a).
     73     if (*pos_ == '\n') {
     74       pos_++;
     75       return true;
     76     } else if (*pos_ == '\r' && pos_ + 1 != end_ && *(pos_ + 1) == '\n') {
     77       pos_ += 2;
     78       return true;
     79     }
     80     return JXL_FAILURE("PGX: expected line break");
     81   }
     82 
     83   Status SkipSingleWhitespace() {
     84     if (pos_ == end_) return JXL_FAILURE("PGX: reached end before whitespace");
     85     if (!IsWhitespace(*pos_)) return JXL_FAILURE("PGX: expected whitespace");
     86     ++pos_;
     87     return true;
     88   }
     89 
     90   Status ParseHeaderPGX(HeaderPGX* header, const uint8_t** pos) {
     91     JXL_RETURN_IF_ERROR(SkipSpace());
     92     if (pos_ + 2 > end_) return JXL_FAILURE("PGX: header too small");
     93     if (*pos_ == 'M' && *(pos_ + 1) == 'L') {
     94       header->big_endian = true;
     95     } else if (*pos_ == 'L' && *(pos_ + 1) == 'M') {
     96       header->big_endian = false;
     97     } else {
     98       return JXL_FAILURE("PGX: invalid endianness");
     99     }
    100     pos_ += 2;
    101     JXL_RETURN_IF_ERROR(SkipSpace());
    102     if (pos_ == end_) return JXL_FAILURE("PGX: header too small");
    103     if (*pos_ == '+') {
    104       header->is_signed = false;
    105     } else if (*pos_ == '-') {
    106       header->is_signed = true;
    107     } else {
    108       return JXL_FAILURE("PGX: invalid signedness");
    109     }
    110     pos_++;
    111     // Skip optional space
    112     if (pos_ < end_ && *pos_ == ' ') pos_++;
    113     JXL_RETURN_IF_ERROR(ParseUnsigned(&header->bits_per_sample));
    114     JXL_RETURN_IF_ERROR(SkipSingleWhitespace());
    115     JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize));
    116     JXL_RETURN_IF_ERROR(SkipSingleWhitespace());
    117     JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize));
    118     // 0xa, or 0xd 0xa.
    119     JXL_RETURN_IF_ERROR(SkipLineBreak());
    120 
    121     // TODO(jon): could do up to 24-bit by converting the values to
    122     // JXL_TYPE_FLOAT.
    123     if (header->bits_per_sample > 16) {
    124       return JXL_FAILURE("PGX: >16 bits not yet supported");
    125     }
    126     // TODO(lode): support signed integers. This may require changing the way
    127     // external_image works.
    128     if (header->is_signed) {
    129       return JXL_FAILURE("PGX: signed not yet supported");
    130     }
    131 
    132     size_t numpixels = header->xsize * header->ysize;
    133     size_t bytes_per_pixel = header->bits_per_sample <= 8 ? 1 : 2;
    134     if (pos_ + numpixels * bytes_per_pixel > end_) {
    135       return JXL_FAILURE("PGX: data too small");
    136     }
    137 
    138     *pos = pos_;
    139     return true;
    140   }
    141 
    142   const uint8_t* pos_;
    143   const uint8_t* const end_;
    144 };
    145 
    146 }  // namespace
    147 
    148 Status DecodeImagePGX(const Span<const uint8_t> bytes,
    149                       const ColorHints& color_hints, PackedPixelFile* ppf,
    150                       const SizeConstraints* constraints) {
    151   Parser parser(bytes);
    152   HeaderPGX header = {};
    153   const uint8_t* pos;
    154   if (!parser.ParseHeader(&header, &pos)) return false;
    155   JXL_RETURN_IF_ERROR(
    156       VerifyDimensions(constraints, header.xsize, header.ysize));
    157   if (header.bits_per_sample == 0 || header.bits_per_sample > 32) {
    158     return JXL_FAILURE("PGX: bits_per_sample invalid");
    159   }
    160 
    161   JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/false,
    162                                       /*is_gray=*/true, ppf));
    163   ppf->info.xsize = header.xsize;
    164   ppf->info.ysize = header.ysize;
    165   // Original data is uint, so exponent_bits_per_sample = 0.
    166   ppf->info.bits_per_sample = header.bits_per_sample;
    167   ppf->info.exponent_bits_per_sample = 0;
    168   ppf->info.uses_original_profile = JXL_TRUE;
    169 
    170   // No alpha in PGX
    171   ppf->info.alpha_bits = 0;
    172   ppf->info.alpha_exponent_bits = 0;
    173   ppf->info.num_color_channels = 1;  // Always grayscale
    174   ppf->info.orientation = JXL_ORIENT_IDENTITY;
    175 
    176   JxlDataType data_type;
    177   if (header.bits_per_sample > 8) {
    178     data_type = JXL_TYPE_UINT16;
    179   } else {
    180     data_type = JXL_TYPE_UINT8;
    181   }
    182 
    183   const JxlPixelFormat format{
    184       /*num_channels=*/1,
    185       /*data_type=*/data_type,
    186       /*endianness=*/header.big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN,
    187       /*align=*/0,
    188   };
    189   ppf->frames.clear();
    190   // Allocates the frame buffer.
    191   ppf->frames.emplace_back(header.xsize, header.ysize, format);
    192   const auto& frame = ppf->frames.back();
    193   size_t pgx_remaining_size = bytes.data() + bytes.size() - pos;
    194   if (pgx_remaining_size < frame.color.pixels_size) {
    195     return JXL_FAILURE("PGX file too small");
    196   }
    197   memcpy(frame.color.pixels(), pos, frame.color.pixels_size);
    198   return true;
    199 }
    200 
    201 }  // namespace extras
    202 }  // namespace jxl