LibGfx/ICO: Do not try to decode a mask if we already reached EOF

When using the BMP encoding, ICO images are expected to contain a 1-bit
mask for transparency. Regardless an alpha channel is already included
in the image, the mask is always required. As stated here[1], the
mask is used to provide shadow around the image.

Unfortunately, it seems that some encoder do not include that second
transparency mask. So let's read that mask only if some data is still
remaining after decoding the image.

The test case has been generated by truncating the 64 last bytes
(originally dedicated to the mask) from the `serenity.ico` file and
changing the declared size of the image in the ICO header. The size
value is stored at the offset 0x0E in the file and I changed the value
from 0x0468 to 0x0428.

[1]: https://devblogs.microsoft.com/oldnewthing/20101021-00/?p=12483
This commit is contained in:
Lucas CHOLLET 2024-01-06 19:24:41 -05:00 committed by Tim Flynn
parent 9c54c13744
commit 402de2985d
Notes: sideshowbarker 2024-07-17 02:08:15 +09:00
3 changed files with 13 additions and 2 deletions

View file

@ -177,6 +177,17 @@ TEST_CASE(test_bmp_embedded_in_ico)
EXPECT_EQ(frame.image->get_pixel(7, 4), Gfx::Color(161, 0, 0));
}
TEST_CASE(test_malformed_maskless_ico)
{
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("ico/malformed_maskless.ico"sv)));
EXPECT(Gfx::ICOImageDecoderPlugin::sniff(file->bytes()));
auto plugin_decoder = TRY_OR_FAIL(Gfx::ICOImageDecoderPlugin::create(file->bytes()));
auto frame = TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 16, 16 }));
EXPECT_EQ(frame.image->get_pixel(0, 0), Gfx::Color::NamedColor::Transparent);
EXPECT_EQ(frame.image->get_pixel(7, 4), Gfx::Color(161, 0, 0));
}
TEST_CASE(test_ilbm)
{
auto file = MUST(Core::MappedFile::map(TEST_INPUT("ilbm/gradient.iff"sv)));

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1429,7 +1429,7 @@ static ErrorOr<void> decode_bmp_pixel_data(BMPLoadingContext& context)
TRY(process_row(row));
}
if (context.is_included_in_ico) {
if (context.is_included_in_ico && !streamer.at_end()) {
for (u32 row = 0; row < height; ++row) {
TRY(process_mask_row(row));
}
@ -1440,7 +1440,7 @@ static ErrorOr<void> decode_bmp_pixel_data(BMPLoadingContext& context)
TRY(process_row(row));
}
if (context.is_included_in_ico) {
if (context.is_included_in_ico && !streamer.at_end()) {
for (i32 row = height - 1; row >= 0; --row) {
TRY(process_mask_row(row));
}