LibGfx: Re-structure the whole initialization pattern for image decoders

When trying to figure out the correct implementation, we now have a very
strong distinction on plugins that are well suited for sniffing, and
plugins that need a MIME type to be chosen.

Instead of having multiple calls to non-static virtual sniff methods for
each Image decoding plugin, we have 2 static methods for each
implementation:
1. The sniff method, which in contrast to the old method, gets a
    ReadonlyBytes parameter and ensures we can figure out the result
    with zero heap allocations for most implementations.
2. The create method, which just creates a new instance so we don't
    expose the constructor to everyone anymore.

In addition to that, we have a new virtual method called initialize,
which has a per-implementation initialization pattern to actually ensure
each implementation can construct a decoder object, and then have a
correct context being applied to it for the actual decoding.
This commit is contained in:
Liav A 2023-01-20 10:13:14 +02:00 committed by Linus Groh
parent 6e6999ce57
commit 57e19a7e56
Notes: sideshowbarker 2024-07-17 01:53:23 +09:00
33 changed files with 493 additions and 206 deletions

View File

@ -9,7 +9,11 @@
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{ {
Gfx::BMPImageDecoderPlugin decoder(data, size); auto decoder_or_error = Gfx::BMPImageDecoderPlugin::create({ data, size });
(void)decoder.frame(0); if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
decoder->initialize();
(void)decoder->frame(0);
return 0; return 0;
} }

View File

