mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-23 09:22:30 +00:00
LibGfx: Implement support for decoding interlaced PNGs
This adds support for decoding the Adam7 interlacing used in some PNGs. Notably this includes many of the images (such as the eyes) used in the acid2 test :^) Note that the HTML engine still doesn't understand the <object> tag well enough to show the eyes on the test.
This commit is contained in:
parent
76553f9f06
commit
b6147de1cb
Notes:
sideshowbarker
2024-07-19 05:40:03 +09:00
Author: https://github.com/MegabytePhreak
Commit: b6147de1cb
Pull-request: https://github.com/SerenityOS/serenity/pull/2554
1 changed files with 172 additions and 35 deletions
|
@ -94,6 +94,11 @@ struct [[gnu::packed]] Quad
|
||||||
T a;
|
T a;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum PngInterlaceMethod {
|
||||||
|
Null = 0,
|
||||||
|
Adam7 = 1
|
||||||
|
};
|
||||||
|
|
||||||
struct PNGLoadingContext {
|
struct PNGLoadingContext {
|
||||||
enum State {
|
enum State {
|
||||||
NotDecoded = 0,
|
NotDecoded = 0,
|
||||||
|
@ -172,7 +177,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
static RefPtr<Gfx::Bitmap> load_png_impl(const u8*, size_t);
|
static RefPtr<Gfx::Bitmap> load_png_impl(const u8*, size_t);
|
||||||
static bool process_chunk(Streamer&, PNGLoadingContext& context, bool decode_size_only);
|
static bool process_chunk(Streamer&, PNGLoadingContext& context);
|
||||||
|
|
||||||
RefPtr<Gfx::Bitmap> load_png(const StringView& path)
|
RefPtr<Gfx::Bitmap> load_png(const StringView& path)
|
||||||
{
|
{
|
||||||
|
@ -538,7 +543,7 @@ static bool decode_png_size(PNGLoadingContext& context)
|
||||||
|
|
||||||
Streamer streamer(data_ptr, data_remaining);
|
Streamer streamer(data_ptr, data_remaining);
|
||||||
while (!streamer.at_end()) {
|
while (!streamer.at_end()) {
|
||||||
if (!process_chunk(streamer, context, true)) {
|
if (!process_chunk(streamer, context)) {
|
||||||
context.state = PNGLoadingContext::State::Error;
|
context.state = PNGLoadingContext::State::Error;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -568,7 +573,7 @@ static bool decode_png_chunks(PNGLoadingContext& context)
|
||||||
|
|
||||||
Streamer streamer(data_ptr, data_remaining);
|
Streamer streamer(data_ptr, data_remaining);
|
||||||
while (!streamer.at_end()) {
|
while (!streamer.at_end()) {
|
||||||
if (!process_chunk(streamer, context, false)) {
|
if (!process_chunk(streamer, context)) {
|
||||||
context.state = PNGLoadingContext::State::Error;
|
context.state = PNGLoadingContext::State::Error;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -578,27 +583,10 @@ static bool decode_png_chunks(PNGLoadingContext& context)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool decode_png_bitmap(PNGLoadingContext& context)
|
static bool decode_png_bitmap_simple(PNGLoadingContext& context)
|
||||||
{
|
{
|
||||||
if (context.state < PNGLoadingContext::State::ChunksDecoded) {
|
|
||||||
if (!decode_png_chunks(context))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.state >= PNGLoadingContext::State::BitmapDecoded)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
unsigned long srclen = context.compressed_data.size() - 6;
|
|
||||||
unsigned long destlen = context.decompression_buffer_size;
|
|
||||||
int ret = puff(context.decompression_buffer, &destlen, context.compressed_data.data() + 2, &srclen);
|
|
||||||
if (ret < 0) {
|
|
||||||
context.state = PNGLoadingContext::State::Error;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
context.compressed_data.clear();
|
|
||||||
|
|
||||||
context.scanlines.ensure_capacity(context.height);
|
|
||||||
Streamer streamer(context.decompression_buffer, context.decompression_buffer_size);
|
Streamer streamer(context.decompression_buffer, context.decompression_buffer_size);
|
||||||
|
|
||||||
for (int y = 0; y < context.height; ++y) {
|
for (int y = 0; y < context.height; ++y) {
|
||||||
u8 filter;
|
u8 filter;
|
||||||
if (!streamer.read(filter)) {
|
if (!streamer.read(filter)) {
|
||||||
|
@ -625,6 +613,163 @@ static bool decode_png_bitmap(PNGLoadingContext& context)
|
||||||
|
|
||||||
unfilter(context);
|
unfilter(context);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int adam7_height(PNGLoadingContext& context, int pass)
|
||||||
|
{
|
||||||
|
switch (pass) {
|
||||||
|
case 1:
|
||||||
|
return (context.height + 7) / 8;
|
||||||
|
case 2:
|
||||||
|
return (context.height + 7) / 8;
|
||||||
|
case 3:
|
||||||
|
return (context.height + 3) / 8;
|
||||||
|
case 4:
|
||||||
|
return (context.height + 3) / 4;
|
||||||
|
case 5:
|
||||||
|
return (context.height + 1) / 4;
|
||||||
|
case 6:
|
||||||
|
return (context.height + 1) / 2;
|
||||||
|
case 7:
|
||||||
|
return context.height / 2;
|
||||||
|
default:
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int adam7_width(PNGLoadingContext& context, int pass)
|
||||||
|
{
|
||||||
|
switch (pass) {
|
||||||
|
case 1:
|
||||||
|
return (context.width + 7) / 8;
|
||||||
|
case 2:
|
||||||
|
return (context.width + 3) / 8;
|
||||||
|
case 3:
|
||||||
|
return (context.width + 3) / 4;
|
||||||
|
case 4:
|
||||||
|
return (context.width + 1) / 4;
|
||||||
|
case 5:
|
||||||
|
return (context.width + 1) / 2;
|
||||||
|
case 6:
|
||||||
|
return context.width / 2;
|
||||||
|
case 7:
|
||||||
|
return context.width;
|
||||||
|
default:
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index 0 unused (non-interlaced case)
|
||||||
|
static int adam7_starty[8] = { 0, 0, 0, 4, 0, 2, 0, 1 };
|
||||||
|
static int adam7_startx[8] = { 0, 0, 4, 0, 2, 0, 1, 0 };
|
||||||
|
static int adam7_stepy[8] = { 1, 8, 8, 8, 4, 4, 2, 2 };
|
||||||
|
static int adam7_stepx[8] = { 1, 8, 8, 4, 4, 2, 2, 1 };
|
||||||
|
|
||||||
|
static bool decode_adam7_pass(PNGLoadingContext& context, Streamer& streamer, int pass)
|
||||||
|
{
|
||||||
|
PNGLoadingContext subimage_context;
|
||||||
|
subimage_context.width = adam7_width(context, pass);
|
||||||
|
subimage_context.height = adam7_height(context, pass);
|
||||||
|
subimage_context.channels = context.channels;
|
||||||
|
subimage_context.color_type = context.color_type;
|
||||||
|
subimage_context.palette_data = context.palette_data;
|
||||||
|
subimage_context.palette_transparency_data = context.palette_transparency_data;
|
||||||
|
subimage_context.bit_depth = context.bit_depth;
|
||||||
|
subimage_context.filter_method = context.filter_method;
|
||||||
|
|
||||||
|
// For small images, some passes might be empty
|
||||||
|
if (!subimage_context.width || !subimage_context.height)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
subimage_context.scanlines.clear_with_capacity();
|
||||||
|
for (int y = 0; y < subimage_context.height; ++y) {
|
||||||
|
u8 filter;
|
||||||
|
if (!streamer.read(filter)) {
|
||||||
|
context.state = PNGLoadingContext::State::Error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter > 4) {
|
||||||
|
dbg() << "Invalid PNG filter: " << filter;
|
||||||
|
context.state = PNGLoadingContext::State::Error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
subimage_context.scanlines.append({ filter });
|
||||||
|
auto& scanline_buffer = subimage_context.scanlines.last().data;
|
||||||
|
auto row_size = ((subimage_context.width * context.channels * context.bit_depth) + 7) / 8;
|
||||||
|
if (!streamer.wrap_bytes(scanline_buffer, row_size)) {
|
||||||
|
context.state = PNGLoadingContext::State::Error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subimage_context.bitmap = Bitmap::create(context.bitmap->format(), { subimage_context.width, subimage_context.height });
|
||||||
|
unfilter(subimage_context);
|
||||||
|
|
||||||
|
// Copy the subimage data into the main image according to the pass pattern
|
||||||
|
for (int y = 0, dy = adam7_starty[pass]; y < subimage_context.height && dy < context.height; ++y, dy += adam7_stepy[pass]) {
|
||||||
|
for (int x = 0, dx = adam7_startx[pass]; x < subimage_context.width && dy < context.width; ++x, dx += adam7_stepx[pass]) {
|
||||||
|
context.bitmap->set_pixel(dx, dy, subimage_context.bitmap->get_pixel(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool decode_png_adam7(PNGLoadingContext& context)
|
||||||
|
{
|
||||||
|
Streamer streamer(context.decompression_buffer, context.decompression_buffer_size);
|
||||||
|
context.bitmap = Bitmap::create_purgeable(context.has_alpha() ? BitmapFormat::RGBA32 : BitmapFormat::RGB32, { context.width, context.height });
|
||||||
|
|
||||||
|
for (int pass = 1; pass <= 7; ++pass) {
|
||||||
|
if (!decode_adam7_pass(context, streamer, pass))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool decode_png_bitmap(PNGLoadingContext& context)
|
||||||
|
{
|
||||||
|
if (context.state < PNGLoadingContext::State::ChunksDecoded) {
|
||||||
|
if (!decode_png_chunks(context))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.state >= PNGLoadingContext::State::BitmapDecoded)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
unsigned long srclen = context.compressed_data.size() - 6;
|
||||||
|
unsigned long destlen = 0;
|
||||||
|
int ret = puff(NULL, &destlen, context.compressed_data.data() + 2, &srclen);
|
||||||
|
if (ret != 0) {
|
||||||
|
context.state = PNGLoadingContext::State::Error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
context.decompression_buffer_size = destlen;
|
||||||
|
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");
|
||||||
|
|
||||||
|
ret = puff(context.decompression_buffer, &destlen, context.compressed_data.data() + 2, &srclen);
|
||||||
|
if (ret != 0) {
|
||||||
|
context.state = PNGLoadingContext::State::Error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
context.compressed_data.clear();
|
||||||
|
|
||||||
|
context.scanlines.ensure_capacity(context.height);
|
||||||
|
switch (context.interlace_method) {
|
||||||
|
case PngInterlaceMethod::Null:
|
||||||
|
if (!decode_png_bitmap_simple(context))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case PngInterlaceMethod::Adam7:
|
||||||
|
if (!decode_png_adam7(context))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
munmap(context.decompression_buffer, context.decompression_buffer_size);
|
munmap(context.decompression_buffer, context.decompression_buffer_size);
|
||||||
context.decompression_buffer = nullptr;
|
context.decompression_buffer = nullptr;
|
||||||
context.decompression_buffer_size = 0;
|
context.decompression_buffer_size = 0;
|
||||||
|
@ -648,7 +793,7 @@ static RefPtr<Gfx::Bitmap> load_png_impl(const u8* data, size_t data_size)
|
||||||
return context.bitmap;
|
return context.bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool process_IHDR(const ByteBuffer& data, PNGLoadingContext& context, bool decode_size_only = false)
|
static bool process_IHDR(const ByteBuffer& data, PNGLoadingContext& context)
|
||||||
{
|
{
|
||||||
if (data.size() < (int)sizeof(PNG_IHDR))
|
if (data.size() < (int)sizeof(PNG_IHDR))
|
||||||
return false;
|
return false;
|
||||||
|
@ -669,9 +814,8 @@ static bool process_IHDR(const ByteBuffer& data, PNGLoadingContext& context, boo
|
||||||
printf(" Interlace type: %d\n", context.interlace_method);
|
printf(" Interlace type: %d\n", context.interlace_method);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// FIXME: Implement Adam7 deinterlacing
|
if (context.interlace_method != PngInterlaceMethod::Null && context.interlace_method != PngInterlaceMethod::Adam7) {
|
||||||
if (context.interlace_method != 0) {
|
dbgprintf("PNGLoader::process_IHDR: unknown interlace method: %d\n", context.interlace_method);
|
||||||
dbgprintf("PNGLoader::process_IHDR: Interlaced PNGs not currently supported.\n");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -694,13 +838,6 @@ static bool process_IHDR(const ByteBuffer& data, PNGLoadingContext& context, boo
|
||||||
default:
|
default:
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!decode_size_only) {
|
|
||||||
// Calculate number of bytes per row (+1 for filter)
|
|
||||||
auto row_size = ((context.width * context.channels * context.bit_depth) + 7) / 8 + 1;
|
|
||||||
context.decompression_buffer_size = row_size * context.height;
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -726,7 +863,7 @@ static bool process_tRNS(const ByteBuffer& data, PNGLoadingContext& context)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool process_chunk(Streamer& streamer, PNGLoadingContext& context, bool decode_size_only)
|
static bool process_chunk(Streamer& streamer, PNGLoadingContext& context)
|
||||||
{
|
{
|
||||||
u32 chunk_size;
|
u32 chunk_size;
|
||||||
if (!streamer.read(chunk_size)) {
|
if (!streamer.read(chunk_size)) {
|
||||||
|
@ -754,7 +891,7 @@ static bool process_chunk(Streamer& streamer, PNGLoadingContext& context, bool d
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!strcmp((const char*)chunk_type, "IHDR"))
|
if (!strcmp((const char*)chunk_type, "IHDR"))
|
||||||
return process_IHDR(chunk_data, context, decode_size_only);
|
return process_IHDR(chunk_data, context);
|
||||||
if (!strcmp((const char*)chunk_type, "IDAT"))
|
if (!strcmp((const char*)chunk_type, "IDAT"))
|
||||||
return process_IDAT(chunk_data, context);
|
return process_IDAT(chunk_data, context);
|
||||||
if (!strcmp((const char*)chunk_type, "PLTE"))
|
if (!strcmp((const char*)chunk_type, "PLTE"))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue