Browse Source

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.
Liav A 2 years ago
parent
commit
57e19a7e56
33 changed files with 493 additions and 206 deletions
  1. 6 2
      Meta/Lagom/Fuzzers/FuzzBMPLoader.cpp
  2. 7 3
      Meta/Lagom/Fuzzers/FuzzGIFLoader.cpp
  3. 6 2
      Meta/Lagom/Fuzzers/FuzzICOLoader.cpp
  4. 6 2
      Meta/Lagom/Fuzzers/FuzzJPGLoader.cpp
  5. 6 2
      Meta/Lagom/Fuzzers/FuzzPBMLoader.cpp
  6. 6 2
      Meta/Lagom/Fuzzers/FuzzPGMLoader.cpp
  7. 6 2
      Meta/Lagom/Fuzzers/FuzzPNGLoader.cpp
  8. 6 2
      Meta/Lagom/Fuzzers/FuzzPPMLoader.cpp
  9. 6 2
      Meta/Lagom/Fuzzers/FuzzQOILoader.cpp
  10. 6 2
      Meta/Lagom/Fuzzers/FuzzTGALoader.cpp
  11. 135 78
      Tests/LibGfx/TestImageDecoder.cpp
  12. 8 3
      Userland/Libraries/LibGUI/FileIconProvider.cpp
  13. 21 3
      Userland/Libraries/LibGfx/BMPLoader.cpp
  14. 14 2
      Userland/Libraries/LibGfx/BMPLoader.h
  15. 16 1
      Userland/Libraries/LibGfx/DDSLoader.cpp
  16. 6 2
      Userland/Libraries/LibGfx/DDSLoader.h
  17. 12 1
      Userland/Libraries/LibGfx/GIFLoader.cpp
  18. 6 2
      Userland/Libraries/LibGfx/GIFLoader.h
  19. 30 14
      Userland/Libraries/LibGfx/ICOLoader.cpp
  20. 7 2
      Userland/Libraries/LibGfx/ICOLoader.h
  21. 45 51
      Userland/Libraries/LibGfx/ImageDecoder.cpp
  22. 1 2
      Userland/Libraries/LibGfx/ImageDecoder.h
  23. 15 5
      Userland/Libraries/LibGfx/JPGLoader.cpp
  24. 6 2
      Userland/Libraries/LibGfx/JPGLoader.h
  25. 14 1
      Userland/Libraries/LibGfx/PNGLoader.cpp
  26. 6 2
      Userland/Libraries/LibGfx/PNGLoader.h
  27. 27 3
      Userland/Libraries/LibGfx/PortableImageMapLoader.h
  28. 12 1
      Userland/Libraries/LibGfx/QOILoader.cpp
  29. 6 2
      Userland/Libraries/LibGfx/QOILoader.h
  30. 18 1
      Userland/Libraries/LibGfx/TGALoader.cpp
  31. 4 1
      Userland/Libraries/LibGfx/TGALoader.h
  32. 8 3
      Userland/Libraries/LibPDF/Filter.cpp
  33. 15 3
      Userland/Services/SpiceAgent/SpiceAgent.cpp

+ 6 - 2
Meta/Lagom/Fuzzers/FuzzBMPLoader.cpp

@@ -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);
-    (void)decoder.frame(0);
+    auto decoder_or_error = Gfx::BMPImageDecoderPlugin::create({ data, size });
+    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;
 }
 }

+ 7 - 3
Meta/Lagom/Fuzzers/FuzzGIFLoader.cpp

@@ -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 bitmap_or_error = gif_decoder.frame(0);
+    auto decoder_or_error = Gfx::GIFImageDecoderPlugin::create({ data, size });
+    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());

+ 6 - 2
Meta/Lagom/Fuzzers/FuzzICOLoader.cpp

@@ -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);
-    (void)decoder.frame(0);
+    auto decoder_or_error = Gfx::ICOImageDecoderPlugin::create({ data, size });
+    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;
 }
 }

+ 6 - 2
Meta/Lagom/Fuzzers/FuzzJPGLoader.cpp

@@ -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);
-    (void)decoder.frame(0);
+    auto decoder_or_error = Gfx::JPGImageDecoderPlugin::create({ data, size });
+    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;
 }
 }

+ 6 - 2
Meta/Lagom/Fuzzers/FuzzPBMLoader.cpp

@@ -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);
-    (void)decoder.frame(0);
+    auto decoder_or_error = Gfx::PBMImageDecoderPlugin::create({ data, size });
+    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;
 }
 }

