/src/libjxl/lib/extras/dec/pnm.cc
Line | Count | Source (jump to first uncovered line) |
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/pnm.h" |
7 | | |
8 | | #include <stdlib.h> |
9 | | #include <string.h> |
10 | | |
11 | | #include <cmath> |
12 | | |
13 | | #include "lib/extras/size_constraints.h" |
14 | | #include "lib/jxl/base/bits.h" |
15 | | #include "lib/jxl/base/compiler_specific.h" |
16 | | #include "lib/jxl/base/status.h" |
17 | | |
18 | | namespace jxl { |
19 | | namespace extras { |
20 | | namespace { |
21 | | |
22 | | struct HeaderPNM { |
23 | | size_t xsize; |
24 | | size_t ysize; |
25 | | bool is_gray; // PGM |
26 | | bool has_alpha; // PAM |
27 | | size_t bits_per_sample; |
28 | | bool floating_point; |
29 | | bool big_endian; |
30 | | std::vector<JxlExtraChannelType> ec_types; // PAM |
31 | | }; |
32 | | |
33 | | class Parser { |
34 | | public: |
35 | | explicit Parser(const Span<const uint8_t> bytes) |
36 | 0 | : pos_(bytes.data()), end_(pos_ + bytes.size()) {} |
37 | | |
38 | | // Sets "pos" to the first non-header byte/pixel on success. |
39 | 0 | Status ParseHeader(HeaderPNM* header, const uint8_t** pos) { |
40 | | // codec.cc ensures we have at least two bytes => no range check here. |
41 | 0 | if (pos_[0] != 'P') return false; |
42 | 0 | const uint8_t type = pos_[1]; |
43 | 0 | pos_ += 2; |
44 | |
|
45 | 0 | switch (type) { |
46 | 0 | case '4': |
47 | 0 | return JXL_FAILURE("pbm not supported"); |
48 | | |
49 | 0 | case '5': |
50 | 0 | header->is_gray = true; |
51 | 0 | return ParseHeaderPNM(header, pos); |
52 | | |
53 | 0 | case '6': |
54 | 0 | header->is_gray = false; |
55 | 0 | return ParseHeaderPNM(header, pos); |
56 | | |
57 | 0 | case '7': |
58 | 0 | return ParseHeaderPAM(header, pos); |
59 | | |
60 | 0 | case 'F': |
61 | 0 | header->is_gray = false; |
62 | 0 | return ParseHeaderPFM(header, pos); |
63 | | |
64 | 0 | case 'f': |
65 | 0 | header->is_gray = true; |
66 | 0 | return ParseHeaderPFM(header, pos); |
67 | 0 | } |
68 | 0 | return false; |
69 | 0 | } |
70 | | |
71 | | // Exposed for testing |
72 | 0 | Status ParseUnsigned(size_t* number) { |
73 | 0 | if (pos_ == end_) return JXL_FAILURE("PNM: reached end before number"); |
74 | 0 | if (!IsDigit(*pos_)) return JXL_FAILURE("PNM: expected unsigned number"); |
75 | | |
76 | 0 | *number = 0; |
77 | 0 | while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { |
78 | 0 | *number *= 10; |
79 | 0 | *number += *pos_ - '0'; |
80 | 0 | ++pos_; |
81 | 0 | } |
82 | |
|
83 | 0 | return true; |
84 | 0 | } |
85 | | |
86 | 0 | Status ParseSigned(double* number) { |
87 | 0 | if (pos_ == end_) return JXL_FAILURE("PNM: reached end before signed"); |
88 | | |
89 | 0 | if (*pos_ != '-' && *pos_ != '+' && !IsDigit(*pos_)) { |
90 | 0 | return JXL_FAILURE("PNM: expected signed number"); |
91 | 0 | } |
92 | | |
93 | | // Skip sign |
94 | 0 | const bool is_neg = *pos_ == '-'; |
95 | 0 | if (is_neg || *pos_ == '+') { |
96 | 0 | ++pos_; |
97 | 0 | if (pos_ == end_) return JXL_FAILURE("PNM: reached end before digits"); |
98 | 0 | } |
99 | | |
100 | | // Leading digits |
101 | 0 | *number = 0.0; |
102 | 0 | while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { |
103 | 0 | *number *= 10; |
104 | 0 | *number += *pos_ - '0'; |
105 | 0 | ++pos_; |
106 | 0 | } |
107 | | |
108 | | // Decimal places? |
109 | 0 | if (pos_ < end_ && *pos_ == '.') { |
110 | 0 | ++pos_; |
111 | 0 | double place = 0.1; |
112 | 0 | while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { |
113 | 0 | *number += (*pos_ - '0') * place; |
114 | 0 | place *= 0.1; |
115 | 0 | ++pos_; |
116 | 0 | } |
117 | 0 | } |
118 | |
|
119 | 0 | if (is_neg) *number = -*number; |
120 | 0 | return true; |
121 | 0 | } |
122 | | |
123 | | private: |
124 | 0 | static bool IsDigit(const uint8_t c) { return '0' <= c && c <= '9'; } |
125 | 0 | static bool IsLineBreak(const uint8_t c) { return c == '\r' || c == '\n'; } |
126 | 0 | static bool IsWhitespace(const uint8_t c) { |
127 | 0 | return IsLineBreak(c) || c == '\t' || c == ' '; |
128 | 0 | } |
129 | | |
130 | 0 | Status SkipBlank() { |
131 | 0 | if (pos_ == end_) return JXL_FAILURE("PNM: reached end before blank"); |
132 | 0 | const uint8_t c = *pos_; |
133 | 0 | if (c != ' ' && c != '\n') return JXL_FAILURE("PNM: expected blank"); |
134 | 0 | ++pos_; |
135 | 0 | return true; |
136 | 0 | } |
137 | | |
138 | 0 | Status SkipSingleWhitespace() { |
139 | 0 | if (pos_ == end_) return JXL_FAILURE("PNM: reached end before whitespace"); |
140 | 0 | if (!IsWhitespace(*pos_)) return JXL_FAILURE("PNM: expected whitespace"); |
141 | 0 | ++pos_; |
142 | 0 | return true; |
143 | 0 | } |
144 | | |
145 | 0 | Status SkipWhitespace() { |
146 | 0 | if (pos_ == end_) return JXL_FAILURE("PNM: reached end before whitespace"); |
147 | 0 | if (!IsWhitespace(*pos_) && *pos_ != '#') { |
148 | 0 | return JXL_FAILURE("PNM: expected whitespace/comment"); |
149 | 0 | } |
150 | | |
151 | 0 | while (pos_ < end_ && IsWhitespace(*pos_)) { |
152 | 0 | ++pos_; |
153 | 0 | } |
154 | | |
155 | | // Comment(s) |
156 | 0 | while (pos_ != end_ && *pos_ == '#') { |
157 | 0 | while (pos_ != end_ && !IsLineBreak(*pos_)) { |
158 | 0 | ++pos_; |
159 | 0 | } |
160 | | // Newline(s) |
161 | 0 | while (pos_ != end_ && IsLineBreak(*pos_)) pos_++; |
162 | 0 | } |
163 | |
|
164 | 0 | while (pos_ < end_ && IsWhitespace(*pos_)) { |
165 | 0 | ++pos_; |
166 | 0 | } |
167 | 0 | return true; |
168 | 0 | } |
169 | | |
170 | 0 | Status MatchString(const char* keyword, bool skipws = true) { |
171 | 0 | const uint8_t* ppos = pos_; |
172 | 0 | while (*keyword) { |
173 | 0 | if (ppos >= end_) return JXL_FAILURE("PAM: unexpected end of input"); |
174 | 0 | if (*keyword != *ppos) return false; |
175 | 0 | ppos++; |
176 | 0 | keyword++; |
177 | 0 | } |
178 | 0 | pos_ = ppos; |
179 | 0 | if (skipws) { |
180 | 0 | JXL_RETURN_IF_ERROR(SkipWhitespace()); |
181 | 0 | } else { |
182 | 0 | JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); |
183 | 0 | } |
184 | 0 | return true; |
185 | 0 | } |
186 | | |
187 | 0 | Status ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) { |
188 | 0 | size_t depth = 3; |
189 | 0 | size_t max_val = 255; |
190 | 0 | JXL_RETURN_IF_ERROR(SkipWhitespace()); |
191 | 0 | while (!MatchString("ENDHDR", /*skipws=*/false)) { |
192 | 0 | if (MatchString("WIDTH")) { |
193 | 0 | JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize)); |
194 | 0 | JXL_RETURN_IF_ERROR(SkipWhitespace()); |
195 | 0 | } else if (MatchString("HEIGHT")) { |
196 | 0 | JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize)); |
197 | 0 | JXL_RETURN_IF_ERROR(SkipWhitespace()); |
198 | 0 | } else if (MatchString("DEPTH")) { |
199 | 0 | JXL_RETURN_IF_ERROR(ParseUnsigned(&depth)); |
200 | 0 | JXL_RETURN_IF_ERROR(SkipWhitespace()); |
201 | 0 | } else if (MatchString("MAXVAL")) { |
202 | 0 | JXL_RETURN_IF_ERROR(ParseUnsigned(&max_val)); |
203 | 0 | JXL_RETURN_IF_ERROR(SkipWhitespace()); |
204 | 0 | } else if (MatchString("TUPLTYPE")) { |
205 | 0 | if (MatchString("RGB_ALPHA")) { |
206 | 0 | header->has_alpha = true; |
207 | 0 | } else if (MatchString("RGB")) { |
208 | 0 | } else if (MatchString("GRAYSCALE_ALPHA")) { |
209 | 0 | header->has_alpha = true; |
210 | 0 | header->is_gray = true; |
211 | 0 | } else if (MatchString("GRAYSCALE")) { |
212 | 0 | header->is_gray = true; |
213 | 0 | } else if (MatchString("BLACKANDWHITE_ALPHA")) { |
214 | 0 | header->has_alpha = true; |
215 | 0 | header->is_gray = true; |
216 | 0 | max_val = 1; |
217 | 0 | } else if (MatchString("BLACKANDWHITE")) { |
218 | 0 | header->is_gray = true; |
219 | 0 | max_val = 1; |
220 | 0 | } else if (MatchString("Alpha")) { |
221 | 0 | header->ec_types.push_back(JXL_CHANNEL_ALPHA); |
222 | 0 | } else if (MatchString("Depth")) { |
223 | 0 | header->ec_types.push_back(JXL_CHANNEL_DEPTH); |
224 | 0 | } else if (MatchString("SpotColor")) { |
225 | 0 | header->ec_types.push_back(JXL_CHANNEL_SPOT_COLOR); |
226 | 0 | } else if (MatchString("SelectionMask")) { |
227 | 0 | header->ec_types.push_back(JXL_CHANNEL_SELECTION_MASK); |
228 | 0 | } else if (MatchString("Black")) { |
229 | 0 | header->ec_types.push_back(JXL_CHANNEL_BLACK); |
230 | 0 | } else if (MatchString("CFA")) { |
231 | 0 | header->ec_types.push_back(JXL_CHANNEL_CFA); |
232 | 0 | } else if (MatchString("Thermal")) { |
233 | 0 | header->ec_types.push_back(JXL_CHANNEL_THERMAL); |
234 | 0 | } else { |
235 | 0 | return JXL_FAILURE("PAM: unknown TUPLTYPE"); |
236 | 0 | } |
237 | 0 | } else { |
238 | 0 | constexpr size_t kMaxHeaderLength = 20; |
239 | 0 | char unknown_header[kMaxHeaderLength + 1]; |
240 | 0 | size_t len = std::min<size_t>(kMaxHeaderLength, end_ - pos_); |
241 | 0 | strncpy(unknown_header, reinterpret_cast<const char*>(pos_), len); |
242 | 0 | unknown_header[len] = 0; |
243 | 0 | return JXL_FAILURE("PAM: unknown header keyword: %s", unknown_header); |
244 | 0 | } |
245 | 0 | } |
246 | 0 | size_t num_channels = header->is_gray ? 1 : 3; |
247 | 0 | if (header->has_alpha) num_channels++; |
248 | 0 | if (num_channels + header->ec_types.size() != depth) { |
249 | 0 | return JXL_FAILURE("PAM: bad DEPTH"); |
250 | 0 | } |
251 | 0 | if (max_val == 0 || max_val >= 65536) { |
252 | 0 | return JXL_FAILURE("PAM: bad MAXVAL"); |
253 | 0 | } |
254 | | // e.g. When `max_val` is 1 , we want 1 bit: |
255 | 0 | header->bits_per_sample = FloorLog2Nonzero(max_val) + 1; |
256 | 0 | if ((1u << header->bits_per_sample) - 1 != max_val) |
257 | 0 | return JXL_FAILURE("PNM: unsupported MaxVal (expected 2^n - 1)"); |
258 | | // PAM does not pack bits as in PBM. |
259 | | |
260 | 0 | header->floating_point = false; |
261 | 0 | header->big_endian = true; |
262 | 0 | *pos = pos_; |
263 | 0 | return true; |
264 | 0 | } |
265 | | |
266 | 0 | Status ParseHeaderPNM(HeaderPNM* header, const uint8_t** pos) { |
267 | 0 | JXL_RETURN_IF_ERROR(SkipWhitespace()); |
268 | 0 | JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize)); |
269 | | |
270 | 0 | JXL_RETURN_IF_ERROR(SkipWhitespace()); |
271 | 0 | JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize)); |
272 | | |
273 | 0 | JXL_RETURN_IF_ERROR(SkipWhitespace()); |
274 | 0 | size_t max_val; |
275 | 0 | JXL_RETURN_IF_ERROR(ParseUnsigned(&max_val)); |
276 | 0 | if (max_val == 0 || max_val >= 65536) { |
277 | 0 | return JXL_FAILURE("PNM: bad MaxVal"); |
278 | 0 | } |
279 | 0 | header->bits_per_sample = FloorLog2Nonzero(max_val) + 1; |
280 | 0 | if ((1u << header->bits_per_sample) - 1 != max_val) |
281 | 0 | return JXL_FAILURE("PNM: unsupported MaxVal (expected 2^n - 1)"); |
282 | 0 | header->floating_point = false; |
283 | 0 | header->big_endian = true; |
284 | |
|
285 | 0 | JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); |
286 | | |
287 | 0 | *pos = pos_; |
288 | 0 | return true; |
289 | 0 | } |
290 | | |
291 | 0 | Status ParseHeaderPFM(HeaderPNM* header, const uint8_t** pos) { |
292 | 0 | JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); |
293 | 0 | JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize)); |
294 | | |
295 | 0 | JXL_RETURN_IF_ERROR(SkipBlank()); |
296 | 0 | JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize)); |
297 | | |
298 | 0 | JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); |
299 | | // The scale has no meaning as multiplier, only its sign is used to |
300 | | // indicate endianness. All software expects nominal range 0..1. |
301 | 0 | double scale; |
302 | 0 | JXL_RETURN_IF_ERROR(ParseSigned(&scale)); |
303 | 0 | if (scale == 0.0) { |
304 | 0 | return JXL_FAILURE("PFM: bad scale factor value."); |
305 | 0 | } else if (std::abs(scale) != 1.0) { |
306 | 0 | JXL_WARNING("PFM: Discarding non-unit scale factor"); |
307 | 0 | } |
308 | 0 | header->big_endian = scale > 0.0; |
309 | 0 | header->bits_per_sample = 32; |
310 | 0 | header->floating_point = true; |
311 | |
|
312 | 0 | JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); |
313 | | |
314 | 0 | *pos = pos_; |
315 | 0 | return true; |
316 | 0 | } |
317 | | |
318 | | const uint8_t* pos_; |
319 | | const uint8_t* const end_; |
320 | | }; |
321 | | |
322 | 0 | Span<const uint8_t> MakeSpan(const char* str) { |
323 | 0 | return Span<const uint8_t>(reinterpret_cast<const uint8_t*>(str), |
324 | 0 | strlen(str)); |
325 | 0 | } |
326 | | |
327 | | } // namespace |
328 | | |
329 | | Status DecodeImagePNM(const Span<const uint8_t> bytes, |
330 | | const ColorHints& color_hints, PackedPixelFile* ppf, |
331 | 0 | const SizeConstraints* constraints) { |
332 | 0 | Parser parser(bytes); |
333 | 0 | HeaderPNM header = {}; |
334 | 0 | const uint8_t* pos = nullptr; |
335 | 0 | if (!parser.ParseHeader(&header, &pos)) return false; |
336 | 0 | JXL_RETURN_IF_ERROR( |
337 | 0 | VerifyDimensions(constraints, header.xsize, header.ysize)); |
338 | | |
339 | 0 | if (header.bits_per_sample == 0 || header.bits_per_sample > 32) { |
340 | 0 | return JXL_FAILURE("PNM: bits_per_sample invalid"); |
341 | 0 | } |
342 | | |
343 | | // PPM specify that in the raster, the sample values are "nonlinear" (BP.709, |
344 | | // with gamma number of 2.2). Deviate from the specification and assume |
345 | | // `sRGB` in our implementation. |
346 | 0 | JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/false, |
347 | 0 | header.is_gray, ppf)); |
348 | | |
349 | 0 | ppf->info.xsize = header.xsize; |
350 | 0 | ppf->info.ysize = header.ysize; |
351 | 0 | if (header.floating_point) { |
352 | 0 | ppf->info.bits_per_sample = 32; |
353 | 0 | ppf->info.exponent_bits_per_sample = 8; |
354 | 0 | } else { |
355 | 0 | ppf->info.bits_per_sample = header.bits_per_sample; |
356 | 0 | ppf->info.exponent_bits_per_sample = 0; |
357 | 0 | } |
358 | |
|
359 | 0 | ppf->info.orientation = JXL_ORIENT_IDENTITY; |
360 | | |
361 | | // No alpha in PNM and PFM |
362 | 0 | ppf->info.alpha_bits = (header.has_alpha ? ppf->info.bits_per_sample : 0); |
363 | 0 | ppf->info.alpha_exponent_bits = 0; |
364 | 0 | ppf->info.num_color_channels = (header.is_gray ? 1 : 3); |
365 | 0 | uint32_t num_alpha_channels = (header.has_alpha ? 1 : 0); |
366 | 0 | uint32_t num_interleaved_channels = |
367 | 0 | ppf->info.num_color_channels + num_alpha_channels; |
368 | 0 | ppf->info.num_extra_channels = num_alpha_channels + header.ec_types.size(); |
369 | |
|
370 | 0 | for (auto type : header.ec_types) { |
371 | 0 | PackedExtraChannel pec; |
372 | 0 | pec.ec_info.bits_per_sample = ppf->info.bits_per_sample; |
373 | 0 | pec.ec_info.type = type; |
374 | 0 | ppf->extra_channels_info.emplace_back(std::move(pec)); |
375 | 0 | } |
376 | |
|
377 | 0 | JxlDataType data_type; |
378 | 0 | if (header.floating_point) { |
379 | | // There's no float16 pnm version. |
380 | 0 | data_type = JXL_TYPE_FLOAT; |
381 | 0 | } else { |
382 | 0 | if (header.bits_per_sample > 8) { |
383 | 0 | data_type = JXL_TYPE_UINT16; |
384 | 0 | } else { |
385 | 0 | data_type = JXL_TYPE_UINT8; |
386 | 0 | } |
387 | 0 | } |
388 | |
|
389 | 0 | const JxlPixelFormat format{ |
390 | 0 | /*num_channels=*/num_interleaved_channels, |
391 | 0 | /*data_type=*/data_type, |
392 | 0 | /*endianness=*/header.big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN, |
393 | 0 | /*align=*/0, |
394 | 0 | }; |
395 | 0 | const JxlPixelFormat ec_format{1, format.data_type, format.endianness, 0}; |
396 | 0 | ppf->frames.clear(); |
397 | 0 | ppf->frames.emplace_back(header.xsize, header.ysize, format); |
398 | 0 | auto* frame = &ppf->frames.back(); |
399 | 0 | for (size_t i = 0; i < header.ec_types.size(); ++i) { |
400 | 0 | frame->extra_channels.emplace_back(header.xsize, header.ysize, ec_format); |
401 | 0 | } |
402 | 0 | size_t pnm_remaining_size = bytes.data() + bytes.size() - pos; |
403 | 0 | if (pnm_remaining_size < frame->color.pixels_size) { |
404 | 0 | return JXL_FAILURE("PNM file too small"); |
405 | 0 | } |
406 | | |
407 | 0 | uint8_t* out = reinterpret_cast<uint8_t*>(frame->color.pixels()); |
408 | 0 | std::vector<uint8_t*> ec_out(header.ec_types.size()); |
409 | 0 | for (size_t i = 0; i < ec_out.size(); ++i) { |
410 | 0 | ec_out[i] = reinterpret_cast<uint8_t*>(frame->extra_channels[i].pixels()); |
411 | 0 | } |
412 | 0 | if (ec_out.empty()) { |
413 | 0 | const bool flipped_y = header.bits_per_sample == 32; // PFMs are flipped |
414 | 0 | for (size_t y = 0; y < header.ysize; ++y) { |
415 | 0 | size_t y_in = flipped_y ? header.ysize - 1 - y : y; |
416 | 0 | const uint8_t* row_in = &pos[y_in * frame->color.stride]; |
417 | 0 | uint8_t* row_out = &out[y * frame->color.stride]; |
418 | 0 | memcpy(row_out, row_in, frame->color.stride); |
419 | 0 | } |
420 | 0 | } else { |
421 | 0 | size_t pwidth = PackedImage::BitsPerChannel(data_type) / 8; |
422 | 0 | for (size_t y = 0; y < header.ysize; ++y) { |
423 | 0 | for (size_t x = 0; x < header.xsize; ++x) { |
424 | 0 | memcpy(out, pos, frame->color.pixel_stride()); |
425 | 0 | out += frame->color.pixel_stride(); |
426 | 0 | pos += frame->color.pixel_stride(); |
427 | 0 | for (auto& p : ec_out) { |
428 | 0 | memcpy(p, pos, pwidth); |
429 | 0 | pos += pwidth; |
430 | 0 | p += pwidth; |
431 | 0 | } |
432 | 0 | } |
433 | 0 | } |
434 | 0 | } |
435 | 0 | return true; |
436 | 0 | } |
437 | | |
438 | 0 | void TestCodecPNM() { |
439 | 0 | size_t u = 77777; // Initialized to wrong value. |
440 | 0 | double d = 77.77; |
441 | | // Failing to parse invalid strings results in a crash if `JXL_CRASH_ON_ERROR` |
442 | | // is defined and hence the tests fail. Therefore we only run these tests if |
443 | | // `JXL_CRASH_ON_ERROR` is not defined. |
444 | 0 | #ifndef JXL_CRASH_ON_ERROR |
445 | 0 | JXL_CHECK(false == Parser(MakeSpan("")).ParseUnsigned(&u)); |
446 | 0 | JXL_CHECK(false == Parser(MakeSpan("+")).ParseUnsigned(&u)); |
447 | 0 | JXL_CHECK(false == Parser(MakeSpan("-")).ParseUnsigned(&u)); |
448 | 0 | JXL_CHECK(false == Parser(MakeSpan("A")).ParseUnsigned(&u)); |
449 | | |
450 | 0 | JXL_CHECK(false == Parser(MakeSpan("")).ParseSigned(&d)); |
451 | 0 | JXL_CHECK(false == Parser(MakeSpan("+")).ParseSigned(&d)); |
452 | 0 | JXL_CHECK(false == Parser(MakeSpan("-")).ParseSigned(&d)); |
453 | 0 | JXL_CHECK(false == Parser(MakeSpan("A")).ParseSigned(&d)); |
454 | 0 | #endif |
455 | 0 | JXL_CHECK(true == Parser(MakeSpan("1")).ParseUnsigned(&u)); |
456 | 0 | JXL_CHECK(u == 1); |
457 | | |
458 | 0 | JXL_CHECK(true == Parser(MakeSpan("32")).ParseUnsigned(&u)); |
459 | 0 | JXL_CHECK(u == 32); |
460 | | |
461 | 0 | JXL_CHECK(true == Parser(MakeSpan("1")).ParseSigned(&d)); |
462 | 0 | JXL_CHECK(d == 1.0); |
463 | 0 | JXL_CHECK(true == Parser(MakeSpan("+2")).ParseSigned(&d)); |
464 | 0 | JXL_CHECK(d == 2.0); |
465 | 0 | JXL_CHECK(true == Parser(MakeSpan("-3")).ParseSigned(&d)); |
466 | 0 | JXL_CHECK(std::abs(d - -3.0) < 1E-15); |
467 | 0 | JXL_CHECK(true == Parser(MakeSpan("3.141592")).ParseSigned(&d)); |
468 | 0 | JXL_CHECK(std::abs(d - 3.141592) < 1E-15); |
469 | 0 | JXL_CHECK(true == Parser(MakeSpan("-3.141592")).ParseSigned(&d)); |
470 | 0 | JXL_CHECK(std::abs(d - -3.141592) < 1E-15); |
471 | 0 | } |
472 | | |
473 | | } // namespace extras |
474 | | } // namespace jxl |