LibGfx/ILBMLoader: Properly display images with a bitplane mask

Images with a display mask ("stencil" as it's called in DPaint) add
an extra bitplane which acts as a mask. For now, at least skip it
properly. Later we should render masked pixels as transparent, but
this requires some refactoring.
This commit is contained in:
Nicolas Ramz 2024-01-16 15:29:44 +01:00 committed by Andreas Kling
parent 45181e8eaf
commit 534eeb6c4b
Notes: sideshowbarker 2024-07-17 20:58:35 +09:00
3 changed files with 21 additions and 5 deletions

View file

@ -265,6 +265,17 @@ TEST_CASE(test_small_24bit)
EXPECT_EQ(frame.image->get_pixel(0, 4), Gfx::Color(1, 0, 1, 255));
}
TEST_CASE(test_stencil_mask)
{
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("ilbm/test-stencil.iff"sv)));
EXPECT(Gfx::ILBMImageDecoderPlugin::sniff(file->bytes()));
auto plugin_decoder = TRY_OR_FAIL(Gfx::ILBMImageDecoderPlugin::create(file->bytes()));
auto frame = TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 320, 200 }));
EXPECT_EQ(frame.image->get_pixel(0, 4), Gfx::Color(0, 0, 0, 255));
}
TEST_CASE(test_ilbm_malformed_header)
{
Array test_inputs = {

Binary file not shown.

View file

@ -182,7 +182,8 @@ static ErrorOr<ByteBuffer> planar_to_chunky(ReadonlyBytes bitplanes, ILBMLoading
u16 pitch = context.pitch;
u16 width = context.bm_header.width;
u16 height = context.bm_header.height;
u8 planes = context.bm_header.planes;
// mask is added as an extra plane
u8 planes = context.bm_header.mask == MaskType::HasMask ? context.bm_header.planes + 1 : context.bm_header.planes;
size_t buffer_size = static_cast<size_t>(width) * height;
// If planes number is 24 we'll store R,G,B components so buffer needs to be 3 times width*height
// otherwise we'll store a single 8bit index to the CMAP.
@ -209,17 +210,17 @@ static ErrorOr<ByteBuffer> planar_to_chunky(ReadonlyBytes bitplanes, ILBMLoading
// when enough data for current bitplane row has been read
for (u8 b = 0; b < 8 && (i * 8) + b < width; b++) {
u8 mask = 1 << (7 - b);
// get current plane
if (bit & mask) {
// get current plane: simply skip mask plane for now
if (bit & mask && p < context.bm_header.planes) {
u16 x = (i * 8) + b;
size_t offset = (scanline * pixel_size) + (x * pixel_size) + rgb_shift;
// Only throw an error if we would actually attempt to write
// outside of the chunky buffer. Some apps like PPaint produce
// malformed bitplane data but files are still accepted by most readers
// since they do not cause writing past the chunky buffer.
if (offset >= chunky.size()) {
if (offset >= chunky.size())
return Error::from_string_literal("Malformed bitplane data");
}
chunky[offset] |= plane_mask;
}
}
@ -239,6 +240,10 @@ static ErrorOr<ByteBuffer> uncompress_byte_run(ReadonlyBytes data, ILBMLoadingCo
size_t plane_data_size = context.pitch * context.bm_header.height * context.bm_header.planes;
// The mask is encoded as an extra bitplane but is not counted in the bm_header planes
if (context.bm_header.mask == MaskType::HasMask)
plane_data_size += context.pitch * context.bm_header.height;
// The maximum run length of this compression method is 127 bytes, so the uncompressed size
// cannot be more than 127 times the size of the chunk we are decompressing.
if (plane_data_size > NumericLimits<u32>::max() || ceil_div(plane_data_size, 127ul) > length)