Imported Upstream version 0.9.0
[platform/upstream/libjxl.git] / lib / extras / dec / pgx.cc
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 = 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