Skip to content

Commit c7f0de1

Browse files
LepkoQQawesomekling
authored andcommitted
LibGfx: Decode paletted and grayscale images with 1/2/4 bit depth
When dealing with png data that has less than 8 bits per pixel, round up to the next byte when allocating per row buffers and streamers. This fixes decoding odd sized PNGs with less than 8 bits per pixel. Also added a test page with some odd sized palleted PNGs.
1 parent e37065c commit c7f0de1

File tree

3 files changed

+95
-32
lines changed

3 files changed

+95
-32
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<HTML>
2+
<HEAD>
3+
<TITLE>PngSuite - Odd sizes / PNG-files</TITLE>
4+
</HEAD>
5+
<BODY BGCOLOR="#ede">
6+
7+
<!-- Modified version of http://schaik.com/pngsuite/pngsuite_siz_png.html -->
8+
9+
<BR><IMG SRC="http://schaik.com/pngsuite/s01n3p01.png" WIDTH="1" HEIGHT="1"> --- s01n3p01 - 1x1 paletted file, no interlacing
10+
<BR><IMG SRC="http://schaik.com/pngsuite/s02n3p01.png" WIDTH="2" HEIGHT="2"> --- s02n3p01 - 2x2 paletted file, no interlacing
11+
<BR><IMG SRC="http://schaik.com/pngsuite/s03n3p01.png" WIDTH="3" HEIGHT="3"> --- s03n3p01 - 3x3 paletted file, no interlacing
12+
<BR><IMG SRC="http://schaik.com/pngsuite/s04n3p01.png" WIDTH="4" HEIGHT="4"> --- s04n3p01 - 4x4 paletted file, no interlacing
13+
<BR><IMG SRC="http://schaik.com/pngsuite/s05n3p02.png" WIDTH="5" HEIGHT="5"> --- s05n3p02 - 5x5 paletted file, no interlacing
14+
<BR><IMG SRC="http://schaik.com/pngsuite/s06n3p02.png" WIDTH="6" HEIGHT="6"> --- s06n3p02 - 6x6 paletted file, no interlacing
15+
<BR><IMG SRC="http://schaik.com/pngsuite/s07n3p02.png" WIDTH="7" HEIGHT="7"> --- s07n3p02 - 7x7 paletted file, no interlacing
16+
<BR><IMG SRC="http://schaik.com/pngsuite/s08n3p02.png" WIDTH="8" HEIGHT="8"> --- s08n3p02 - 8x8 paletted file, no interlacing
17+
<BR><IMG SRC="http://schaik.com/pngsuite/s09n3p02.png" WIDTH="9" HEIGHT="9"> --- s09n3p02 - 9x9 paletted file, no interlacing
18+
<BR><IMG SRC="http://schaik.com/pngsuite/s32n3p04.png" WIDTH="32" HEIGHT="32"> --- s32n3p04 - 32x32 paletted file, no interlacing
19+
<BR><IMG SRC="http://schaik.com/pngsuite/s33n3p04.png" WIDTH="33" HEIGHT="33"> --- s33n3p04 - 33x33 paletted file, no interlacing
20+
<BR><IMG SRC="http://schaik.com/pngsuite/s34n3p04.png" WIDTH="34" HEIGHT="34"> --- s34n3p04 - 34x34 paletted file, no interlacing
21+
<BR><IMG SRC="http://schaik.com/pngsuite/s35n3p04.png" WIDTH="35" HEIGHT="35"> --- s35n3p04 - 35x35 paletted file, no interlacing
22+
<BR><IMG SRC="http://schaik.com/pngsuite/s36n3p04.png" WIDTH="36" HEIGHT="36"> --- s36n3p04 - 36x36 paletted file, no interlacing
23+
<BR><IMG SRC="http://schaik.com/pngsuite/s37n3p04.png" WIDTH="37" HEIGHT="37"> --- s37n3p04 - 37x37 paletted file, no interlacing
24+
<BR><IMG SRC="http://schaik.com/pngsuite/s38n3p04.png" WIDTH="38" HEIGHT="38"> --- s38n3p04 - 38x38 paletted file, no interlacing
25+
<BR><IMG SRC="http://schaik.com/pngsuite/s39n3p04.png" WIDTH="39" HEIGHT="39"> --- s39n3p04 - 39x39 paletted file, no interlacing
26+
<BR><IMG SRC="http://schaik.com/pngsuite/s40n3p04.png" WIDTH="40" HEIGHT="40"> --- s40n3p04 - 40x40 paletted file, no interlacing
27+
28+
</BODY>
29+
</HTML>
30+

Base/home/anon/www/welcome.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ <h1>Welcome to the Serenity Browser!</h1>
2828
<p>Your user agent is: <b><span id="ua"></span></b></p>
2929
<p>Some small test pages:</p>
3030
<ul>
31+
<li><a href="pngsuite_siz_png.html">pngsuite odd sizes test</a></li>
3132
<li><a href="pngsuite_bas_png.html">pngsuite basic formats test</a></li>
3233
<li><a href="canvas-path.html">canvas path house!</a></li>
3334
<li><a href="img-canvas.html">canvas drawImage() test</a></li>

Libraries/LibGfx/PNGLoader.cpp

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <AK/NetworkOrdered.h>
3131
#include <LibCore/puff.h>
3232
#include <LibGfx/PNGLoader.h>
33+
#include <LibM/math.h>
3334
#include <fcntl.h>
3435
#include <serenity.h>
3536
#include <stdio.h>
@@ -120,7 +121,7 @@ struct PNGLoadingContext {
120121
u8 compression_method { 0 };
121122
u8 filter_method { 0 };
122123
u8 interlace_method { 0 };
123-
u8 bytes_per_pixel { 0 };
124+
u8 channels { 0 };
124125
bool has_seen_zlib_header { false };
125126
bool has_alpha() const { return color_type & 4 || palette_transparency_data.size() > 0; }
126127
Vector<Scanline> scanlines;
@@ -339,6 +340,21 @@ NEVER_INLINE FLATTEN static void unfilter(PNGLoadingContext& context)
339340
pixel.a = 0xff;
340341
}
341342
}
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+
}
342358
} else {
343359
ASSERT_NOT_REACHED();
344360
}
@@ -418,19 +434,42 @@ NEVER_INLINE FLATTEN static void unfilter(PNGLoadingContext& context)
418434
}
419435
break;
420436
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+
}
433451
}
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();
434473
}
435474
break;
436475
default:
@@ -586,7 +625,8 @@ static bool decode_png_bitmap(PNGLoadingContext& context)
586625

587626
context.scanlines.append({ filter });
588627
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)) {
590630
context.state = PNGLoadingContext::State::Error;
591631
return false;
592632
}
@@ -648,36 +688,28 @@ static bool process_IHDR(const ByteBuffer& data, PNGLoadingContext& context, boo
648688

649689
switch (context.color_type) {
650690
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;
657692
break;
658693
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;
660695
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;
663698
break;
664699
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;
671701
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;
674704
break;
675705
default:
676706
ASSERT_NOT_REACHED();
677707
}
678708

679709
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;
681713
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");
682714
}
683715
return true;

0 commit comments

Comments
 (0)