|
30 | 30 | #include <AK/NetworkOrdered.h>
|
31 | 31 | #include <LibCore/puff.h>
|
32 | 32 | #include <LibGfx/PNGLoader.h>
|
| 33 | +#include <LibM/math.h> |
33 | 34 | #include <fcntl.h>
|
34 | 35 | #include <serenity.h>
|
35 | 36 | #include <stdio.h>
|
@@ -120,7 +121,7 @@ struct PNGLoadingContext {
|
120 | 121 | u8 compression_method { 0 };
|
121 | 122 | u8 filter_method { 0 };
|
122 | 123 | u8 interlace_method { 0 };
|
123 |
| - u8 bytes_per_pixel { 0 }; |
| 124 | + u8 channels { 0 }; |
124 | 125 | bool has_seen_zlib_header { false };
|
125 | 126 | bool has_alpha() const { return color_type & 4 || palette_transparency_data.size() > 0; }
|
126 | 127 | Vector<Scanline> scanlines;
|
@@ -339,6 +340,21 @@ NEVER_INLINE FLATTEN static void unfilter(PNGLoadingContext& context)
|
339 | 340 | pixel.a = 0xff;
|
340 | 341 | }
|
341 | 342 | }
|
| 343 | + } else if (context.bit_depth == 1 || context.bit_depth == 2 || context.bit_depth == 4) { |
| 344 | + auto pixels_per_byte = 8 / context.bit_depth; |
| 345 | + auto mask = (1 << context.bit_depth) - 1; |
| 346 | + for (int y = 0; y < context.height; ++y) { |
| 347 | + auto* gray_values = (u8*)context.scanlines[y].data.data(); |
| 348 | + for (int i = 0; i < context.width; ++i) { |
| 349 | + auto bit_offset = (8 - context.bit_depth) - (context.bit_depth * (i % pixels_per_byte)); |
| 350 | + auto value = (gray_values[i / pixels_per_byte] >> bit_offset) & mask; |
| 351 | + auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; |
| 352 | + pixel.r = value * (0xff / pow(context.bit_depth, 2)); |
| 353 | + pixel.g = value * (0xff / pow(context.bit_depth, 2)); |
| 354 | + pixel.b = value * (0xff / pow(context.bit_depth, 2)); |
| 355 | + pixel.a = 0xff; |
| 356 | + } |
| 357 | + } |
342 | 358 | } else {
|
343 | 359 | ASSERT_NOT_REACHED();
|
344 | 360 | }
|
@@ -418,19 +434,42 @@ NEVER_INLINE FLATTEN static void unfilter(PNGLoadingContext& context)
|
418 | 434 | }
|
419 | 435 | break;
|
420 | 436 | case 3:
|
421 |
| - for (int y = 0; y < context.height; ++y) { |
422 |
| - auto* palette_index = (u8*)context.scanlines[y].data.data(); |
423 |
| - for (int i = 0; i < context.width; ++i) { |
424 |
| - auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; |
425 |
| - auto& color = context.palette_data.at((int)palette_index[i]); |
426 |
| - auto transparency = context.palette_transparency_data.size() >= palette_index[i] + 1u |
427 |
| - ? context.palette_transparency_data.data()[palette_index[i]] |
428 |
| - : 0xff; |
429 |
| - pixel.r = color.r; |
430 |
| - pixel.g = color.g; |
431 |
| - pixel.b = color.b; |
432 |
| - pixel.a = transparency; |
| 437 | + if (context.bit_depth == 8) { |
| 438 | + for (int y = 0; y < context.height; ++y) { |
| 439 | + auto* palette_index = (u8*)context.scanlines[y].data.data(); |
| 440 | + for (int i = 0; i < context.width; ++i) { |
| 441 | + auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; |
| 442 | + auto& color = context.palette_data.at((int)palette_index[i]); |
| 443 | + auto transparency = context.palette_transparency_data.size() >= palette_index[i] + 1u |
| 444 | + ? context.palette_transparency_data.data()[palette_index[i]] |
| 445 | + : 0xff; |
| 446 | + pixel.r = color.r; |
| 447 | + pixel.g = color.g; |
| 448 | + pixel.b = color.b; |
| 449 | + pixel.a = transparency; |
| 450 | + } |
433 | 451 | }
|
| 452 | + } else if (context.bit_depth == 1 || context.bit_depth == 2 || context.bit_depth == 4) { |
| 453 | + auto pixels_per_byte = 8 / context.bit_depth; |
| 454 | + auto mask = (1 << context.bit_depth) - 1; |
| 455 | + for (int y = 0; y < context.height; ++y) { |
| 456 | + auto* palette_indexes = (u8*)context.scanlines[y].data.data(); |
| 457 | + for (int i = 0; i < context.width; ++i) { |
| 458 | + auto bit_offset = (8 - context.bit_depth) - (context.bit_depth * (i % pixels_per_byte)); |
| 459 | + auto palette_index = (palette_indexes[i / pixels_per_byte] >> bit_offset) & mask; |
| 460 | + auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; |
| 461 | + auto& color = context.palette_data.at(palette_index); |
| 462 | + auto transparency = context.palette_transparency_data.size() >= palette_index + 1u |
| 463 | + ? context.palette_transparency_data.data()[palette_index] |
| 464 | + : 0xff; |
| 465 | + pixel.r = color.r; |
| 466 | + pixel.g = color.g; |
| 467 | + pixel.b = color.b; |
| 468 | + pixel.a = transparency; |
| 469 | + } |
| 470 | + } |
| 471 | + } else { |
| 472 | + ASSERT_NOT_REACHED(); |
434 | 473 | }
|
435 | 474 | break;
|
436 | 475 | default:
|
@@ -586,7 +625,8 @@ static bool decode_png_bitmap(PNGLoadingContext& context)
|
586 | 625 |
|
587 | 626 | context.scanlines.append({ filter });
|
588 | 627 | auto& scanline_buffer = context.scanlines.last().data;
|
589 |
| - if (!streamer.wrap_bytes(scanline_buffer, context.width * context.bytes_per_pixel)) { |
| 628 | + auto row_size = ((context.width * context.channels * context.bit_depth) + 7) / 8; |
| 629 | + if (!streamer.wrap_bytes(scanline_buffer, row_size)) { |
590 | 630 | context.state = PNGLoadingContext::State::Error;
|
591 | 631 | return false;
|
592 | 632 | }
|
@@ -648,36 +688,28 @@ static bool process_IHDR(const ByteBuffer& data, PNGLoadingContext& context, boo
|
648 | 688 |
|
649 | 689 | switch (context.color_type) {
|
650 | 690 | case 0: // Each pixel is a grayscale sample.
|
651 |
| - // FIXME: Implement support for 1/2/4 bit grayscale based images. |
652 |
| - if (ihdr.bit_depth != 8 && ihdr.bit_depth != 16) { |
653 |
| - dbgprintf("PNGLoader::process_IHDR: Unsupported grayscale format (%d bpp).\n", context.bit_depth); |
654 |
| - return false; |
655 |
| - } |
656 |
| - context.bytes_per_pixel = ihdr.bit_depth / 8; |
| 691 | + context.channels = 1; |
657 | 692 | break;
|
658 | 693 | case 4: // Each pixel is a grayscale sample, followed by an alpha sample.
|
659 |
| - context.bytes_per_pixel = 2 * ihdr.bit_depth / 8; |
| 694 | + context.channels = 2; |
660 | 695 | break;
|
661 |
| - case 2: |
662 |
| - context.bytes_per_pixel = 3 * (ihdr.bit_depth / 8); |
| 696 | + case 2: // Each pixel is an RGB sample |
| 697 | + context.channels = 3; |
663 | 698 | break;
|
664 | 699 | case 3: // Each pixel is a palette index; a PLTE chunk must appear.
|
665 |
| - // FIXME: Implement support for 1/2/4 bit palette based images. |
666 |
| - if (ihdr.bit_depth != 8) { |
667 |
| - dbgprintf("PNGLoader::process_IHDR: Unsupported index-based format (%d bpp).\n", context.bit_depth); |
668 |
| - return false; |
669 |
| - } |
670 |
| - context.bytes_per_pixel = 1; |
| 700 | + context.channels = 1; |
671 | 701 | break;
|
672 |
| - case 6: |
673 |
| - context.bytes_per_pixel = 4 * (ihdr.bit_depth / 8); |
| 702 | + case 6: // Each pixel is an RGB sample, followed by an alpha sample. |
| 703 | + context.channels = 4; |
674 | 704 | break;
|
675 | 705 | default:
|
676 | 706 | ASSERT_NOT_REACHED();
|
677 | 707 | }
|
678 | 708 |
|
679 | 709 | if (!decode_size_only) {
|
680 |
| - context.decompression_buffer_size = (context.width * context.height * context.bytes_per_pixel + context.height); |
| 710 | + // Calculate number of bytes per row (+1 for filter) |
| 711 | + auto row_size = ((context.width * context.channels * context.bit_depth) + 7) / 8 + 1; |
| 712 | + context.decompression_buffer_size = row_size * context.height; |
681 | 713 | context.decompression_buffer = (u8*)mmap_with_name(nullptr, context.decompression_buffer_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0, "PNG decompression buffer");
|
682 | 714 | }
|
683 | 715 | return true;
|
|
0 commit comments