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