tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

pgx.cc (6438B)


      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 = nullptr;
    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  {
    192    JXL_ASSIGN_OR_RETURN(
    193        PackedFrame frame,
    194        PackedFrame::Create(header.xsize, header.ysize, format));
    195    ppf->frames.emplace_back(std::move(frame));
    196  }
    197  const auto& frame = ppf->frames.back();
    198  size_t pgx_remaining_size = bytes.data() + bytes.size() - pos;
    199  if (pgx_remaining_size < frame.color.pixels_size) {
    200    return JXL_FAILURE("PGX file too small");
    201  }
    202  memcpy(frame.color.pixels(), pos, frame.color.pixels_size);
    203  return true;
    204 }
    205 
    206 }  // namespace extras
    207 }  // namespace jxl