+ 6 - 2
Meta/Lagom/Fuzzers/FuzzPGMLoader.cpp

@@ -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);
-    (void)decoder.frame(0);
+    auto decoder_or_error = Gfx::PGMImageDecoderPlugin::create({ data, size });
+    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;
 }
 }

+ 6 - 2
Meta/Lagom/Fuzzers/FuzzPNGLoader.cpp

@@ -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);
-    (void)decoder.frame(0);
+    auto decoder_or_error = Gfx::PNGImageDecoderPlugin::create({ data, size });
+    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;
 }
 }

+ 6 - 2
Meta/Lagom/Fuzzers/FuzzPPMLoader.cpp

@@ -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);
-    (void)decoder.frame(0);
+    auto decoder_or_error = Gfx::PPMImageDecoderPlugin::create({ data, size });
+    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;
 }
 }

+ 6 - 2
Meta/Lagom/Fuzzers/FuzzQOILoader.cpp

@@ -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);
-    (void)decoder.frame(0);
+    auto decoder_or_error = Gfx::QOIImageDecoderPlugin::create({ data, size });
+    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;
 }
 }

+ 6 - 2
Meta/Lagom/Fuzzers/FuzzTGALoader.cpp

@@ -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);
-    (void)decoder.frame(0);
+    auto decoder_or_error = Gfx::TGAImageDecoderPlugin::create({ data, size });
+    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;
 }
 }

+ 135 - 78
Tests/LibGfx/TestImageDecoder.cpp