@ -13,14 +13,18 @@
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{ {
Gfx::GIFImageDecoderPlugin gif_decoder(data, size); auto decoder_or_error = Gfx::GIFImageDecoderPlugin::create({ data, size });
auto bitmap_or_error = gif_decoder.frame(0); if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
decoder->initialize();
auto& gif_decoder = *decoder;
auto bitmap_or_error = decoder->frame(0);
if (!bitmap_or_error.is_error()) { if (!bitmap_or_error.is_error()) {
auto const& bitmap = bitmap_or_error.value().image; auto const& bitmap = bitmap_or_error.value().image;
// Looks like a valid GIF. Try to load the other frames: // Looks like a valid GIF. Try to load the other frames:
dbgln_if(GIF_DEBUG, "bitmap size: {}", bitmap->size()); dbgln_if(GIF_DEBUG, "bitmap size: {}", bitmap->size());
dbgln_if(GIF_DEBUG, "codec size: {}", gif_decoder.size()); dbgln_if(GIF_DEBUG, "codec size: {}", gif_decoder.size());
dbgln_if(GIF_DEBUG, "is_sniff: {}", gif_decoder.sniff());
dbgln_if(GIF_DEBUG, "is_animated: {}", gif_decoder.is_animated()); dbgln_if(GIF_DEBUG, "is_animated: {}", gif_decoder.is_animated());
dbgln_if(GIF_DEBUG, "loop_count: {}", gif_decoder.loop_count()); dbgln_if(GIF_DEBUG, "loop_count: {}", gif_decoder.loop_count());
dbgln_if(GIF_DEBUG, "frame_count: {}", gif_decoder.frame_count()); dbgln_if(GIF_DEBUG, "frame_count: {}", gif_decoder.frame_count());

View File

@ -10,7 +10,11 @@
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{ {
Gfx::ICOImageDecoderPlugin decoder(data, size); auto decoder_or_error = Gfx::ICOImageDecoderPlugin::create({ data, size });
(void)decoder.frame(0); if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
decoder->initialize();
(void)decoder->frame(0);
return 0; return 0;
} }

View File

@ -10,7 +10,11 @@
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{ {
Gfx::JPGImageDecoderPlugin decoder(data, size); auto decoder_or_error = Gfx::JPGImageDecoderPlugin::create({ data, size });
(void)decoder.frame(0); if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
decoder->initialize();
(void)decoder->frame(0);
return 0; return 0;
} }

View File

@ -10,7 +10,11 @@
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{ {
Gfx::PBMImageDecoderPlugin decoder(data, size); auto decoder_or_error = Gfx::PBMImageDecoderPlugin::create({ data, size });
(void)decoder.frame(0); if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
decoder->initialize();
(void)decoder->frame(0);
return 0; return 0;
} }

View File

@ -10,7 +10,11 @@
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{ {
Gfx::PGMImageDecoderPlugin decoder(data, size); auto decoder_or_error = Gfx::PGMImageDecoderPlugin::create({ data, size });
(void)decoder.frame(0); if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
decoder->initialize();
(void)decoder->frame(0);
return 0; return 0;
} }

View File

@ -10,7 +10,11 @@
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{ {
Gfx::PNGImageDecoderPlugin decoder(data, size); auto decoder_or_error = Gfx::PNGImageDecoderPlugin::create({ data, size });
(void)decoder.frame(0); if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
decoder->initialize();
(void)decoder->frame(0);
return 0; return 0;
} }

View File

@ -10,7 +10,11 @@
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{ {
Gfx::PPMImageDecoderPlugin decoder(data, size); auto decoder_or_error = Gfx::PPMImageDecoderPlugin::create({ data, size });
(void)decoder.frame(0); if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
decoder->initialize();
(void)decoder->frame(0);
return 0; return 0;
} }

View File

@ -10,7 +10,11 @@
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{ {
Gfx::QOIImageDecoderPlugin decoder(data, size); auto decoder_or_error = Gfx::QOIImageDecoderPlugin::create({ data, size });
(void)decoder.frame(0); if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
decoder->initialize();
(void)decoder->frame(0);
return 0; return 0;
} }

View File

@ -10,7 +10,11 @@
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{ {
Gfx::TGAImageDecoderPlugin decoder(data, size); auto decoder_or_error = Gfx::TGAImageDecoderPlugin::create({ data, size });
(void)decoder.frame(0); if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
decoder->initialize();
(void)decoder->frame(0);
return 0; return 0;
} }

View File

@ -24,138 +24,180 @@
TEST_CASE(test_bmp) TEST_CASE(test_bmp)
{ {
auto file = Core::MappedFile::map("/res/html/misc/bmpsuite_files/rgba32-1.bmp"sv).release_value(); auto file = Core::MappedFile::map("/res/html/misc/bmpsuite_files/rgba32-1.bmp"sv).release_value();
auto bmp = Gfx::BMPImageDecoderPlugin((u8 const*)file->data(), file->size()); EXPECT_EQ(MUST(Gfx::BMPImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
EXPECT(bmp.frame_count()); auto plugin_decoder_or_error = Gfx::BMPImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(bmp.sniff()); EXPECT(plugin_decoder->frame_count());
EXPECT(!bmp.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!bmp.loop_count()); EXPECT(!plugin_decoder->loop_count());
auto frame = bmp.frame(0).release_value_but_fixme_should_propagate_errors(); auto frame = plugin_decoder->frame(0).release_value_but_fixme_should_propagate_errors();
EXPECT(frame.duration == 0); EXPECT(frame.duration == 0);
} }
TEST_CASE(test_gif) TEST_CASE(test_gif)
{ {
auto file = Core::MappedFile::map("/res/graphics/download-animation.gif"sv).release_value(); auto file = Core::MappedFile::map("/res/graphics/download-animation.gif"sv).release_value();
auto gif = Gfx::GIFImageDecoderPlugin((u8 const*)file->data(), file->size()); EXPECT_EQ(MUST(Gfx::GIFImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
EXPECT(gif.frame_count()); auto plugin_decoder_or_error = Gfx::GIFImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(gif.sniff()); EXPECT(plugin_decoder->frame_count());
EXPECT(gif.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!gif.loop_count()); EXPECT(!plugin_decoder->loop_count());
auto frame = gif.frame(1).release_value_but_fixme_should_propagate_errors(); auto frame = plugin_decoder->frame(1).release_value_but_fixme_should_propagate_errors();
EXPECT(frame.duration == 400); EXPECT(frame.duration == 400);
} }
TEST_CASE(test_not_ico) TEST_CASE(test_not_ico)
{ {
auto file = Core::MappedFile::map("/res/graphics/buggie.png"sv).release_value(); auto file = Core::MappedFile::map("/res/graphics/buggie.png"sv).release_value();
auto ico = Gfx::ICOImageDecoderPlugin((u8 const*)file->data(), file->size()); EXPECT_EQ(MUST(Gfx::ICOImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
EXPECT(ico.frame_count()); auto plugin_decoder_or_error = Gfx::ICOImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(!ico.sniff()); EXPECT(plugin_decoder->frame_count());
EXPECT(!ico.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!ico.loop_count()); EXPECT(!plugin_decoder->loop_count());
EXPECT(ico.frame(0).is_error()); EXPECT(plugin_decoder->frame(0).is_error());
} }
TEST_CASE(test_bmp_embedded_in_ico) TEST_CASE(test_bmp_embedded_in_ico)
{ {
auto file = Core::MappedFile::map("/res/icons/16x16/serenity.ico"sv).release_value(); auto file = Core::MappedFile::map("/res/icons/16x16/serenity.ico"sv).release_value();
auto ico = Gfx::ICOImageDecoderPlugin((u8 const*)file->data(), file->size()); EXPECT_EQ(MUST(Gfx::ICOImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
EXPECT(ico.frame_count()); auto plugin_decoder_or_error = Gfx::ICOImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(ico.sniff()); EXPECT(plugin_decoder->frame_count());
EXPECT(!ico.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!ico.loop_count()); EXPECT(!plugin_decoder->loop_count());
EXPECT(!ico.frame(0).is_error()); EXPECT(!plugin_decoder->frame(0).is_error());
} }
TEST_CASE(test_jpg) TEST_CASE(test_jpg)
{ {
auto file = Core::MappedFile::map("/res/html/misc/bmpsuite_files/rgb24.jpg"sv).release_value(); auto file = Core::MappedFile::map("/res/html/misc/bmpsuite_files/rgb24.jpg"sv).release_value();
auto jpg = Gfx::JPGImageDecoderPlugin((u8 const*)file->data(), file->size()); EXPECT_EQ(MUST(Gfx::JPGImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
EXPECT(jpg.frame_count()); auto plugin_decoder_or_error = Gfx::JPGImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(jpg.sniff()); EXPECT(plugin_decoder->frame_count());
EXPECT(!jpg.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!jpg.loop_count()); EXPECT(!plugin_decoder->loop_count());
auto frame = jpg.frame(0).release_value_but_fixme_should_propagate_errors(); EXPECT(!plugin_decoder->frame(0).is_error());
auto frame = plugin_decoder->frame(0).release_value_but_fixme_should_propagate_errors();
EXPECT(frame.duration == 0); EXPECT(frame.duration == 0);
} }
TEST_CASE(test_pbm) TEST_CASE(test_pbm)
{ {
auto file = Core::MappedFile::map("/res/html/misc/pbmsuite_files/buggie-raw.pbm"sv).release_value(); auto file = Core::MappedFile::map("/res/html/misc/pbmsuite_files/buggie-raw.pbm"sv).release_value();
auto pbm = Gfx::PBMImageDecoderPlugin((u8 const*)file->data(), file->size()); EXPECT_EQ(MUST(Gfx::PBMImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
EXPECT(pbm.frame_count()); auto plugin_decoder_or_error = Gfx::PBMImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(pbm.sniff()); EXPECT(plugin_decoder->frame_count());
EXPECT(!pbm.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!pbm.loop_count()); EXPECT(!plugin_decoder->loop_count());
auto frame = pbm.frame(0).release_value_but_fixme_should_propagate_errors(); EXPECT(!plugin_decoder->frame(0).is_error());
auto frame = plugin_decoder->frame(0).release_value_but_fixme_should_propagate_errors();
EXPECT(frame.duration == 0); EXPECT(frame.duration == 0);
} }
TEST_CASE(test_pgm) TEST_CASE(test_pgm)
{ {
auto file = Core::MappedFile::map("/res/html/misc/pgmsuite_files/buggie-raw.pgm"sv).release_value(); auto file = Core::MappedFile::map("/res/html/misc/pgmsuite_files/buggie-raw.pgm"sv).release_value();
auto pgm = Gfx::PGMImageDecoderPlugin((u8 const*)file->data(), file->size()); EXPECT_EQ(MUST(Gfx::PGMImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
EXPECT(pgm.frame_count()); auto plugin_decoder_or_error = Gfx::PGMImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(pgm.sniff()); EXPECT(plugin_decoder->frame_count());
EXPECT(!pgm.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!pgm.loop_count()); EXPECT(!plugin_decoder->loop_count());
auto frame = pgm.frame(0).release_value_but_fixme_should_propagate_errors(); EXPECT(!plugin_decoder->frame(0).is_error());
auto frame = plugin_decoder->frame(0).release_value_but_fixme_should_propagate_errors();
EXPECT(frame.duration == 0); EXPECT(frame.duration == 0);
} }
TEST_CASE(test_png) TEST_CASE(test_png)
{ {
auto file = Core::MappedFile::map("/res/graphics/buggie.png"sv).release_value(); auto file = Core::MappedFile::map("/res/graphics/buggie.png"sv).release_value();
auto png = Gfx::PNGImageDecoderPlugin((u8 const*)file->data(), file->size()); EXPECT_EQ(MUST(Gfx::PNGImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
EXPECT(png.frame_count()); auto plugin_decoder_or_error = Gfx::PNGImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(png.sniff()); EXPECT(plugin_decoder->frame_count());
EXPECT(!png.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!png.loop_count()); EXPECT(!plugin_decoder->loop_count());
auto frame = png.frame(0).release_value_but_fixme_should_propagate_errors(); EXPECT(!plugin_decoder->frame(0).is_error());
auto frame = plugin_decoder->frame(0).release_value_but_fixme_should_propagate_errors();
EXPECT(frame.duration == 0); EXPECT(frame.duration == 0);
} }
TEST_CASE(test_ppm) TEST_CASE(test_ppm)
{ {
auto file = Core::MappedFile::map("/res/html/misc/ppmsuite_files/buggie-raw.ppm"sv).release_value(); auto file = Core::MappedFile::map("/res/html/misc/ppmsuite_files/buggie-raw.ppm"sv).release_value();
auto ppm = Gfx::PPMImageDecoderPlugin((u8 const*)file->data(), file->size()); EXPECT_EQ(MUST(Gfx::PPMImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
EXPECT(ppm.frame_count()); auto plugin_decoder_or_error = Gfx::PPMImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(ppm.sniff()); EXPECT(plugin_decoder->frame_count());
EXPECT(!ppm.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!ppm.loop_count()); EXPECT(!plugin_decoder->loop_count());
auto frame = ppm.frame(0).release_value_but_fixme_should_propagate_errors(); EXPECT(!plugin_decoder->frame(0).is_error());
auto frame = plugin_decoder->frame(0).release_value_but_fixme_should_propagate_errors();
EXPECT(frame.duration == 0); EXPECT(frame.duration == 0);
} }
TEST_CASE(test_targa_bottom_left) TEST_CASE(test_targa_bottom_left)
{ {
auto file = Core::MappedFile::map("/res/html/misc/targasuite_files/buggie-bottom-left-uncompressed.tga"sv).release_value(); auto file = Core::MappedFile::map("/res/html/misc/targasuite_files/buggie-bottom-left-uncompressed.tga"sv).release_value();
auto tga = Gfx::TGAImageDecoderPlugin(reinterpret_cast<u8 const*>(file->data()), file->size()); EXPECT_EQ(MUST(Gfx::TGAImageDecoderPlugin::validate_before_create({ (u8 const*)file->data(), file->size() })), true);
EXPECT_EQ(tga.frame_count(), 1u); auto plugin_decoder_or_error = Gfx::TGAImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(tga.sniff()); EXPECT_EQ(plugin_decoder->frame_count(), 1u);
EXPECT(!tga.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!tga.loop_count()); EXPECT(!plugin_decoder->loop_count());
auto frame_or_error = tga.frame(0); EXPECT(!plugin_decoder->frame(0).is_error());
auto frame_or_error = plugin_decoder->frame(0);
EXPECT(!frame_or_error.is_error()); EXPECT(!frame_or_error.is_error());
auto frame = frame_or_error.release_value(); auto frame = frame_or_error.release_value();
EXPECT(frame.duration == 0); EXPECT(frame.duration == 0);
@ -164,14 +206,19 @@ TEST_CASE(test_targa_bottom_left)
TEST_CASE(test_targa_top_left) TEST_CASE(test_targa_top_left)
{ {
auto file = Core::MappedFile::map("/res/html/misc/targasuite_files/buggie-top-left-uncompressed.tga"sv).release_value(); auto file = Core::MappedFile::map("/res/html/misc/targasuite_files/buggie-top-left-uncompressed.tga"sv).release_value();
auto tga = Gfx::TGAImageDecoderPlugin(reinterpret_cast<u8 const*>(file->data()), file->size()); EXPECT_EQ(MUST(Gfx::TGAImageDecoderPlugin::validate_before_create({ (u8 const*)file->data(), file->size() })), true);
EXPECT_EQ(tga.frame_count(), 1u); auto plugin_decoder_or_error = Gfx::TGAImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(tga.sniff()); EXPECT_EQ(plugin_decoder->frame_count(), 1u);
EXPECT(!tga.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!tga.loop_count()); EXPECT(!plugin_decoder->loop_count());
auto frame_or_error = tga.frame(0); EXPECT(!plugin_decoder->frame(0).is_error());
auto frame_or_error = plugin_decoder->frame(0);
EXPECT(!frame_or_error.is_error()); EXPECT(!frame_or_error.is_error());
auto frame = frame_or_error.release_value(); auto frame = frame_or_error.release_value();
EXPECT(frame.duration == 0); EXPECT(frame.duration == 0);
@ -180,14 +227,19 @@ TEST_CASE(test_targa_top_left)
TEST_CASE(test_targa_bottom_left_compressed) TEST_CASE(test_targa_bottom_left_compressed)
{ {
auto file = Core::MappedFile::map("/res/html/misc/targasuite_files/buggie-bottom-left-compressed.tga"sv).release_value(); auto file = Core::MappedFile::map("/res/html/misc/targasuite_files/buggie-bottom-left-compressed.tga"sv).release_value();
auto tga = Gfx::TGAImageDecoderPlugin(reinterpret_cast<u8 const*>(file->data()), file->size()); EXPECT_EQ(MUST(Gfx::TGAImageDecoderPlugin::validate_before_create({ (u8 const*)file->data(), file->size() })), true);
EXPECT_EQ(tga.frame_count(), 1u); auto plugin_decoder_or_error = Gfx::TGAImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(tga.sniff()); EXPECT_EQ(plugin_decoder->frame_count(), 1u);
EXPECT(!tga.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!tga.loop_count()); EXPECT(!plugin_decoder->loop_count());
auto frame_or_error = tga.frame(0); EXPECT(!plugin_decoder->frame(0).is_error());
auto frame_or_error = plugin_decoder->frame(0);
EXPECT(!frame_or_error.is_error()); EXPECT(!frame_or_error.is_error());
auto frame = frame_or_error.release_value(); auto frame = frame_or_error.release_value();
EXPECT(frame.duration == 0); EXPECT(frame.duration == 0);
@ -196,14 +248,19 @@ TEST_CASE(test_targa_bottom_left_compressed)
TEST_CASE(test_targa_top_left_compressed) TEST_CASE(test_targa_top_left_compressed)
{ {
auto file = Core::MappedFile::map("/res/html/misc/targasuite_files/buggie-top-left-compressed.tga"sv).release_value(); auto file = Core::MappedFile::map("/res/html/misc/targasuite_files/buggie-top-left-compressed.tga"sv).release_value();
auto tga = Gfx::TGAImageDecoderPlugin(reinterpret_cast<u8 const*>(file->data()), file->size()); EXPECT_EQ(MUST(Gfx::TGAImageDecoderPlugin::validate_before_create({ (u8 const*)file->data(), file->size() })), true);
EXPECT_EQ(tga.frame_count(), 1u); auto plugin_decoder_or_error = Gfx::TGAImageDecoderPlugin::create({ (u8 const*)file->data(), file->size() });
EXPECT(!plugin_decoder_or_error.is_error());
auto plugin_decoder = plugin_decoder_or_error.release_value();
EXPECT_EQ(plugin_decoder->initialize(), true);
EXPECT(tga.sniff()); EXPECT_EQ(plugin_decoder->frame_count(), 1u);
EXPECT(!tga.is_animated()); EXPECT(!plugin_decoder->is_animated());
EXPECT(!tga.loop_count()); EXPECT(!plugin_decoder->loop_count());
auto frame_or_error = tga.frame(0); EXPECT(!plugin_decoder->frame(0).is_error());
auto frame_or_error = plugin_decoder->frame(0);
EXPECT(!frame_or_error.is_error()); EXPECT(!frame_or_error.is_error());
auto frame = frame_or_error.release_value(); auto frame = frame_or_error.release_value();
EXPECT(frame.duration == 0); EXPECT(frame.duration == 0);

View File

@ -210,9 +210,14 @@ Icon FileIconProvider::icon_for_executable(DeprecatedString const& path)
bitmap = s_executable_icon.bitmap_for_size(icon_section.image_size); bitmap = s_executable_icon.bitmap_for_size(icon_section.image_size);
} else { } else {
// FIXME: Use the ImageDecoder service. // FIXME: Use the ImageDecoder service.
auto frame_or_error = Gfx::PNGImageDecoderPlugin(reinterpret_cast<u8 const*>(section->raw_data()), section->size()).frame(0); if (Gfx::PNGImageDecoderPlugin::sniff({ section->raw_data(), section->size() }).release_value_but_fixme_should_propagate_errors()) {
if (!frame_or_error.is_error()) { auto png_decoder = Gfx::PNGImageDecoderPlugin::create({ section->raw_data(), section->size() }).release_value_but_fixme_should_propagate_errors();
bitmap = frame_or_error.value().image; if (png_decoder->initialize()) {
auto frame_or_error = png_decoder->frame(0);
if (!frame_or_error.is_error()) {
bitmap = frame_or_error.value().image;
}
}
} }
} }

View File

@ -1400,12 +1400,12 @@ static ErrorOr<void> decode_bmp_pixel_data(BMPLoadingContext& context)
return {}; return {};
} }
BMPImageDecoderPlugin::BMPImageDecoderPlugin(u8 const* data, size_t data_size, bool is_included_in_ico) BMPImageDecoderPlugin::BMPImageDecoderPlugin(u8 const* data, size_t data_size, IncludedInICO is_included_in_ico)
{ {
m_context = make<BMPLoadingContext>(); m_context = make<BMPLoadingContext>();
m_context->file_bytes = data; m_context->file_bytes = data;
m_context->file_size = data_size; m_context->file_size = data_size;
m_context->is_included_in_ico = is_included_in_ico; m_context->is_included_in_ico = (is_included_in_ico == IncludedInICO::Yes);
} }
BMPImageDecoderPlugin::~BMPImageDecoderPlugin() = default; BMPImageDecoderPlugin::~BMPImageDecoderPlugin() = default;
@ -1434,11 +1434,29 @@ bool BMPImageDecoderPlugin::set_nonvolatile(bool& was_purged)
return m_context->bitmap->set_nonvolatile(was_purged); return m_context->bitmap->set_nonvolatile(was_purged);
} }
bool BMPImageDecoderPlugin::sniff() bool BMPImageDecoderPlugin::initialize()
{ {
return !decode_bmp_header(*m_context).is_error(); return !decode_bmp_header(*m_context).is_error();
} }
ErrorOr<bool> BMPImageDecoderPlugin::sniff(ReadonlyBytes data)
{
BMPLoadingContext context;
context.file_bytes = data.data();
context.file_size = data.size();
return !decode_bmp_header(context).is_error();
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> BMPImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) BMPImageDecoderPlugin(data.data(), data.size()));
}
ErrorOr<NonnullOwnPtr<BMPImageDecoderPlugin>> BMPImageDecoderPlugin::create_as_included_in_ico(Badge<ICOImageDecoderPlugin>, ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) BMPImageDecoderPlugin(data.data(), data.size(), IncludedInICO::Yes));
}
bool BMPImageDecoderPlugin::sniff_dib() bool BMPImageDecoderPlugin::sniff_dib()
{ {
return !decode_bmp_dib(*m_context).is_error(); return !decode_bmp_dib(*m_context).is_error();

View File

@ -6,21 +6,31 @@
#pragma once #pragma once
#include <LibGfx/ICOLoader.h>
#include <LibGfx/ImageDecoder.h> #include <LibGfx/ImageDecoder.h>
namespace Gfx { namespace Gfx {
struct BMPLoadingContext; struct BMPLoadingContext;
class ICOImageDecoderPlugin;
class BMPImageDecoderPlugin final : public ImageDecoderPlugin { class BMPImageDecoderPlugin final : public ImageDecoderPlugin {
public: public:
static ErrorOr<bool> sniff(ReadonlyBytes);
static ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> create(ReadonlyBytes);
static ErrorOr<NonnullOwnPtr<BMPImageDecoderPlugin>> create_as_included_in_ico(Badge<ICOImageDecoderPlugin>, ReadonlyBytes);
enum class IncludedInICO {
Yes,
No,
};
virtual ~BMPImageDecoderPlugin() override; virtual ~BMPImageDecoderPlugin() override;
BMPImageDecoderPlugin(u8 const*, size_t, bool is_included_in_ico = false);
virtual IntSize size() override; virtual IntSize size() override;
virtual void set_volatile() override; virtual void set_volatile() override;
[[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override;
virtual bool sniff() override; virtual bool initialize() override;
bool sniff_dib(); bool sniff_dib();
virtual bool is_animated() override; virtual bool is_animated() override;
virtual size_t loop_count() override; virtual size_t loop_count() override;
@ -28,6 +38,8 @@ public:
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override; virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override;
private: private:
BMPImageDecoderPlugin(u8 const*, size_t, IncludedInICO included_in_ico = IncludedInICO::No);
OwnPtr<BMPLoadingContext> m_context; OwnPtr<BMPLoadingContext> m_context;
}; };

View File

@ -968,7 +968,7 @@ bool DDSImageDecoderPlugin::set_nonvolatile(bool& was_purged)
return m_context->bitmap->set_nonvolatile(was_purged); return m_context->bitmap->set_nonvolatile(was_purged);
} }
bool DDSImageDecoderPlugin::sniff() bool DDSImageDecoderPlugin::initialize()
{ {
// The header is always at least 128 bytes, so if the file is smaller, it can't be a DDS. // The header is always at least 128 bytes, so if the file is smaller, it can't be a DDS.
return m_context->data_size > 128 return m_context->data_size > 128
@ -978,6 +978,21 @@ bool DDSImageDecoderPlugin::sniff()
&& m_context->data[3] == 0x20; && m_context->data[3] == 0x20;
} }
ErrorOr<bool> DDSImageDecoderPlugin::sniff(ReadonlyBytes data)
{
// The header is always at least 128 bytes, so if the file is smaller, it can't be a DDS.
return data.size() > 128
&& data.data()[0] == 0x44
&& data.data()[1] == 0x44
&& data.data()[2] == 0x53
&& data.data()[3] == 0x20;
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> DDSImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) DDSImageDecoderPlugin(data.data(), data.size()));
}
bool DDSImageDecoderPlugin::is_animated() bool DDSImageDecoderPlugin::is_animated()
{ {
return false; return false;

View File

@ -235,19 +235,23 @@ struct DDSLoadingContext;
class DDSImageDecoderPlugin final : public ImageDecoderPlugin { class DDSImageDecoderPlugin final : public ImageDecoderPlugin {
public: public:
static ErrorOr<bool> sniff(ReadonlyBytes);
static ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> create(ReadonlyBytes);
virtual ~DDSImageDecoderPlugin() override; virtual ~DDSImageDecoderPlugin() override;
DDSImageDecoderPlugin(u8 const*, size_t);
virtual IntSize size() override; virtual IntSize size() override;
virtual void set_volatile() override; virtual void set_volatile() override;
[[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override;
virtual bool sniff() override; virtual bool initialize() override;
virtual bool is_animated() override; virtual bool is_animated() override;
virtual size_t loop_count() override; virtual size_t loop_count() override;
virtual size_t frame_count() override; virtual size_t frame_count() override;
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override; virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override;
private: private:
DDSImageDecoderPlugin(u8 const*, size_t);
OwnPtr<DDSLoadingContext> m_context; OwnPtr<DDSLoadingContext> m_context;
void dump_debug(); void dump_debug();
}; };

View File

@ -614,12 +614,23 @@ bool GIFImageDecoderPlugin::set_nonvolatile(bool& was_purged)
return m_context->frame_buffer->set_nonvolatile(was_purged); return m_context->frame_buffer->set_nonvolatile(was_purged);
} }
bool GIFImageDecoderPlugin::sniff() bool GIFImageDecoderPlugin::initialize()
{ {
InputMemoryStream stream { { m_context->data, m_context->data_size } }; InputMemoryStream stream { { m_context->data, m_context->data_size } };
return !decode_gif_header(stream).is_error(); return !decode_gif_header(stream).is_error();
} }
ErrorOr<bool> GIFImageDecoderPlugin::sniff(ReadonlyBytes data)
{
InputMemoryStream stream { { data.data(), data.size() } };
return !decode_gif_header(stream).is_error();
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> GIFImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) GIFImageDecoderPlugin(data.data(), data.size()));
}
bool GIFImageDecoderPlugin::is_animated() bool GIFImageDecoderPlugin::is_animated()
{ {
if (m_context->error_state != GIFLoadingContext::ErrorState::NoError) { if (m_context->error_state != GIFLoadingContext::ErrorState::NoError) {

View File

@ -15,19 +15,23 @@ struct GIFLoadingContext;
class GIFImageDecoderPlugin final : public ImageDecoderPlugin { class GIFImageDecoderPlugin final : public ImageDecoderPlugin {
public: public:
static ErrorOr<bool> sniff(ReadonlyBytes);
static ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> create(ReadonlyBytes);
virtual ~GIFImageDecoderPlugin() override; virtual ~GIFImageDecoderPlugin() override;
GIFImageDecoderPlugin(u8 const*, size_t);
virtual IntSize size() override; virtual IntSize size() override;
virtual void set_volatile() override; virtual void set_volatile() override;
[[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override;
virtual bool sniff() override; virtual bool initialize() override;
virtual bool is_animated() override; virtual bool is_animated() override;
virtual size_t loop_count() override; virtual size_t loop_count() override;
virtual size_t frame_count() override; virtual size_t frame_count() override;
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override; virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override;
private: private:
GIFImageDecoderPlugin(u8 const*, size_t);
OwnPtr<GIFLoadingContext> m_context; OwnPtr<GIFLoadingContext> m_context;
}; };

View File

@ -136,7 +136,7 @@ static bool load_ico_directory(ICOLoadingContext& context)
return true; return true;
} }
static bool load_ico_bitmap(ICOLoadingContext& context, Optional<size_t> index) bool ICOImageDecoderPlugin::load_ico_bitmap(ICOLoadingContext& context, Optional<size_t> index)
{ {
if (context.state < ICOLoadingContext::State::DirectoryDecoded) { if (context.state < ICOLoadingContext::State::DirectoryDecoded) {
if (!load_ico_directory(context)) { if (!load_ico_directory(context)) {
@ -153,20 +153,25 @@ static bool load_ico_bitmap(ICOLoadingContext& context, Optional<size_t> index)
} }
ICOImageDescriptor& desc = context.images[real_index]; ICOImageDescriptor& desc = context.images[real_index];
if (PNGImageDecoderPlugin::sniff({ context.data + desc.offset, desc.size }).release_value_but_fixme_should_propagate_errors()) {
PNGImageDecoderPlugin png_decoder(context.data + desc.offset, desc.size); auto png_decoder = PNGImageDecoderPlugin::create({ context.data + desc.offset, desc.size }).release_value_but_fixme_should_propagate_errors();
if (png_decoder.sniff()) { if (png_decoder->initialize()) {
auto decoded_png_frame = png_decoder.frame(0); auto decoded_png_frame = png_decoder->frame(0);
if (decoded_png_frame.is_error() || !decoded_png_frame.value().image) { if (decoded_png_frame.is_error() || !decoded_png_frame.value().image) {
dbgln_if(ICO_DEBUG, "load_ico_bitmap: failed to load PNG encoded image index: {}", real_index); dbgln_if(ICO_DEBUG, "load_ico_bitmap: failed to load PNG encoded image index: {}", real_index);
return false; return false;
}
desc.bitmap = decoded_png_frame.value().image;
return true;
} }
desc.bitmap = decoded_png_frame.value().image; return false;
return true;
} else { } else {
BMPImageDecoderPlugin bmp_decoder(context.data + desc.offset, desc.size, true); auto bmp_decoder = BMPImageDecoderPlugin::create_as_included_in_ico({}, { context.data + desc.offset, desc.size }).release_value_but_fixme_should_propagate_errors();
if (bmp_decoder.sniff_dib()) { // NOTE: We don't initialize a BMP decoder in the usual way, but rather
auto decoded_bmp_frame = bmp_decoder.frame(0); // we just create an object and try to sniff for a frame when it's included
// inside an ICO image.
if (bmp_decoder->sniff_dib()) {
auto decoded_bmp_frame = bmp_decoder->frame(0);
if (decoded_bmp_frame.is_error() || !decoded_bmp_frame.value().image) { if (decoded_bmp_frame.is_error() || !decoded_bmp_frame.value().image) {
dbgln_if(ICO_DEBUG, "load_ico_bitmap: failed to load BMP encoded image index: {}", real_index); dbgln_if(ICO_DEBUG, "load_ico_bitmap: failed to load BMP encoded image index: {}", real_index);
return false; return false;
@ -180,6 +185,17 @@ static bool load_ico_bitmap(ICOLoadingContext& context, Optional<size_t> index)
} }
} }
ErrorOr<bool> ICOImageDecoderPlugin::sniff(ReadonlyBytes data)
{
InputMemoryStream stream { { data.data(), data.size() } };
return decode_ico_header(stream).has_value();
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> ICOImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) ICOImageDecoderPlugin(data.data(), data.size()));
}
ICOImageDecoderPlugin::ICOImageDecoderPlugin(u8 const* data, size_t size) ICOImageDecoderPlugin::ICOImageDecoderPlugin(u8 const* data, size_t size)
{ {
m_context = make<ICOLoadingContext>(); m_context = make<ICOLoadingContext>();
@ -219,7 +235,7 @@ bool ICOImageDecoderPlugin::set_nonvolatile(bool& was_purged)
return m_context->images[0].bitmap->set_nonvolatile(was_purged); return m_context->images[0].bitmap->set_nonvolatile(was_purged);
} }
bool ICOImageDecoderPlugin::sniff() bool ICOImageDecoderPlugin::initialize()
{ {
InputMemoryStream stream { { m_context->data, m_context->data_size } }; InputMemoryStream stream { { m_context->data, m_context->data_size } };
return decode_ico_header(stream).has_value(); return decode_ico_header(stream).has_value();

View File

@ -14,19 +14,24 @@ struct ICOLoadingContext;
class ICOImageDecoderPlugin final : public ImageDecoderPlugin { class ICOImageDecoderPlugin final : public ImageDecoderPlugin {
public: public:
static ErrorOr<bool> sniff(ReadonlyBytes);
static ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> create(ReadonlyBytes);
virtual ~ICOImageDecoderPlugin() override; virtual ~ICOImageDecoderPlugin() override;
ICOImageDecoderPlugin(u8 const*, size_t);
virtual IntSize size() override; virtual IntSize size() override;
virtual void set_volatile() override; virtual void set_volatile() override;
[[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override;
virtual bool sniff() override; virtual bool initialize() override;
virtual bool is_animated() override; virtual bool is_animated() override;
virtual size_t loop_count() override; virtual size_t loop_count() override;
virtual size_t frame_count() override; virtual size_t frame_count() override;
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override; virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override;
private: private:
ICOImageDecoderPlugin(u8 const*, size_t);
static bool load_ico_bitmap(ICOLoadingContext& context, Optional<size_t> index);
OwnPtr<ICOLoadingContext> m_context; OwnPtr<ICOLoadingContext> m_context;
}; };

View File

@ -20,64 +20,58 @@
namespace Gfx { namespace Gfx {
struct ImagePluginInitializer {
ErrorOr<bool> (*sniff)(ReadonlyBytes) = nullptr;
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> (*create)(ReadonlyBytes) = nullptr;
};
static constexpr ImagePluginInitializer s_initializers[] = {
{ PNGImageDecoderPlugin::sniff, PNGImageDecoderPlugin::create },
{ GIFImageDecoderPlugin::sniff, GIFImageDecoderPlugin::create },
{ BMPImageDecoderPlugin::sniff, BMPImageDecoderPlugin::create },
{ PBMImageDecoderPlugin::sniff, PBMImageDecoderPlugin::create },
{ PGMImageDecoderPlugin::sniff, PGMImageDecoderPlugin::create },
{ PPMImageDecoderPlugin::sniff, PPMImageDecoderPlugin::create },
{ ICOImageDecoderPlugin::sniff, ICOImageDecoderPlugin::create },
{ JPGImageDecoderPlugin::sniff, JPGImageDecoderPlugin::create },
{ DDSImageDecoderPlugin::sniff, DDSImageDecoderPlugin::create },
{ QOIImageDecoderPlugin::sniff, QOIImageDecoderPlugin::create },
};
struct ImagePluginWithMIMETypeInitializer {
ErrorOr<bool> (*validate_before_create)(ReadonlyBytes) = nullptr;
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> (*create)(ReadonlyBytes) = nullptr;
StringView mime_type;
};
static constexpr ImagePluginWithMIMETypeInitializer s_initializers_with_mime_type[] = {
{ TGAImageDecoderPlugin::validate_before_create, TGAImageDecoderPlugin::create, "image/x-targa"sv },
};
static OwnPtr<ImageDecoderPlugin> probe_and_sniff_for_appropriate_plugin(ReadonlyBytes bytes) static OwnPtr<ImageDecoderPlugin> probe_and_sniff_for_appropriate_plugin(ReadonlyBytes bytes)
{ {
auto* data = bytes.data(); for (auto& plugin : s_initializers) {
auto size = bytes.size(); auto sniff_result = plugin.sniff(bytes).release_value_but_fixme_should_propagate_errors();
OwnPtr<ImageDecoderPlugin> plugin; if (!sniff_result)
continue;
plugin = make<PNGImageDecoderPlugin>(data, size); auto plugin_decoder = plugin.create(bytes).release_value_but_fixme_should_propagate_errors();
if (plugin->sniff()) if (plugin_decoder->initialize())
return plugin; return plugin_decoder;
}
plugin = make<GIFImageDecoderPlugin>(data, size);
if (plugin->sniff())
return plugin;
plugin = make<BMPImageDecoderPlugin>(data, size);
if (plugin->sniff())
return plugin;
plugin = make<PBMImageDecoderPlugin>(data, size);
if (plugin->sniff())
return plugin;
plugin = make<PGMImageDecoderPlugin>(data, size);
if (plugin->sniff())
return plugin;
plugin = make<PPMImageDecoderPlugin>(data, size);
if (plugin->sniff())
return plugin;
plugin = make<ICOImageDecoderPlugin>(data, size);
if (plugin->sniff())
return plugin;
plugin = make<JPGImageDecoderPlugin>(data, size);
if (plugin->sniff())
return plugin;
plugin = make<DDSImageDecoderPlugin>(data, size);
if (plugin->sniff())
return plugin;
plugin = make<QOIImageDecoderPlugin>(data, size);
if (plugin->sniff())
return plugin;
return {}; return {};
} }
static OwnPtr<ImageDecoderPlugin> probe_and_sniff_for_appropriate_plugin_with_known_mime_type(StringView mime_type, ReadonlyBytes bytes) static OwnPtr<ImageDecoderPlugin> probe_and_sniff_for_appropriate_plugin_with_known_mime_type(StringView mime_type, ReadonlyBytes bytes)
{ {
auto* data = bytes.data(); for (auto& plugin : s_initializers_with_mime_type) {
auto size = bytes.size(); if (plugin.mime_type != mime_type)
OwnPtr<ImageDecoderPlugin> plugin; continue;
if (mime_type == "image/x-targa"sv) { auto validation_result = plugin.validate_before_create(bytes).release_value_but_fixme_should_propagate_errors();
plugin = make<TGAImageDecoderPlugin>(data, size); if (!validation_result)
if (plugin->sniff()) continue;
return plugin; auto plugin_decoder = plugin.create(bytes).release_value_but_fixme_should_propagate_errors();
if (plugin_decoder->initialize())
return plugin_decoder;
} }
return {}; return {};
} }

View File

@ -34,7 +34,7 @@ public:
virtual void set_volatile() = 0; virtual void set_volatile() = 0;
[[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) = 0; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) = 0;
virtual bool sniff() = 0; virtual bool initialize() = 0;
virtual bool is_animated() = 0; virtual bool is_animated() = 0;
virtual size_t loop_count() = 0; virtual size_t loop_count() = 0;
@ -55,7 +55,6 @@ public:
int height() const { return size().height(); } int height() const { return size().height(); }
void set_volatile() { m_plugin->set_volatile(); } void set_volatile() { m_plugin->set_volatile(); }
[[nodiscard]] bool set_nonvolatile(bool& was_purged) { return m_plugin->set_nonvolatile(was_purged); } [[nodiscard]] bool set_nonvolatile(bool& was_purged) { return m_plugin->set_nonvolatile(was_purged); }
bool sniff() const { return m_plugin->sniff(); }
bool is_animated() const { return m_plugin->is_animated(); } bool is_animated() const { return m_plugin->is_animated(); }
size_t loop_count() const { return m_plugin->loop_count(); } size_t loop_count() const { return m_plugin->loop_count(); }
size_t frame_count() const { return m_plugin->frame_count(); } size_t frame_count() const { return m_plugin->frame_count(); }

View File

@ -1179,12 +1179,22 @@ bool JPGImageDecoderPlugin::set_nonvolatile(bool& was_purged)
return m_context->bitmap->set_nonvolatile(was_purged); return m_context->bitmap->set_nonvolatile(was_purged);
} }
bool JPGImageDecoderPlugin::sniff() bool JPGImageDecoderPlugin::initialize()
{ {
return m_context->data_size > 3 return true;
&& m_context->data[0] == 0xFF }
&& m_context->data[1] == 0xD8
&& m_context->data[2] == 0xFF; ErrorOr<bool> JPGImageDecoderPlugin::sniff(ReadonlyBytes data)
{
return data.size() > 3
&& data.data()[0] == 0xFF
&& data.data()[1] == 0xD8
&& data.data()[2] == 0xFF;
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JPGImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) JPGImageDecoderPlugin(data.data(), data.size()));
} }
bool JPGImageDecoderPlugin::is_animated() bool JPGImageDecoderPlugin::is_animated()

View File

@ -14,18 +14,22 @@ struct JPGLoadingContext;
class JPGImageDecoderPlugin : public ImageDecoderPlugin { class JPGImageDecoderPlugin : public ImageDecoderPlugin {
public: public:
static ErrorOr<bool> sniff(ReadonlyBytes);
static ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> create(ReadonlyBytes);
virtual ~JPGImageDecoderPlugin() override; virtual ~JPGImageDecoderPlugin() override;
JPGImageDecoderPlugin(u8 const*, size_t);
virtual IntSize size() override; virtual IntSize size() override;
virtual void set_volatile() override; virtual void set_volatile() override;
[[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override;
virtual bool sniff() override; virtual bool initialize() override;
virtual bool is_animated() override; virtual bool is_animated() override;
virtual size_t loop_count() override; virtual size_t loop_count() override;
virtual size_t frame_count() override; virtual size_t frame_count() override;
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override; virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override;
private: private:
JPGImageDecoderPlugin(u8 const*, size_t);
OwnPtr<JPGLoadingContext> m_context; OwnPtr<JPGLoadingContext> m_context;
}; };
} }

View File

@ -913,11 +913,24 @@ bool PNGImageDecoderPlugin::set_nonvolatile(bool& was_purged)
return m_context->bitmap->set_nonvolatile(was_purged); return m_context->bitmap->set_nonvolatile(was_purged);
} }
bool PNGImageDecoderPlugin::sniff() bool PNGImageDecoderPlugin::initialize()
{ {
return decode_png_header(*m_context); return decode_png_header(*m_context);
} }
ErrorOr<bool> PNGImageDecoderPlugin::sniff(ReadonlyBytes data)
{
PNGLoadingContext context;
context.data = data.data();
context.data_size = data.size();
return decode_png_header(context);
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> PNGImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) PNGImageDecoderPlugin(data.data(), data.size()));
}
bool PNGImageDecoderPlugin::is_animated() bool PNGImageDecoderPlugin::is_animated()
{ {
return false; return false;

View File

@ -14,19 +14,23 @@ struct PNGLoadingContext;
class PNGImageDecoderPlugin final : public ImageDecoderPlugin { class PNGImageDecoderPlugin final : public ImageDecoderPlugin {
public: public:
static ErrorOr<bool> sniff(ReadonlyBytes);
static ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> create(ReadonlyBytes);
virtual ~PNGImageDecoderPlugin() override; virtual ~PNGImageDecoderPlugin() override;
PNGImageDecoderPlugin(u8 const*, size_t);
virtual IntSize size() override; virtual IntSize size() override;
virtual void set_volatile() override; virtual void set_volatile() override;
[[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override;
virtual bool sniff() override; virtual bool initialize() override;
virtual bool is_animated() override; virtual bool is_animated() override;
virtual size_t loop_count() override; virtual size_t loop_count() override;
virtual size_t frame_count() override; virtual size_t frame_count() override;
virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override; virtual ErrorOr<ImageFrameDescriptor> frame(size_t index) override;
private: private:
PNGImageDecoderPlugin(u8 const*, size_t);
OwnPtr<PNGLoadingContext> m_context; OwnPtr<PNGLoadingContext> m_context;
}; };

View File

@ -49,6 +49,9 @@ struct PortableImageMapLoadingContext {
template<typename TContext> template<typename TContext>
class PortableImageDecoderPlugin final : public ImageDecoderPlugin { class PortableImageDecoderPlugin final : public ImageDecoderPlugin {
public: public:
static ErrorOr<bool> sniff(ReadonlyBytes);
static ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> create(ReadonlyBytes);
PortableImageDecoderPlugin(u8 const*, size_t); PortableImageDecoderPlugin(u8 const*, size_t);
virtual ~PortableImageDecoderPlugin() override = default; virtual ~PortableImageDecoderPlugin() override = default;
@ -57,8 +60,7 @@ public:
virtual void set_volatile() override; virtual void set_volatile() override;
[[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override;
virtual bool sniff() override; virtual bool initialize() override;
virtual bool is_animated() override; virtual bool is_animated() override;
virtual size_t loop_count() override; virtual size_t loop_count() override;
virtual size_t frame_count() override; virtual size_t frame_count() override;
@ -108,7 +110,7 @@ bool PortableImageDecoderPlugin<TContext>::set_nonvolatile(bool& was_purged)
} }
template<typename TContext> template<typename TContext>
bool PortableImageDecoderPlugin<TContext>::sniff() bool PortableImageDecoderPlugin<TContext>::initialize()
{ {
using Context = TContext; using Context = TContext;
if (m_context->data_size < 2) if (m_context->data_size < 2)
@ -123,6 +125,28 @@ bool PortableImageDecoderPlugin<TContext>::sniff()
return false; return false;
} }
template<typename TContext>
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> PortableImageDecoderPlugin<TContext>::create(ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) PortableImageDecoderPlugin<TContext>(data.data(), data.size()));
}
template<typename TContext>
ErrorOr<bool> PortableImageDecoderPlugin<TContext>::sniff(ReadonlyBytes data)
{
using Context = TContext;
if (data.size() < 2)
return false;
if (data.data()[0] == 'P' && data.data()[1] == Context::FormatDetails::ascii_magic_number)
return true;
if (data.data()[0] == 'P' && data.data()[1] == Context::FormatDetails::binary_magic_number)
return true;
return false;
}
template<typename TContext> template<typename TContext>
bool PortableImageDecoderPlugin<TContext>::is_animated() bool PortableImageDecoderPlugin<TContext>::is_animated()
{ {

View File

@ -223,12 +223,23 @@ bool QOIImageDecoderPlugin::set_nonvolatile(bool& was_purged)
return m_context->bitmap->set_nonvolatile(was_purged); return m_context->bitmap->set_nonvolatile(was_purged);
} }
bool QOIImageDecoderPlugin::sniff() bool QOIImageDecoderPlugin::initialize()
{ {
InputMemoryStream stream { { m_context->data, m_context->data_size } }; InputMemoryStream stream { { m_context->data, m_context->data_size } };
return !decode_qoi_header(stream).is_error(); return !decode_qoi_header(stream).is_error();
} }
ErrorOr<bool> QOIImageDecoderPlugin::sniff(ReadonlyBytes data)
{
InputMemoryStream stream { { data.data(), data.size() } };
return !decode_qoi_header(stream).is_error();
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> QOIImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) QOIImageDecoderPlugin(data.data(), data.size()));
}
ErrorOr<ImageFrameDescriptor> QOIImageDecoderPlugin::frame(size_t index) ErrorOr<ImageFrameDescriptor> QOIImageDecoderPlugin::frame(size_t index)
{ {
if (index > 0) if (index > 0)

View File

@ -40,13 +40,15 @@ struct QOILoadingContext {
class QOIImageDecoderPlugin final : public ImageDecoderPlugin { class QOIImageDecoderPlugin final : public ImageDecoderPlugin {
public: public:
static ErrorOr<bool> sniff(ReadonlyBytes);
static ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> create(ReadonlyBytes);
virtual ~QOIImageDecoderPlugin() override = default; virtual ~QOIImageDecoderPlugin() override = default;
QOIImageDecoderPlugin(u8 const*, size_t);
virtual IntSize size() override; virtual IntSize size() override;
virtual void set_volatile() override; virtual void set_volatile() override;
[[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override;
virtual bool sniff() override; virtual bool initialize() override;
virtual bool is_animated() override { return false; } virtual bool is_animated() override { return false; }
virtual size_t loop_count() override { return 0; } virtual size_t loop_count() override { return 0; }
virtual size_t frame_count() override { return 1; } virtual size_t frame_count() override { return 1; }
@ -56,6 +58,8 @@ private:
ErrorOr<void> decode_header_and_update_context(InputMemoryStream&); ErrorOr<void> decode_header_and_update_context(InputMemoryStream&);
ErrorOr<void> decode_image_and_update_context(InputMemoryStream&); ErrorOr<void> decode_image_and_update_context(InputMemoryStream&);
QOIImageDecoderPlugin(u8 const*, size_t);
OwnPtr<QOILoadingContext> m_context; OwnPtr<QOILoadingContext> m_context;
}; };

View File

@ -212,11 +212,28 @@ bool TGAImageDecoderPlugin::decode_tga_header()
return true; return true;
} }
bool TGAImageDecoderPlugin::sniff() bool TGAImageDecoderPlugin::initialize()
{ {
return decode_tga_header(); return decode_tga_header();
} }
ErrorOr<bool> TGAImageDecoderPlugin::validate_before_create(ReadonlyBytes data)
{
if (data.size() < sizeof(TGAHeader))
return false;
TGAHeader const& header = *reinterpret_cast<TGAHeader const*>(data.data());
if (header.data_type_code == TGADataType::UncompressedRGB && data.size() < (header.width * header.height * (header.bits_per_pixel / 8)))
return false;
if (header.bits_per_pixel < 8 || header.bits_per_pixel > 32)
return false;
return true;
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> TGAImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) TGAImageDecoderPlugin(data.data(), data.size()));
}
bool TGAImageDecoderPlugin::is_animated() bool TGAImageDecoderPlugin::is_animated()
{ {
return false; return false;

View File

@ -14,13 +14,16 @@ struct TGALoadingContext;
class TGAImageDecoderPlugin final : public ImageDecoderPlugin { class TGAImageDecoderPlugin final : public ImageDecoderPlugin {
public: public:
static ErrorOr<bool> validate_before_create(ReadonlyBytes);
static ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> create(ReadonlyBytes);
virtual ~TGAImageDecoderPlugin() override; virtual ~TGAImageDecoderPlugin() override;
TGAImageDecoderPlugin(u8 const*, size_t); TGAImageDecoderPlugin(u8 const*, size_t);
virtual IntSize size() override; virtual IntSize size() override;
virtual void set_volatile() override; virtual void set_volatile() override;
[[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override;
virtual bool sniff() override; virtual bool initialize() override;
virtual bool is_animated() override; virtual bool is_animated() override;
virtual size_t loop_count() override; virtual size_t loop_count() override;
virtual size_t frame_count() override; virtual size_t frame_count() override;

View File

@ -269,9 +269,14 @@ ErrorOr<ByteBuffer> Filter::decode_jbig2(ReadonlyBytes)
ErrorOr<ByteBuffer> Filter::decode_dct(ReadonlyBytes bytes) ErrorOr<ByteBuffer> Filter::decode_dct(ReadonlyBytes bytes)
{ {
Gfx::JPGImageDecoderPlugin decoder(bytes.data(), bytes.size()); if (Gfx::JPGImageDecoderPlugin::sniff({ bytes.data(), bytes.size() }).release_value_but_fixme_should_propagate_errors()) {
auto frame = TRY(decoder.frame(0)); auto decoder = Gfx::JPGImageDecoderPlugin::create({ bytes.data(), bytes.size() }).release_value_but_fixme_should_propagate_errors();
return frame.image->serialize_to_byte_buffer(); if (decoder->initialize()) {
auto frame = TRY(decoder->frame(0));
return frame.image->serialize_to_byte_buffer();
}
}
return AK::Error::from_string_literal("Not a JPG image!");
}; };
ErrorOr<ByteBuffer> Filter::decode_jpx(ReadonlyBytes) ErrorOr<ByteBuffer> Filter::decode_jpx(ReadonlyBytes)

View File

@ -142,11 +142,23 @@ void SpiceAgent::on_message_received()
} else { } else {
ErrorOr<Gfx::ImageFrameDescriptor> frame_or_error = Gfx::ImageFrameDescriptor {}; ErrorOr<Gfx::ImageFrameDescriptor> frame_or_error = Gfx::ImageFrameDescriptor {};
if (type == ClipboardType::PNG) { if (type == ClipboardType::PNG) {
frame_or_error = Gfx::PNGImageDecoderPlugin(data_buffer.data(), data_buffer.size()).frame(0); if (Gfx::PNGImageDecoderPlugin::sniff({ data_buffer.data(), data_buffer.size() }).release_value_but_fixme_should_propagate_errors()) {
auto png_decoder = Gfx::PNGImageDecoderPlugin::create({ data_buffer.data(), data_buffer.size() }).release_value_but_fixme_should_propagate_errors();
if (png_decoder->initialize())
frame_or_error = png_decoder->frame(0);
}
} else if (type == ClipboardType::BMP) { } else if (type == ClipboardType::BMP) {
frame_or_error = Gfx::BMPImageDecoderPlugin(data_buffer.data(), data_buffer.size()).frame(0); if (Gfx::BMPImageDecoderPlugin::sniff({ data_buffer.data(), data_buffer.size() }).release_value_but_fixme_should_propagate_errors()) {
auto bmp_decoder = Gfx::BMPImageDecoderPlugin::create({ data_buffer.data(), data_buffer.size() }).release_value_but_fixme_should_propagate_errors();
if (bmp_decoder->initialize())
frame_or_error = bmp_decoder->frame(0);
}
} else if (type == ClipboardType::JPG) { } else if (type == ClipboardType::JPG) {
frame_or_error = Gfx::JPGImageDecoderPlugin(data_buffer.data(), data_buffer.size()).frame(0); if (Gfx::JPGImageDecoderPlugin::sniff({ data_buffer.data(), data_buffer.size() }).release_value_but_fixme_should_propagate_errors()) {
auto jpg_decoder = Gfx::JPGImageDecoderPlugin::create({ data_buffer.data(), data_buffer.size() }).release_value_but_fixme_should_propagate_errors();
if (jpg_decoder->initialize())
frame_or_error = jpg_decoder->frame(0);
}
} else { } else {
dbgln("Unknown clipboard type: {}", (u32)type); dbgln("Unknown clipboard type: {}", (u32)type);
return; return;