33 auto const bytes = as_bstring_view(_view);
34 read_header(bytes, offset);
35 read_chunks(bytes, offset);
38 [[nodiscard]]
png(std::filesystem::path
const& path) :
png(
file_view{path}) {}
53 auto const image_data_size = _stride * _height;
55 auto image_data = decompress_IDATs(image_data_size);
56 hi_check(ssize(image_data) == image_data_size,
"Uncompressed image data has incorrect size.");
58 unfilter_lines(image_data);
60 data_to_image(image_data, image);
67 png_data.decode_image(image);
77 big_uint32_buf_t length;
82 big_uint32_buf_t width;
83 big_uint32_buf_t height;
86 uint8_t compression_method;
87 uint8_t filter_method;
88 uint8_t interlace_method;
92 big_uint32_buf_t gamma;
96 big_uint32_buf_t white_point_x;
97 big_uint32_buf_t white_point_y;
98 big_uint32_buf_t red_x;
99 big_uint32_buf_t red_y;
100 big_uint32_buf_t green_x;
101 big_uint32_buf_t green_y;
102 big_uint32_buf_t blue_x;
103 big_uint32_buf_t blue_y;
107 uint8_t rendering_intent;
123 int _compression_method = 0;
124 int _filter_method = 0;
125 int _interlace_method = 0;
130 int _samples_per_pixel = 0;
131 int _bits_per_pixel = 0;
132 int _bytes_per_pixel = 0;
133 int _bytes_per_line = 0;
144 static std::string read_string(std::span<std::byte const> bytes)
148 for (
ssize_t i = 0; i != ssize(bytes); ++i) {
149 auto const c =
static_cast<char>(bytes[i]);
156 throw parse_error(
"string is not null terminated.");
159 static uint8_t paeth_predictor(uint8_t _a, uint8_t _b, uint8_t _c)
noexcept
161 auto const a =
static_cast<int>(_a);
162 auto const b =
static_cast<int>(_b);
163 auto const c =
static_cast<int>(_c);
165 auto const p = a + b - c;
166 auto const pa = std::abs(p - a);
167 auto const pb = std::abs(p - b);
168 auto const pc = std::abs(p - c);
170 if (pa <= pb && pa <= pc) {
171 return narrow_cast<uint8_t>(a);
172 }
else if (pb <= pc) {
173 return narrow_cast<uint8_t>(b);
175 return narrow_cast<uint8_t>(c);
179 static uint16_t get_sample(std::span<std::byte const> bytes,
ssize_t& offset,
bool two_bytes)
181 uint16_t value =
static_cast<uint8_t
>(bytes[offset++]);
184 value |=
static_cast<uint8_t
>(bytes[offset++]);
189 void read_header(std::span<std::byte const> bytes,
std::size_t& offset)
191 auto const png_header = make_placement_ptr<PNGHeader>(bytes, offset);
193 auto const valid_signature = png_header->signature[0] == 137 && png_header->signature[1] == 80 &&
194 png_header->signature[2] == 78 && png_header->signature[3] == 71 && png_header->signature[4] == 13 &&
195 png_header->signature[5] == 10 && png_header->signature[6] == 26 && png_header->signature[7] == 10;
197 hi_check(valid_signature,
"invalid PNG file signature");
200 void read_chunks(std::span<std::byte const> bytes,
std::size_t& offset)
202 auto IHDR_bytes = std::span<std::byte const>{};
203 auto cHRM_bytes = std::span<std::byte const>{};
204 auto gAMA_bytes = std::span<std::byte const>{};
205 auto iCCP_bytes = std::span<std::byte const>{};
206 auto sRGB_bytes = std::span<std::byte const>{};
207 bool has_IEND =
false;
210 auto const header = make_placement_ptr<ChunkHeader>(bytes, offset);
211 auto const length = narrow_cast<ssize_t>(*header->length);
212 hi_check(length < 0x8000'0000,
"Chunk length must be smaller than 2GB");
213 hi_check(offset + length + ssizeof(uint32_t) <= bytes.size(),
"Chuck extents beyond file.");
215 switch (fourcc(header->type)) {
217 _idat_chunk_data.
push_back(bytes.subspan(offset, length));
221 IHDR_bytes = bytes.subspan(offset, length);
225 cHRM_bytes = bytes.subspan(offset, length);
229 gAMA_bytes = bytes.subspan(offset, length);
233 iCCP_bytes = bytes.subspan(offset, length);
237 sRGB_bytes = bytes.subspan(offset, length);
249 [[maybe_unused]]
auto const crc = make_placement_ptr<big_uint32_buf_t>(bytes, offset);
252 hi_check(!IHDR_bytes.empty(),
"Missing IHDR chunk.");
253 read_IHDR(IHDR_bytes);
254 if (!cHRM_bytes.empty()) {
255 read_cHRM(cHRM_bytes);
257 if (!gAMA_bytes.empty()) {
258 read_gAMA(gAMA_bytes);
262 if (!iCCP_bytes.empty()) {
263 read_iCCP(iCCP_bytes);
267 if (!sRGB_bytes.empty()) {
268 read_sRGB(sRGB_bytes);
272 void read_IHDR(std::span<std::byte const> bytes)
274 auto const ihdr = make_placement_ptr<IHDR>(bytes);
276 _width = *ihdr->width;
277 _height = *ihdr->height;
278 _bit_depth = ihdr->bit_depth;
279 _color_type = ihdr->color_type;
280 _compression_method = ihdr->compression_method;
281 _filter_method = ihdr->filter_method;
282 _interlace_method = ihdr->interlace_method;
284 hi_check(_width <= 16384,
"PNG width too large.");
285 hi_check(_height <= 16384,
"PNG height too large.");
286 hi_check(_bit_depth == 8 || _bit_depth == 16,
"PNG only bit depth of 8 or 16 is implemented.");
287 hi_check(_compression_method == 0,
"Only deflate/inflate compression is allowed.");
288 hi_check(_filter_method == 0,
"Only adaptive filtering is allowed.");
289 hi_check(_interlace_method == 0,
"Only non interlaced PNG are implemented.");
291 _is_palletted = (_color_type & 1) != 0;
292 _is_color = (_color_type & 2) != 0;
293 _has_alpha = (_color_type & 4) != 0;
294 hi_check((_color_type & 0xf8) == 0,
"Invalid color type");
295 hi_check(!_is_palletted,
"Paletted images are not supported");
298 _samples_per_pixel = 1;
300 _samples_per_pixel =
static_cast<int>(_has_alpha);
301 _samples_per_pixel += _is_color ? 3 : 1;
304 _bits_per_pixel = _samples_per_pixel * _bit_depth;
305 _bytes_per_line = (_bits_per_pixel * _width + 7) / 8;
306 _stride = _bytes_per_line + 1;
307 _bytes_per_pixel =
std::max(1, _bits_per_pixel / 8);
309 generate_sRGB_transfer_function();
312 void read_cHRM(std::span<std::byte const> bytes)
314 auto const chrm = make_placement_ptr<cHRM>(bytes);
317 narrow_cast<float>(*chrm->white_point_x) / 100'000.0f,
318 narrow_cast<float>(*chrm->white_point_y) / 100'000.0f,
319 narrow_cast<float>(*chrm->red_x) / 100'000.0f,
320 narrow_cast<float>(*chrm->red_y) / 100'000.0f,
321 narrow_cast<float>(*chrm->green_x) / 100'000.0f,
322 narrow_cast<float>(*chrm->green_y) / 100'000.0f,
323 narrow_cast<float>(*chrm->blue_x) / 100'000.0f,
324 narrow_cast<float>(*chrm->blue_y) / 100'000.0f);
329 void read_gAMA(std::span<std::byte const> bytes)
331 auto const gama = make_placement_ptr<gAMA>(bytes);
332 auto const gamma = narrow_cast<float>(*gama->gamma) / 100'000.0f;
333 hi_check(gamma != 0.0f,
"Gamma value can not be zero");
335 generate_gamma_transfer_function(1.0f / gamma);
338 void read_iCCP(std::span<std::byte const> bytes)
340 auto profile_name = read_string(bytes);
342 if (profile_name ==
"ITUR_2100_PQ_FULL") {
347 generate_Rec2100_transfer_function();
352 void read_sRGB(std::span<std::byte const> bytes)
354 auto const srgb = make_placement_ptr<sRGB>(bytes);
355 auto const rendering_intent = srgb->rendering_intent;
356 hi_check(rendering_intent <= 3,
"Invalid rendering intent");
359 generate_sRGB_transfer_function();
362 void generate_sRGB_transfer_function()
noexcept
364 auto const value_range = _bit_depth == 8 ? 256 : 65536;
365 auto const value_range_f = narrow_cast<float>(value_range);
366 for (
int i = 0; i != value_range; ++i) {
367 auto u = narrow_cast<float>(i) / value_range_f;
372 void generate_Rec2100_transfer_function()
noexcept
375 constexpr float hdr_multiplier = 10'000.0f / 80.0f;
377 auto const value_range = _bit_depth == 8 ? 256 : 65536;
378 auto const value_range_f = narrow_cast<float>(value_range);
379 for (
int i = 0; i != value_range; ++i) {
380 auto u = narrow_cast<float>(i) / value_range_f;
385 void generate_gamma_transfer_function(
float gamma)
noexcept
387 auto const value_range = _bit_depth == 8 ? 256 : 65536;
388 auto const value_range_f = narrow_cast<float>(value_range);
389 for (
int i = 0; i != value_range; ++i) {
390 auto u = narrow_cast<float>(i) / value_range_f;
391 _transfer_function.
push_back(powf(u, gamma));
395 [[nodiscard]] bstring decompress_IDATs(
std::size_t image_data_size)
const
397 if (ssize(_idat_chunk_data) == 1) {
398 return zlib_decompress(_idat_chunk_data[0], image_data_size);
401 auto const compressed_data_size =
406 bstring compressed_data;
407 compressed_data.
reserve(compressed_data_size);
408 for (
auto const chunk_data : _idat_chunk_data) {
412 return zlib_decompress(compressed_data, image_data_size);
416 void unfilter_lines(bstring& image_data)
const
418 auto const image_bytes = std::span(
reinterpret_cast<uint8_t *
>(image_data.data()), image_data.size());
421 auto prev_line = std::span(zero_line.data(), zero_line.size());
422 for (
auto y = 0_uz; y != _height; ++y) {
423 auto const line = image_bytes.subspan(y * _stride, _stride);
424 unfilter_line(line, prev_line);
425 prev_line = line.subspan(1, _bytes_per_line);
429 void unfilter_line(std::span<uint8_t> line, std::span<uint8_t const> prev_line)
const
435 return unfilter_line_sub(line.subspan(1, _bytes_per_line), prev_line);
437 return unfilter_line_up(line.subspan(1, _bytes_per_line), prev_line);
439 return unfilter_line_average(line.subspan(1, _bytes_per_line), prev_line);
441 return unfilter_line_paeth(line.subspan(1, _bytes_per_line), prev_line);
447 void unfilter_line_sub(std::span<uint8_t> line, std::span<uint8_t const> prev_line)
const noexcept
449 for (
int i = 0; i != _bytes_per_line; ++i) {
450 auto const j = i - _bytes_per_pixel;
452 uint8_t prev_raw = j >= 0 ? line[j] : 0;
457 void unfilter_line_up(std::span<uint8_t> line, std::span<uint8_t const> prev_line)
const noexcept
459 for (
int i = 0; i != _bytes_per_line; ++i) {
460 line[i] += prev_line[i];
464 void unfilter_line_average(std::span<uint8_t> line, std::span<uint8_t const> prev_line)
const noexcept
466 for (
int i = 0; i != _bytes_per_line; ++i) {
467 auto const j = i - _bytes_per_pixel;
469 uint8_t prev_raw = j >= 0 ? line[j] : 0;
470 line[i] += (prev_raw + prev_line[i]) / 2;
474 void unfilter_line_paeth(std::span<uint8_t> line, std::span<uint8_t const> prev_line)
const noexcept
476 for (
int i = 0; i != _bytes_per_line; ++i) {
477 auto const j = i - _bytes_per_pixel;
479 uint8_t
const up = prev_line[i];
480 uint8_t
const left = j >= 0 ? line[j] : 0;
481 uint8_t
const left_up = j >= 0 ? prev_line[j] : 0;
482 line[i] += paeth_predictor(
left, up, left_up);
488 auto bytes_span = std::span(bytes);
490 for (
int y = 0; y != _height; ++y) {
491 int inv_y = _height - y - 1;
493 auto bytes_line = bytes_span.subspan(inv_y * _stride + 1, _bytes_per_line);
494 auto pixel_line = image[y];
495 data_to_image_line(bytes_line, pixel_line);
499 void data_to_image_line(std::span<std::byte const> bytes, std::span<sfloat_rgba16> line)
const noexcept
501 auto const alpha_mul = _bit_depth == 16 ? 1.0f / 65535.0f : 1.0f / 255.0f;
502 for (
int x = 0; x != _width; ++x) {
503 auto const value = extract_pixel_from_line(bytes, x);
505 auto const linear_RGB =
506 f32x4{_transfer_function[value.x()], _transfer_function[value.y()], _transfer_function[value.z()], 1.0f};
508 auto const linear_sRGB_color = _color_to_sRGB * linear_RGB;
509 auto const alpha =
static_cast<float>(value.w()) * alpha_mul;
512 line[x] = linear_sRGB_color * f32x4::broadcast(alpha);
516 u16x4 extract_pixel_from_line(std::span<std::byte const> bytes,
int x)
const noexcept
518 hi_axiom(_bit_depth == 8 or _bit_depth == 16);
519 hi_axiom(not _is_palletted);
526 ssize_t offset = x * _bytes_per_pixel;
528 r = get_sample(bytes, offset, _bit_depth == 16);
529 g = get_sample(bytes, offset, _bit_depth == 16);
530 b = get_sample(bytes, offset, _bit_depth == 16);
532 r = g = b = get_sample(bytes, offset, _bit_depth == 16);
535 a = get_sample(bytes, offset, _bit_depth == 16);
537 a = (_bit_depth == 16) ? 65535 : 255;
540 return u16x4{r, g, b, a};