@@ -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(bmp.frame_count());
+    EXPECT_EQ(MUST(Gfx::BMPImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
+    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(!bmp.is_animated());
-    EXPECT(!bmp.loop_count());
+    EXPECT(plugin_decoder->frame_count());
+    EXPECT(!plugin_decoder->is_animated());
+    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(gif.frame_count());
+    EXPECT_EQ(MUST(Gfx::GIFImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
+    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(gif.is_animated());
-    EXPECT(!gif.loop_count());
+    EXPECT(plugin_decoder->frame_count());
+    EXPECT(!plugin_decoder->is_animated());
+    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(ico.frame_count());
+    EXPECT_EQ(MUST(Gfx::ICOImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
+    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(!ico.is_animated());
-    EXPECT(!ico.loop_count());
+    EXPECT(plugin_decoder->frame_count());
+    EXPECT(!plugin_decoder->is_animated());
+    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(ico.frame_count());
+    EXPECT_EQ(MUST(Gfx::ICOImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
+    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(!ico.is_animated());
-    EXPECT(!ico.loop_count());
+    EXPECT(plugin_decoder->frame_count());
+    EXPECT(!plugin_decoder->is_animated());
+    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(jpg.frame_count());
+    EXPECT_EQ(MUST(Gfx::JPGImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
+    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(!jpg.is_animated());
-    EXPECT(!jpg.loop_count());
+    EXPECT(plugin_decoder->frame_count());
+    EXPECT(!plugin_decoder->is_animated());
+    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(pbm.frame_count());
+    EXPECT_EQ(MUST(Gfx::PBMImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
+    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(plugin_decoder->frame_count());
+    EXPECT(!plugin_decoder->is_animated());
+    EXPECT(!plugin_decoder->loop_count());
 
 
-    EXPECT(pbm.sniff());
-    EXPECT(!pbm.is_animated());
-    EXPECT(!pbm.loop_count());
+    EXPECT(!plugin_decoder->frame(0).is_error());
 
 
-    auto frame = pbm.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_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(pgm.frame_count());
+    EXPECT_EQ(MUST(Gfx::PGMImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
+    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(plugin_decoder->frame_count());
+    EXPECT(!plugin_decoder->is_animated());
+    EXPECT(!plugin_decoder->loop_count());
 
 
-    EXPECT(pgm.sniff());
-    EXPECT(!pgm.is_animated());
-    EXPECT(!pgm.loop_count());
+    EXPECT(!plugin_decoder->frame(0).is_error());
 
 
-    auto frame = pgm.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_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(png.frame_count());
+    EXPECT_EQ(MUST(Gfx::PNGImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
+    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(!png.is_animated());
-    EXPECT(!png.loop_count());
+    EXPECT(plugin_decoder->frame_count());
+    EXPECT(!plugin_decoder->is_animated());
+    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(ppm.frame_count());
+    EXPECT_EQ(MUST(Gfx::PPMImageDecoderPlugin::sniff({ (u8 const*)file->data(), file->size() })), true);
+    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(plugin_decoder->frame_count());
+    EXPECT(!plugin_decoder->is_animated());
+    EXPECT(!plugin_decoder->loop_count());
 
 
-    EXPECT(ppm.sniff());
-    EXPECT(!ppm.is_animated());
-    EXPECT(!ppm.loop_count());
+    EXPECT(!plugin_decoder->frame(0).is_error());
 
 
-    auto frame = ppm.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_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(tga.frame_count(), 1u);
+    EXPECT_EQ(MUST(Gfx::TGAImageDecoderPlugin::validate_before_create({ (u8 const*)file->data(), file->size() })), true);
+    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(!tga.is_animated());
-    EXPECT(!tga.loop_count());
+    EXPECT_EQ(plugin_decoder->frame_count(), 1u);
+    EXPECT(!plugin_decoder->is_animated());
+    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(tga.frame_count(), 1u);
+    EXPECT_EQ(MUST(Gfx::TGAImageDecoderPlugin::validate_before_create({ (u8 const*)file->data(), file->size() })), true);
+    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_EQ(plugin_decoder->frame_count(), 1u);
+    EXPECT(!plugin_decoder->is_animated());
+    EXPECT(!plugin_decoder->loop_count());
 
 
-    EXPECT(tga.sniff());
-    EXPECT(!tga.is_animated());
-    EXPECT(!tga.loop_count());
+    EXPECT(!plugin_decoder->frame(0).is_error());
 
 
-    auto frame_or_error = tga.frame(0);
+    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(tga.frame_count(), 1u);
+    EXPECT_EQ(MUST(Gfx::TGAImageDecoderPlugin::validate_before_create({ (u8 const*)file->data(), file->size() })), true);
+    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(!tga.is_animated());
-    EXPECT(!tga.loop_count());
+    EXPECT_EQ(plugin_decoder->frame_count(), 1u);
+    EXPECT(!plugin_decoder->is_animated());
+    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(tga.frame_count(), 1u);
+    EXPECT_EQ(MUST(Gfx::TGAImageDecoderPlugin::validate_before_create({ (u8 const*)file->data(), file->size() })), true);
+    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_EQ(plugin_decoder->frame_count(), 1u);
+    EXPECT(!plugin_decoder->is_animated());
+    EXPECT(!plugin_decoder->loop_count());
 
 
-    EXPECT(tga.sniff());
-    EXPECT(!tga.is_animated());
-    EXPECT(!tga.loop_count());
+    EXPECT(!plugin_decoder->frame(0).is_error());
 
 
-    auto frame_or_error = tga.frame(0);
+    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);

+ 8 - 3
Userland/Libraries/LibGUI/FileIconProvider.cpp

@@ -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 (!frame_or_error.is_error()) {
-                bitmap = frame_or_error.value().image;
+            if (Gfx::PNGImageDecoderPlugin::sniff({ section->raw_data(), section->size() }).release_value_but_fixme_should_propagate_errors()) {
+                auto png_decoder = Gfx::PNGImageDecoderPlugin::create({ section->raw_data(), section->size() }).release_value_but_fixme_should_propagate_errors();
+                if (png_decoder->initialize()) {
+                    auto frame_or_error = png_decoder->frame(0);
+                    if (!frame_or_error.is_error()) {
+                        bitmap = frame_or_error.value().image;
+                    }
+                }
             }
             }
         }
         }
 
 

+ 21 - 3
Userland/Libraries/LibGfx/BMPLoader.cpp

@@ -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();

+ 14 - 2
Userland/Libraries/LibGfx/BMPLoader.h

@@ -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;
 };
 };
 
 

+ 16 - 1
Userland/Libraries/LibGfx/DDSLoader.cpp

@@ -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;

+ 6 - 2
Userland/Libraries/LibGfx/DDSLoader.h

@@ -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();
 };
 };

+ 12 - 1
Userland/Libraries/LibGfx/GIFLoader.cpp

@@ -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) {

+ 6 - 2
Userland/Libraries/LibGfx/GIFLoader.h

@@ -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;
 };
 };
 
 

+ 30 - 14
Userland/Libraries/LibGfx/ICOLoader.cpp

@@ -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];
-
-    PNGImageDecoderPlugin png_decoder(context.data + desc.offset, desc.size);
-    if (png_decoder.sniff()) {
-        auto decoded_png_frame = png_decoder.frame(0);
-        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);
-            return false;
+    if (PNGImageDecoderPlugin::sniff({ context.data + desc.offset, desc.size }).release_value_but_fixme_should_propagate_errors()) {
+        auto png_decoder = PNGImageDecoderPlugin::create({ context.data + desc.offset, desc.size }).release_value_but_fixme_should_propagate_errors();
+        if (png_decoder->initialize()) {
+            auto decoded_png_frame = png_decoder->frame(0);
+            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);
+                return false;
+            }
+            desc.bitmap = decoded_png_frame.value().image;
+            return true;
         }
         }
-        desc.bitmap = decoded_png_frame.value().image;
-        return true;
+        return false;
     } else {
     } else {
-        BMPImageDecoderPlugin bmp_decoder(context.data + desc.offset, desc.size, true);
-        if (bmp_decoder.sniff_dib()) {
-            auto decoded_bmp_frame = bmp_decoder.frame(0);
+        auto bmp_decoder = BMPImageDecoderPlugin::create_as_included_in_ico({}, { context.data + desc.offset, desc.size }).release_value_but_fixme_should_propagate_errors();
+        // NOTE: We don't initialize a BMP decoder in the usual way, but rather
+        // 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();

+ 7 - 2
Userland/Libraries/LibGfx/ICOLoader.h

@@ -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;
 };
 };
 
 

+ 45 - 51
Userland/Libraries/LibGfx/ImageDecoder.cpp

@@ -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();
-    auto size = bytes.size();
-    OwnPtr<ImageDecoderPlugin> plugin;
-
-    plugin = make<PNGImageDecoderPlugin>(data, size);
-    if (plugin->sniff())
-        return plugin;
-
-    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;
-
+    for (auto& plugin : s_initializers) {
+        auto sniff_result = plugin.sniff(bytes).release_value_but_fixme_should_propagate_errors();
+        if (!sniff_result)
+            continue;
+        auto plugin_decoder = plugin.create(bytes).release_value_but_fixme_should_propagate_errors();
+        if (plugin_decoder->initialize())
+            return plugin_decoder;
+    }
     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();
-    auto size = bytes.size();
-    OwnPtr<ImageDecoderPlugin> plugin;
-    if (mime_type == "image/x-targa"sv) {
-        plugin = make<TGAImageDecoderPlugin>(data, size);
-        if (plugin->sniff())
-            return plugin;
+    for (auto& plugin : s_initializers_with_mime_type) {
+        if (plugin.mime_type != mime_type)
+            continue;
+        auto validation_result = plugin.validate_before_create(bytes).release_value_but_fixme_should_propagate_errors();
+        if (!validation_result)
+            continue;
+        auto plugin_decoder = plugin.create(bytes).release_value_but_fixme_should_propagate_errors();
+        if (plugin_decoder->initialize())
+            return plugin_decoder;
     }
     }
     return {};
     return {};
 }
 }

+ 1 - 2
Userland/Libraries/LibGfx/ImageDecoder.h

@@ -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(); }

