diff --git a/Meta/Lagom/Fuzzers/FuzzBMPLoader.cpp b/Meta/Lagom/Fuzzers/FuzzBMPLoader.cpp index f581351d14..8a701ab518 100644 --- a/Meta/Lagom/Fuzzers/FuzzBMPLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzBMPLoader.cpp @@ -9,7 +9,11 @@ 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; } diff --git a/Meta/Lagom/Fuzzers/FuzzGIFLoader.cpp b/Meta/Lagom/Fuzzers/FuzzGIFLoader.cpp index f72354e19c..5de7cb6f79 100644 --- a/Meta/Lagom/Fuzzers/FuzzGIFLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzGIFLoader.cpp @@ -13,14 +13,18 @@ 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()) { auto const& bitmap = bitmap_or_error.value().image; // Looks like a valid GIF. Try to load the other frames: dbgln_if(GIF_DEBUG, "bitmap size: {}", bitmap->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, "loop_count: {}", gif_decoder.loop_count()); dbgln_if(GIF_DEBUG, "frame_count: {}", gif_decoder.frame_count()); diff --git a/Meta/Lagom/Fuzzers/FuzzICOLoader.cpp b/Meta/Lagom/Fuzzers/FuzzICOLoader.cpp index 56db322b1b..1e4b995f40 100644 --- a/Meta/Lagom/Fuzzers/FuzzICOLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzICOLoader.cpp @@ -10,7 +10,11 @@ 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; } diff --git a/Meta/Lagom/Fuzzers/FuzzJPGLoader.cpp b/Meta/Lagom/Fuzzers/FuzzJPGLoader.cpp index 54d54bcdc1..c9dc81de7a 100644 --- a/Meta/Lagom/Fuzzers/FuzzJPGLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzJPGLoader.cpp @@ -10,7 +10,11 @@ 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; } diff --git a/Meta/Lagom/Fuzzers/FuzzPBMLoader.cpp b/Meta/Lagom/Fuzzers/FuzzPBMLoader.cpp index d5244a6ee1..e2cc65711b 100644 --- a/Meta/Lagom/Fuzzers/FuzzPBMLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzPBMLoader.cpp @@ -10,7 +10,11 @@ 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; } diff --git a/Meta/Lagom/Fuzzers/FuzzPGMLoader.cpp b/Meta/Lagom/Fuzzers/FuzzPGMLoader.cpp index 3db44ec53d..bea425c068 100644 --- a/Meta/Lagom/Fuzzers/FuzzPGMLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzPGMLoader.cpp @@ -10,7 +10,11 @@ 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; } diff --git a/Meta/Lagom/Fuzzers/FuzzPNGLoader.cpp b/Meta/Lagom/Fuzzers/FuzzPNGLoader.cpp index 54a1b832f0..a871d1ef83 100644 --- a/Meta/Lagom/Fuzzers/FuzzPNGLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzPNGLoader.cpp @@ -10,7 +10,11 @@ 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; } diff --git a/Meta/Lagom/Fuzzers/FuzzPPMLoader.cpp b/Meta/Lagom/Fuzzers/FuzzPPMLoader.cpp index 30f53592e6..4b78866e73 100644 --- a/Meta/Lagom/Fuzzers/FuzzPPMLoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzPPMLoader.cpp @@ -10,7 +10,11 @@ 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; } diff --git a/Meta/Lagom/Fuzzers/FuzzQOILoader.cpp b/Meta/Lagom/Fuzzers/FuzzQOILoader.cpp index 4714b9f738..caf278b28f 100644 --- a/Meta/Lagom/Fuzzers/FuzzQOILoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzQOILoader.cpp @@ -10,7 +10,11 @@ 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; } diff --git a/Meta/Lagom/Fuzzers/FuzzTGALoader.cpp b/Meta/Lagom/Fuzzers/FuzzTGALoader.cpp index 7ffe6949ca..4d1848e0b4 100644 --- a/Meta/Lagom/Fuzzers/FuzzTGALoader.cpp +++ b/Meta/Lagom/Fuzzers/FuzzTGALoader.cpp @@ -10,7 +10,11 @@ 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; } diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index 7c6ceb836a..16ccec27ba 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -24,138 +24,180 @@ TEST_CASE(test_bmp) { 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); } TEST_CASE(test_gif) { 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); } TEST_CASE(test_not_ico) { 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) { 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) { 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); } TEST_CASE(test_pbm) { 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(pbm.sniff()); - EXPECT(!pbm.is_animated()); - EXPECT(!pbm.loop_count()); + EXPECT(plugin_decoder->frame_count()); + EXPECT(!plugin_decoder->is_animated()); + 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); } TEST_CASE(test_pgm) { 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(pgm.sniff()); - EXPECT(!pgm.is_animated()); - EXPECT(!pgm.loop_count()); + EXPECT(plugin_decoder->frame_count()); + EXPECT(!plugin_decoder->is_animated()); + 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); } TEST_CASE(test_png) { 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); } TEST_CASE(test_ppm) { 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(ppm.sniff()); - EXPECT(!ppm.is_animated()); - EXPECT(!ppm.loop_count()); + EXPECT(plugin_decoder->frame_count()); + EXPECT(!plugin_decoder->is_animated()); + 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); } 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 tga = Gfx::TGAImageDecoderPlugin(reinterpret_cast(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()); auto frame = frame_or_error.release_value(); EXPECT(frame.duration == 0); @@ -164,14 +206,19 @@ TEST_CASE(test_targa_bottom_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 tga = Gfx::TGAImageDecoderPlugin(reinterpret_cast(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()); auto frame = frame_or_error.release_value(); EXPECT(frame.duration == 0); @@ -180,14 +227,19 @@ TEST_CASE(test_targa_top_left) 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 tga = Gfx::TGAImageDecoderPlugin(reinterpret_cast(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()); auto frame = frame_or_error.release_value(); EXPECT(frame.duration == 0); @@ -196,14 +248,19 @@ TEST_CASE(test_targa_bottom_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 tga = Gfx::TGAImageDecoderPlugin(reinterpret_cast(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()); auto frame = frame_or_error.release_value(); EXPECT(frame.duration == 0); diff --git a/Userland/Libraries/LibGUI/FileIconProvider.cpp b/Userland/Libraries/LibGUI/FileIconProvider.cpp index 87df5d0345..ebb62794a0 100644 --- a/Userland/Libraries/LibGUI/FileIconProvider.cpp +++ b/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); } else { // FIXME: Use the ImageDecoder service. - auto frame_or_error = Gfx::PNGImageDecoderPlugin(reinterpret_cast(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; + } + } } } diff --git a/Userland/Libraries/LibGfx/BMPLoader.cpp b/Userland/Libraries/LibGfx/BMPLoader.cpp index ece02245ed..8b7e30095d 100644 --- a/Userland/Libraries/LibGfx/BMPLoader.cpp +++ b/Userland/Libraries/LibGfx/BMPLoader.cpp @@ -1400,12 +1400,12 @@ static ErrorOr decode_bmp_pixel_data(BMPLoadingContext& context) 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(); m_context->file_bytes = data; 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; @@ -1434,11 +1434,29 @@ bool BMPImageDecoderPlugin::set_nonvolatile(bool& was_purged) return m_context->bitmap->set_nonvolatile(was_purged); } -bool BMPImageDecoderPlugin::sniff() +bool BMPImageDecoderPlugin::initialize() { return !decode_bmp_header(*m_context).is_error(); } +ErrorOr BMPImageDecoderPlugin::sniff(ReadonlyBytes data) +{ + BMPLoadingContext context; + context.file_bytes = data.data(); + context.file_size = data.size(); + return !decode_bmp_header(context).is_error(); +} + +ErrorOr> BMPImageDecoderPlugin::create(ReadonlyBytes data) +{ + return adopt_nonnull_own_or_enomem(new (nothrow) BMPImageDecoderPlugin(data.data(), data.size())); +} + +ErrorOr> BMPImageDecoderPlugin::create_as_included_in_ico(Badge, ReadonlyBytes data) +{ + return adopt_nonnull_own_or_enomem(new (nothrow) BMPImageDecoderPlugin(data.data(), data.size(), IncludedInICO::Yes)); +} + bool BMPImageDecoderPlugin::sniff_dib() { return !decode_bmp_dib(*m_context).is_error(); diff --git a/Userland/Libraries/LibGfx/BMPLoader.h b/Userland/Libraries/LibGfx/BMPLoader.h index 07fbf7dcfb..6f21d7bd66 100644 --- a/Userland/Libraries/LibGfx/BMPLoader.h +++ b/Userland/Libraries/LibGfx/BMPLoader.h @@ -6,21 +6,31 @@ #pragma once +#include #include namespace Gfx { struct BMPLoadingContext; +class ICOImageDecoderPlugin; class BMPImageDecoderPlugin final : public ImageDecoderPlugin { public: + static ErrorOr sniff(ReadonlyBytes); + static ErrorOr> create(ReadonlyBytes); + static ErrorOr> create_as_included_in_ico(Badge, ReadonlyBytes); + + enum class IncludedInICO { + Yes, + No, + }; + virtual ~BMPImageDecoderPlugin() override; - BMPImageDecoderPlugin(u8 const*, size_t, bool is_included_in_ico = false); virtual IntSize size() override; virtual void set_volatile() override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; - virtual bool sniff() override; + virtual bool initialize() override; bool sniff_dib(); virtual bool is_animated() override; virtual size_t loop_count() override; @@ -28,6 +38,8 @@ public: virtual ErrorOr frame(size_t index) override; private: + BMPImageDecoderPlugin(u8 const*, size_t, IncludedInICO included_in_ico = IncludedInICO::No); + OwnPtr m_context; }; diff --git a/Userland/Libraries/LibGfx/DDSLoader.cpp b/Userland/Libraries/LibGfx/DDSLoader.cpp index a9ed4641b2..f69f7fccac 100644 --- a/Userland/Libraries/LibGfx/DDSLoader.cpp +++ b/Userland/Libraries/LibGfx/DDSLoader.cpp @@ -968,7 +968,7 @@ bool DDSImageDecoderPlugin::set_nonvolatile(bool& 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. return m_context->data_size > 128 @@ -978,6 +978,21 @@ bool DDSImageDecoderPlugin::sniff() && m_context->data[3] == 0x20; } +ErrorOr 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> DDSImageDecoderPlugin::create(ReadonlyBytes data) +{ + return adopt_nonnull_own_or_enomem(new (nothrow) DDSImageDecoderPlugin(data.data(), data.size())); +} + bool DDSImageDecoderPlugin::is_animated() { return false; diff --git a/Userland/Libraries/LibGfx/DDSLoader.h b/Userland/Libraries/LibGfx/DDSLoader.h index 87321d79df..1c56ae2af1 100644 --- a/Userland/Libraries/LibGfx/DDSLoader.h +++ b/Userland/Libraries/LibGfx/DDSLoader.h @@ -235,19 +235,23 @@ struct DDSLoadingContext; class DDSImageDecoderPlugin final : public ImageDecoderPlugin { public: + static ErrorOr sniff(ReadonlyBytes); + static ErrorOr> create(ReadonlyBytes); + virtual ~DDSImageDecoderPlugin() override; - DDSImageDecoderPlugin(u8 const*, size_t); virtual IntSize size() override; virtual void set_volatile() override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; - virtual bool sniff() override; + virtual bool initialize() override; virtual bool is_animated() override; virtual size_t loop_count() override; virtual size_t frame_count() override; virtual ErrorOr frame(size_t index) override; private: + DDSImageDecoderPlugin(u8 const*, size_t); + OwnPtr m_context; void dump_debug(); }; diff --git a/Userland/Libraries/LibGfx/GIFLoader.cpp b/Userland/Libraries/LibGfx/GIFLoader.cpp index 7d53cbbb9d..f491cdd451 100644 --- a/Userland/Libraries/LibGfx/GIFLoader.cpp +++ b/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); } -bool GIFImageDecoderPlugin::sniff() +bool GIFImageDecoderPlugin::initialize() { InputMemoryStream stream { { m_context->data, m_context->data_size } }; return !decode_gif_header(stream).is_error(); } +ErrorOr GIFImageDecoderPlugin::sniff(ReadonlyBytes data) +{ + InputMemoryStream stream { { data.data(), data.size() } }; + return !decode_gif_header(stream).is_error(); +} + +ErrorOr> GIFImageDecoderPlugin::create(ReadonlyBytes data) +{ + return adopt_nonnull_own_or_enomem(new (nothrow) GIFImageDecoderPlugin(data.data(), data.size())); +} + bool GIFImageDecoderPlugin::is_animated() { if (m_context->error_state != GIFLoadingContext::ErrorState::NoError) { diff --git a/Userland/Libraries/LibGfx/GIFLoader.h b/Userland/Libraries/LibGfx/GIFLoader.h index dbc6065d2a..fc78f9ca10 100644 --- a/Userland/Libraries/LibGfx/GIFLoader.h +++ b/Userland/Libraries/LibGfx/GIFLoader.h @@ -15,19 +15,23 @@ struct GIFLoadingContext; class GIFImageDecoderPlugin final : public ImageDecoderPlugin { public: + static ErrorOr sniff(ReadonlyBytes); + static ErrorOr> create(ReadonlyBytes); + virtual ~GIFImageDecoderPlugin() override; - GIFImageDecoderPlugin(u8 const*, size_t); virtual IntSize size() override; virtual void set_volatile() override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; - virtual bool sniff() override; + virtual bool initialize() override; virtual bool is_animated() override; virtual size_t loop_count() override; virtual size_t frame_count() override; virtual ErrorOr frame(size_t index) override; private: + GIFImageDecoderPlugin(u8 const*, size_t); + OwnPtr m_context; }; diff --git a/Userland/Libraries/LibGfx/ICOLoader.cpp b/Userland/Libraries/LibGfx/ICOLoader.cpp index 27a00f211f..242015ffb9 100644 --- a/Userland/Libraries/LibGfx/ICOLoader.cpp +++ b/Userland/Libraries/LibGfx/ICOLoader.cpp @@ -136,7 +136,7 @@ static bool load_ico_directory(ICOLoadingContext& context) return true; } -static bool load_ico_bitmap(ICOLoadingContext& context, Optional index) +bool ICOImageDecoderPlugin::load_ico_bitmap(ICOLoadingContext& context, Optional index) { if (context.state < ICOLoadingContext::State::DirectoryDecoded) { if (!load_ico_directory(context)) { @@ -153,20 +153,25 @@ static bool load_ico_bitmap(ICOLoadingContext& context, Optional 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 { - 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) { dbgln_if(ICO_DEBUG, "load_ico_bitmap: failed to load BMP encoded image index: {}", real_index); return false; @@ -180,6 +185,17 @@ static bool load_ico_bitmap(ICOLoadingContext& context, Optional index) } } +ErrorOr ICOImageDecoderPlugin::sniff(ReadonlyBytes data) +{ + InputMemoryStream stream { { data.data(), data.size() } }; + return decode_ico_header(stream).has_value(); +} + +ErrorOr> 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) { m_context = make(); @@ -219,7 +235,7 @@ bool ICOImageDecoderPlugin::set_nonvolatile(bool& 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 } }; return decode_ico_header(stream).has_value(); diff --git a/Userland/Libraries/LibGfx/ICOLoader.h b/Userland/Libraries/LibGfx/ICOLoader.h index 334e60f09c..8136319e57 100644 --- a/Userland/Libraries/LibGfx/ICOLoader.h +++ b/Userland/Libraries/LibGfx/ICOLoader.h @@ -14,19 +14,24 @@ struct ICOLoadingContext; class ICOImageDecoderPlugin final : public ImageDecoderPlugin { public: + static ErrorOr sniff(ReadonlyBytes); + static ErrorOr> create(ReadonlyBytes); + virtual ~ICOImageDecoderPlugin() override; - ICOImageDecoderPlugin(u8 const*, size_t); virtual IntSize size() override; virtual void set_volatile() override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; - virtual bool sniff() override; + virtual bool initialize() override; virtual bool is_animated() override; virtual size_t loop_count() override; virtual size_t frame_count() override; virtual ErrorOr frame(size_t index) override; private: + ICOImageDecoderPlugin(u8 const*, size_t); + static bool load_ico_bitmap(ICOLoadingContext& context, Optional index); + OwnPtr m_context; }; diff --git a/Userland/Libraries/LibGfx/ImageDecoder.cpp b/Userland/Libraries/LibGfx/ImageDecoder.cpp index 9d293317b3..ad0feaa6df 100644 --- a/Userland/Libraries/LibGfx/ImageDecoder.cpp +++ b/Userland/Libraries/LibGfx/ImageDecoder.cpp @@ -20,64 +20,58 @@ namespace Gfx { +struct ImagePluginInitializer { + ErrorOr (*sniff)(ReadonlyBytes) = nullptr; + ErrorOr> (*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 (*validate_before_create)(ReadonlyBytes) = nullptr; + ErrorOr> (*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 probe_and_sniff_for_appropriate_plugin(ReadonlyBytes bytes) { - auto* data = bytes.data(); - auto size = bytes.size(); - OwnPtr plugin; - - plugin = make(data, size); - if (plugin->sniff()) - return plugin; - - plugin = make(data, size); - if (plugin->sniff()) - return plugin; - - plugin = make(data, size); - if (plugin->sniff()) - return plugin; - - plugin = make(data, size); - if (plugin->sniff()) - return plugin; - - plugin = make(data, size); - if (plugin->sniff()) - return plugin; - - plugin = make(data, size); - if (plugin->sniff()) - return plugin; - - plugin = make(data, size); - if (plugin->sniff()) - return plugin; - - plugin = make(data, size); - if (plugin->sniff()) - return plugin; - - plugin = make(data, size); - if (plugin->sniff()) - return plugin; - - plugin = make(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 {}; } static OwnPtr probe_and_sniff_for_appropriate_plugin_with_known_mime_type(StringView mime_type, ReadonlyBytes bytes) { - auto* data = bytes.data(); - auto size = bytes.size(); - OwnPtr plugin; - if (mime_type == "image/x-targa"sv) { - plugin = make(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 {}; } diff --git a/Userland/Libraries/LibGfx/ImageDecoder.h b/Userland/Libraries/LibGfx/ImageDecoder.h index 9d622ec8ac..a8ceae3a78 100644 --- a/Userland/Libraries/LibGfx/ImageDecoder.h +++ b/Userland/Libraries/LibGfx/ImageDecoder.h @@ -34,7 +34,7 @@ public: virtual void set_volatile() = 0; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) = 0; - virtual bool sniff() = 0; + virtual bool initialize() = 0; virtual bool is_animated() = 0; virtual size_t loop_count() = 0; @@ -55,7 +55,6 @@ public: int height() const { return size().height(); } void set_volatile() { m_plugin->set_volatile(); } [[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(); } size_t loop_count() const { return m_plugin->loop_count(); } size_t frame_count() const { return m_plugin->frame_count(); } diff --git a/Userland/Libraries/LibGfx/JPGLoader.cpp b/Userland/Libraries/LibGfx/JPGLoader.cpp index 260e3322ae..1bab4ff45e 100644 --- a/Userland/Libraries/LibGfx/JPGLoader.cpp +++ b/Userland/Libraries/LibGfx/JPGLoader.cpp @@ -1179,12 +1179,22 @@ bool JPGImageDecoderPlugin::set_nonvolatile(bool& 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 JPGImageDecoderPlugin::sniff(ReadonlyBytes data) +{ + return data.size() > 3 + && data.data()[0] == 0xFF + && data.data()[1] == 0xD8 + && data.data()[2] == 0xFF; +} + +ErrorOr> JPGImageDecoderPlugin::create(ReadonlyBytes data) +{ + return adopt_nonnull_own_or_enomem(new (nothrow) JPGImageDecoderPlugin(data.data(), data.size())); } bool JPGImageDecoderPlugin::is_animated() diff --git a/Userland/Libraries/LibGfx/JPGLoader.h b/Userland/Libraries/LibGfx/JPGLoader.h index 592f8a93a0..f1ab0de78e 100644 --- a/Userland/Libraries/LibGfx/JPGLoader.h +++ b/Userland/Libraries/LibGfx/JPGLoader.h @@ -14,18 +14,22 @@ struct JPGLoadingContext; class JPGImageDecoderPlugin : public ImageDecoderPlugin { public: + static ErrorOr sniff(ReadonlyBytes); + static ErrorOr> create(ReadonlyBytes); + virtual ~JPGImageDecoderPlugin() override; - JPGImageDecoderPlugin(u8 const*, size_t); virtual IntSize size() override; virtual void set_volatile() override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; - virtual bool sniff() override; + virtual bool initialize() override; virtual bool is_animated() override; virtual size_t loop_count() override; virtual size_t frame_count() override; virtual ErrorOr frame(size_t index) override; private: + JPGImageDecoderPlugin(u8 const*, size_t); + OwnPtr m_context; }; } diff --git a/Userland/Libraries/LibGfx/PNGLoader.cpp b/Userland/Libraries/LibGfx/PNGLoader.cpp index 6c310ba03b..a2e7d8dc60 100644 --- a/Userland/Libraries/LibGfx/PNGLoader.cpp +++ b/Userland/Libraries/LibGfx/PNGLoader.cpp @@ -913,11 +913,24 @@ bool PNGImageDecoderPlugin::set_nonvolatile(bool& was_purged) return m_context->bitmap->set_nonvolatile(was_purged); } -bool PNGImageDecoderPlugin::sniff() +bool PNGImageDecoderPlugin::initialize() { return decode_png_header(*m_context); } +ErrorOr PNGImageDecoderPlugin::sniff(ReadonlyBytes data) +{ + PNGLoadingContext context; + context.data = data.data(); + context.data_size = data.size(); + return decode_png_header(context); +} + +ErrorOr> PNGImageDecoderPlugin::create(ReadonlyBytes data) +{ + return adopt_nonnull_own_or_enomem(new (nothrow) PNGImageDecoderPlugin(data.data(), data.size())); +} + bool PNGImageDecoderPlugin::is_animated() { return false; diff --git a/Userland/Libraries/LibGfx/PNGLoader.h b/Userland/Libraries/LibGfx/PNGLoader.h index 8e11319b5b..7fa0ae0130 100644 --- a/Userland/Libraries/LibGfx/PNGLoader.h +++ b/Userland/Libraries/LibGfx/PNGLoader.h @@ -14,19 +14,23 @@ struct PNGLoadingContext; class PNGImageDecoderPlugin final : public ImageDecoderPlugin { public: + static ErrorOr sniff(ReadonlyBytes); + static ErrorOr> create(ReadonlyBytes); + virtual ~PNGImageDecoderPlugin() override; - PNGImageDecoderPlugin(u8 const*, size_t); virtual IntSize size() override; virtual void set_volatile() override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; - virtual bool sniff() override; + virtual bool initialize() override; virtual bool is_animated() override; virtual size_t loop_count() override; virtual size_t frame_count() override; virtual ErrorOr frame(size_t index) override; private: + PNGImageDecoderPlugin(u8 const*, size_t); + OwnPtr m_context; }; diff --git a/Userland/Libraries/LibGfx/PortableImageMapLoader.h b/Userland/Libraries/LibGfx/PortableImageMapLoader.h index ab5a466406..9afc1cce67 100644 --- a/Userland/Libraries/LibGfx/PortableImageMapLoader.h +++ b/Userland/Libraries/LibGfx/PortableImageMapLoader.h @@ -49,6 +49,9 @@ struct PortableImageMapLoadingContext { template class PortableImageDecoderPlugin final : public ImageDecoderPlugin { public: + static ErrorOr sniff(ReadonlyBytes); + static ErrorOr> create(ReadonlyBytes); + PortableImageDecoderPlugin(u8 const*, size_t); virtual ~PortableImageDecoderPlugin() override = default; @@ -57,8 +60,7 @@ public: virtual void set_volatile() override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; - virtual bool sniff() override; - + virtual bool initialize() override; virtual bool is_animated() override; virtual size_t loop_count() override; virtual size_t frame_count() override; @@ -108,7 +110,7 @@ bool PortableImageDecoderPlugin::set_nonvolatile(bool& was_purged) } template -bool PortableImageDecoderPlugin::sniff() +bool PortableImageDecoderPlugin::initialize() { using Context = TContext; if (m_context->data_size < 2) @@ -123,6 +125,28 @@ bool PortableImageDecoderPlugin::sniff() return false; } +template +ErrorOr> PortableImageDecoderPlugin::create(ReadonlyBytes data) +{ + return adopt_nonnull_own_or_enomem(new (nothrow) PortableImageDecoderPlugin(data.data(), data.size())); +} + +template +ErrorOr PortableImageDecoderPlugin::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 bool PortableImageDecoderPlugin::is_animated() { diff --git a/Userland/Libraries/LibGfx/QOILoader.cpp b/Userland/Libraries/LibGfx/QOILoader.cpp index 623b507a73..8cec503a4e 100644 --- a/Userland/Libraries/LibGfx/QOILoader.cpp +++ b/Userland/Libraries/LibGfx/QOILoader.cpp @@ -223,12 +223,23 @@ bool QOIImageDecoderPlugin::set_nonvolatile(bool& 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 } }; return !decode_qoi_header(stream).is_error(); } +ErrorOr QOIImageDecoderPlugin::sniff(ReadonlyBytes data) +{ + InputMemoryStream stream { { data.data(), data.size() } }; + return !decode_qoi_header(stream).is_error(); +} + +ErrorOr> QOIImageDecoderPlugin::create(ReadonlyBytes data) +{ + return adopt_nonnull_own_or_enomem(new (nothrow) QOIImageDecoderPlugin(data.data(), data.size())); +} + ErrorOr QOIImageDecoderPlugin::frame(size_t index) { if (index > 0) diff --git a/Userland/Libraries/LibGfx/QOILoader.h b/Userland/Libraries/LibGfx/QOILoader.h index 9446e69a16..b5df791830 100644 --- a/Userland/Libraries/LibGfx/QOILoader.h +++ b/Userland/Libraries/LibGfx/QOILoader.h @@ -40,13 +40,15 @@ struct QOILoadingContext { class QOIImageDecoderPlugin final : public ImageDecoderPlugin { public: + static ErrorOr sniff(ReadonlyBytes); + static ErrorOr> create(ReadonlyBytes); + virtual ~QOIImageDecoderPlugin() override = default; - QOIImageDecoderPlugin(u8 const*, size_t); virtual IntSize size() override; virtual void set_volatile() 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 size_t loop_count() override { return 0; } virtual size_t frame_count() override { return 1; } @@ -56,6 +58,8 @@ private: ErrorOr decode_header_and_update_context(InputMemoryStream&); ErrorOr decode_image_and_update_context(InputMemoryStream&); + QOIImageDecoderPlugin(u8 const*, size_t); + OwnPtr m_context; }; diff --git a/Userland/Libraries/LibGfx/TGALoader.cpp b/Userland/Libraries/LibGfx/TGALoader.cpp index 4f253fb9d3..b699194449 100644 --- a/Userland/Libraries/LibGfx/TGALoader.cpp +++ b/Userland/Libraries/LibGfx/TGALoader.cpp @@ -212,11 +212,28 @@ bool TGAImageDecoderPlugin::decode_tga_header() return true; } -bool TGAImageDecoderPlugin::sniff() +bool TGAImageDecoderPlugin::initialize() { return decode_tga_header(); } +ErrorOr TGAImageDecoderPlugin::validate_before_create(ReadonlyBytes data) +{ + if (data.size() < sizeof(TGAHeader)) + return false; + TGAHeader const& header = *reinterpret_cast(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> TGAImageDecoderPlugin::create(ReadonlyBytes data) +{ + return adopt_nonnull_own_or_enomem(new (nothrow) TGAImageDecoderPlugin(data.data(), data.size())); +} + bool TGAImageDecoderPlugin::is_animated() { return false; diff --git a/Userland/Libraries/LibGfx/TGALoader.h b/Userland/Libraries/LibGfx/TGALoader.h index 0aede54815..d2de9fecae 100644 --- a/Userland/Libraries/LibGfx/TGALoader.h +++ b/Userland/Libraries/LibGfx/TGALoader.h @@ -14,13 +14,16 @@ struct TGALoadingContext; class TGAImageDecoderPlugin final : public ImageDecoderPlugin { public: + static ErrorOr validate_before_create(ReadonlyBytes); + static ErrorOr> create(ReadonlyBytes); + virtual ~TGAImageDecoderPlugin() override; TGAImageDecoderPlugin(u8 const*, size_t); virtual IntSize size() override; virtual void set_volatile() override; [[nodiscard]] virtual bool set_nonvolatile(bool& was_purged) override; - virtual bool sniff() override; + virtual bool initialize() override; virtual bool is_animated() override; virtual size_t loop_count() override; virtual size_t frame_count() override; diff --git a/Userland/Libraries/LibPDF/Filter.cpp b/Userland/Libraries/LibPDF/Filter.cpp index 8898e010eb..976c3be9de 100644 --- a/Userland/Libraries/LibPDF/Filter.cpp +++ b/Userland/Libraries/LibPDF/Filter.cpp @@ -269,9 +269,14 @@ ErrorOr Filter::decode_jbig2(ReadonlyBytes) ErrorOr 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 Filter::decode_jpx(ReadonlyBytes) diff --git a/Userland/Services/SpiceAgent/SpiceAgent.cpp b/Userland/Services/SpiceAgent/SpiceAgent.cpp index 9119ab84b5..fcd52767e2 100644 --- a/Userland/Services/SpiceAgent/SpiceAgent.cpp +++ b/Userland/Services/SpiceAgent/SpiceAgent.cpp @@ -142,11 +142,23 @@ void SpiceAgent::on_message_received() } else { ErrorOr frame_or_error = Gfx::ImageFrameDescriptor {}; 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) { - 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) { - 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 { dbgln("Unknown clipboard type: {}", (u32)type); return;