+ 15 - 5
Userland/Libraries/LibGfx/JPGLoader.cpp

@@ -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
-        && m_context->data[0] == 0xFF
-        && m_context->data[1] == 0xD8
-        && m_context->data[2] == 0xFF;
+    return true;
+}
+
+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()

+ 6 - 2
Userland/Libraries/LibGfx/JPGLoader.h

@@ -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;
 };
 };
 }
 }

+ 14 - 1
Userland/Libraries/LibGfx/PNGLoader.cpp

@@ -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;

+ 6 - 2
Userland/Libraries/LibGfx/PNGLoader.h

@@ -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;
 };
 };
 
 

+ 27 - 3
Userland/Libraries/LibGfx/PortableImageMapLoader.h

@@ -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()
 {
 {

+ 12 - 1
Userland/Libraries/LibGfx/QOILoader.cpp

@@ -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)

+ 6 - 2
Userland/Libraries/LibGfx/QOILoader.h

@@ -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;
 };
 };
 
 

+ 18 - 1
Userland/Libraries/LibGfx/TGALoader.cpp

@@ -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;

+ 4 - 1
Userland/Libraries/LibGfx/TGALoader.h

@@ -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;

+ 8 - 3
Userland/Libraries/LibPDF/Filter.cpp

@@ -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());
-    auto frame = TRY(decoder.frame(0));
-    return frame.image->serialize_to_byte_buffer();
+    if (Gfx::JPGImageDecoderPlugin::sniff({ bytes.data(), bytes.size() }).release_value_but_fixme_should_propagate_errors()) {
+        auto decoder = Gfx::JPGImageDecoderPlugin::create({ bytes.data(), bytes.size() }).release_value_but_fixme_should_propagate_errors();
+        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)

+ 15 - 3
Userland/Services/SpiceAgent/SpiceAgent.cpp

@@ -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;