diff --git a/AK/Debug.h.in b/AK/Debug.h.in index 9ecc1ea215a..a303c386bcf 100644 --- a/AK/Debug.h.in +++ b/AK/Debug.h.in @@ -110,10 +110,6 @@ # cmakedefine01 IDL_DEBUG #endif -#ifndef ILBM_DEBUG -# cmakedefine01 ILBM_DEBUG -#endif - #ifndef IMAGE_DECODER_DEBUG # cmakedefine01 IMAGE_DECODER_DEBUG #endif diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index 962cf413328..b0cc186cba1 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -23,7 +23,6 @@ set(HTTPJOB_DEBUG ON) set(HUNKS_DEBUG ON) set(ICO_DEBUG ON) set(IDL_DEBUG ON) -set(ILBM_DEBUG ON) set(IMAGE_DECODER_DEBUG ON) set(IMAGE_LOADER_DEBUG ON) set(JOB_DEBUG ON) diff --git a/Meta/Lagom/Fuzzers/FuzzILBMLoader.cpp b/Meta/Lagom/Fuzzers/FuzzILBMLoader.cpp deleted file mode 100644 index 7cc3d9822b6..00000000000 --- a/Meta/Lagom/Fuzzers/FuzzILBMLoader.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2023, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) -{ - AK::set_debug_enabled(false); - auto decoder_or_error = Gfx::ILBMImageDecoderPlugin::create({ data, size }); - if (decoder_or_error.is_error()) - return 0; - auto decoder = decoder_or_error.release_value(); - (void)decoder->frame(0); - return 0; -} diff --git a/Meta/Lagom/Fuzzers/fuzzers.cmake b/Meta/Lagom/Fuzzers/fuzzers.cmake index ca26b0c3f77..d31233e41db 100644 --- a/Meta/Lagom/Fuzzers/fuzzers.cmake +++ b/Meta/Lagom/Fuzzers/fuzzers.cmake @@ -13,7 +13,6 @@ set(FUZZER_TARGETS HttpRequest ICCProfile ICOLoader - ILBMLoader JPEG2000Loader JPEGLoader Js @@ -71,7 +70,6 @@ set(FUZZER_DEPENDENCIES_GzipRoundtrip LibCompress) set(FUZZER_DEPENDENCIES_HttpRequest LibHTTP) set(FUZZER_DEPENDENCIES_ICCProfile LibGfx) set(FUZZER_DEPENDENCIES_ICOLoader LibGfx) -set(FUZZER_DEPENDENCIES_ILBMLoader LibGfx) set(FUZZER_DEPENDENCIES_JPEG2000Loader LibGfx) set(FUZZER_DEPENDENCIES_JPEGLoader LibGfx) set(FUZZER_DEPENDENCIES_Js LibJS) diff --git a/Meta/gn/secondary/AK/BUILD.gn b/Meta/gn/secondary/AK/BUILD.gn index 2f69ed5923e..223f69d2e25 100644 --- a/Meta/gn/secondary/AK/BUILD.gn +++ b/Meta/gn/secondary/AK/BUILD.gn @@ -244,7 +244,6 @@ write_cmake_config("ak_debug_gen") { "HUNKS_DEBUG=", "ICO_DEBUG=", "IDL_DEBUG=", - "ILBM_DEBUG=", "IMAGE_DECODER_DEBUG=", "IMAGE_LOADER_DEBUG=", "JOB_DEBUG=", diff --git a/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn index e10506037dd..a12f60ac25a 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn @@ -63,7 +63,6 @@ shared_library("LibGfx") { "ImageFormats/CCITTDecoder.cpp", "ImageFormats/GIFLoader.cpp", "ImageFormats/ICOLoader.cpp", - "ImageFormats/ILBMLoader.cpp", "ImageFormats/ISOBMFF/Boxes.cpp", "ImageFormats/ISOBMFF/JPEG2000Boxes.cpp", "ImageFormats/ISOBMFF/Reader.cpp", diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index bfbf1c090e9..1b9d8e7655c 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -195,123 +194,6 @@ TEST_CASE(test_malformed_maskless_ico) EXPECT_EQ(frame.image->get_pixel(7, 4), Gfx::Color(161, 0, 0)); } -TEST_CASE(test_ilbm) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("ilbm/gradient.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(8, 0), Gfx::Color(0xee, 0xbb, 0, 255)); -} - -TEST_CASE(test_ilbm_uncompressed) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("ilbm/gradient-uncompressed.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(8, 0), Gfx::Color(0xee, 0xbb, 0, 255)); -} - -TEST_CASE(test_ilbm_ham6) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("ilbm/ham6.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, { 256, 256 })); - - EXPECT_EQ(frame.image->get_pixel(77, 107), Gfx::Color(0xf0, 0x40, 0x40, 0xff)); -} - -TEST_CASE(test_ilbm_dos) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("ilbm/serenity.lbm"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, { 640, 480 })); - - EXPECT_EQ(frame.image->get_pixel(315, 134), Gfx::Color::NamedColor::Red); -} - -TEST_CASE(test_24bit) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("ilbm/serenity-24bit.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, { 640, 640 })); - - EXPECT_EQ(frame.image->get_pixel(158, 270), Gfx::Color(0xee, 0x3d, 0x3c, 255)); -} - -TEST_CASE(test_brush_transparent_color) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("ilbm/brush-transparent-color.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, { 266, 309 })); - - EXPECT_EQ(frame.image->get_pixel(114, 103), Gfx::Color::NamedColor::Black); -} - -TEST_CASE(test_small_24bit) -{ - auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("ilbm/small-24bit.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, { 10, 10 })); - - 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 = { - TEST_INPUT("ilbm/truncated-bmhd-chunk.iff"sv) - }; - - for (auto test_input : test_inputs) { - auto file = TRY_OR_FAIL(Core::MappedFile::map(test_input)); - auto plugin_decoder_or_error = Gfx::ILBMImageDecoderPlugin::create(file->bytes()); - EXPECT(plugin_decoder_or_error.is_error()); - } -} - -TEST_CASE(test_ilbm_malformed_frame) -{ - Array test_inputs = { - TEST_INPUT("ilbm/incorrect-cmap-size.iff"sv), - TEST_INPUT("ilbm/incorrect-uncompressed-size.iff"sv), - TEST_INPUT("ilbm/missing-body-chunk.iff"sv) - }; - - for (auto test_input : test_inputs) { - auto file = TRY_OR_FAIL(Core::MappedFile::map(test_input)); - auto plugin_decoder = TRY_OR_FAIL(Gfx::ILBMImageDecoderPlugin::create(file->bytes())); - auto frame_or_error = plugin_decoder->frame(0); - EXPECT(frame_or_error.is_error()); - } -} - TEST_CASE(test_jpeg_sof0_one_scan) { auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("jpg/rgb24.jpg"sv))); diff --git a/Tests/LibGfx/test-inputs/ilbm/brush-transparent-color.iff b/Tests/LibGfx/test-inputs/ilbm/brush-transparent-color.iff deleted file mode 100644 index ed5e7268f60..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/brush-transparent-color.iff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/ilbm/gradient-uncompressed.iff b/Tests/LibGfx/test-inputs/ilbm/gradient-uncompressed.iff deleted file mode 100644 index ef77add5b00..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/gradient-uncompressed.iff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/ilbm/gradient.iff b/Tests/LibGfx/test-inputs/ilbm/gradient.iff deleted file mode 100644 index e187cc716b3..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/gradient.iff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/ilbm/ham6.iff b/Tests/LibGfx/test-inputs/ilbm/ham6.iff deleted file mode 100644 index 6f0678ad8fd..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/ham6.iff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/ilbm/incorrect-cmap-size.iff b/Tests/LibGfx/test-inputs/ilbm/incorrect-cmap-size.iff deleted file mode 100644 index 98c2a17594f..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/incorrect-cmap-size.iff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/ilbm/incorrect-uncompressed-size.iff b/Tests/LibGfx/test-inputs/ilbm/incorrect-uncompressed-size.iff deleted file mode 100644 index 80e47cf43a9..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/incorrect-uncompressed-size.iff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/ilbm/missing-body-chunk.iff b/Tests/LibGfx/test-inputs/ilbm/missing-body-chunk.iff deleted file mode 100644 index 6a3d0572aed..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/missing-body-chunk.iff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/ilbm/serenity-24bit.iff b/Tests/LibGfx/test-inputs/ilbm/serenity-24bit.iff deleted file mode 100644 index 37885b748de..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/serenity-24bit.iff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/ilbm/serenity.lbm b/Tests/LibGfx/test-inputs/ilbm/serenity.lbm deleted file mode 100644 index b44a31d53be..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/serenity.lbm and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/ilbm/small-24bit.iff b/Tests/LibGfx/test-inputs/ilbm/small-24bit.iff deleted file mode 100644 index 39580314bc5..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/small-24bit.iff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/ilbm/test-stencil.iff b/Tests/LibGfx/test-inputs/ilbm/test-stencil.iff deleted file mode 100644 index 233ceac178c..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/test-stencil.iff and /dev/null differ diff --git a/Tests/LibGfx/test-inputs/ilbm/truncated-bmhd-chunk.iff b/Tests/LibGfx/test-inputs/ilbm/truncated-bmhd-chunk.iff deleted file mode 100644 index f2471f04a62..00000000000 Binary files a/Tests/LibGfx/test-inputs/ilbm/truncated-bmhd-chunk.iff and /dev/null differ diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt index 6c1fa23390b..1d47fa64b0d 100644 --- a/Userland/Libraries/LibGfx/CMakeLists.txt +++ b/Userland/Libraries/LibGfx/CMakeLists.txt @@ -41,7 +41,6 @@ set(SOURCES ImageFormats/GIFLoader.cpp ImageFormats/GIFWriter.cpp ImageFormats/ICOLoader.cpp - ImageFormats/ILBMLoader.cpp ImageFormats/ImageDecoder.cpp ImageFormats/ISOBMFF/Boxes.cpp ImageFormats/ISOBMFF/JPEG2000Boxes.cpp diff --git a/Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.cpp deleted file mode 100644 index 7dfed023fec..00000000000 --- a/Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.cpp +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright (c) 2023, Nicolas Ramz - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Gfx { - -static constexpr size_t const ilbm_header_size = 12; - -enum class CompressionType : u8 { - None = 0, - ByteRun = 1, - __Count -}; - -enum class MaskType : u8 { - None = 0, - HasMask = 1, - HasTransparentColor = 2, - HasLasso = 3, - __Count -}; - -enum class ViewportMode : u32 { - EHB = 0x80, - HAM = 0x800 -}; - -enum class Format : u8 { - // Amiga interleaved format - ILBM = 0, - // PC-DeluxePaint chunky format - PBM = 1 -}; - -AK_ENUM_BITWISE_OPERATORS(ViewportMode); - -struct BMHDHeader { - BigEndian width; - BigEndian height; - BigEndian x; - BigEndian y; - u8 planes; - MaskType mask; - CompressionType compression; - u8 pad; - BigEndian transparent_color; - u8 x_aspect; - u8 y_aspect; - BigEndian page_width; - BigEndian page_height; -}; - -static_assert(sizeof(BMHDHeader) == 20); - -struct ILBMLoadingContext { - enum class State { - NotDecoded = 0, - HeaderDecoded, - BitmapDecoded - }; - State state { State::NotDecoded }; - ReadonlyBytes data; - - // points to current chunk - ReadonlyBytes chunks_cursor; - - // max number of bytes per plane row - u16 pitch; - - ViewportMode viewport_mode; - - Vector color_table; - - // number of bits needed to describe current palette - u8 cmap_bits; - - RefPtr bitmap; - - BMHDHeader bm_header; - - Format format; -}; - -static ErrorOr decode_iff_ilbm_header(ILBMLoadingContext& context) -{ - if (context.state >= ILBMLoadingContext::State::HeaderDecoded) - return {}; - - if (context.data.size() < ilbm_header_size) - return Error::from_string_literal("Missing IFF header"); - - auto header_stream = FixedMemoryStream { context.data }; - auto header = TRY(IFF::FileHeader::read_from_stream(header_stream)); - if (header.magic() != "FORM"sv || (header.subformat != "ILBM"sv && header.subformat != "PBM "sv)) - return Error::from_string_literal("Invalid IFF-ILBM header"); - - context.format = header.subformat == "ILBM" ? Format::ILBM : Format::PBM; - - return {}; -} - -static ErrorOr> decode_cmap_chunk(IFF::Chunk cmap_chunk) -{ - size_t const size = cmap_chunk.size() / 3; - Vector color_table; - TRY(color_table.try_ensure_capacity(size)); - - for (size_t i = 0; i < size; ++i) { - color_table.unchecked_append(Color(cmap_chunk[i * 3], cmap_chunk[(i * 3) + 1], cmap_chunk[(i * 3) + 2])); - } - - return color_table; -} - -static ErrorOr> chunky_to_bitmap(ILBMLoadingContext& context, ByteBuffer const& chunky) -{ - auto const width = context.bm_header.width; - auto const height = context.bm_header.height; - - RefPtr bitmap = TRY(Bitmap::create(BitmapFormat::BGRA8888, { width, height })); - - dbgln_if(ILBM_DEBUG, "created Bitmap {}x{}", width, height); - - // - For 24bit pictures: the chunky buffer contains 3 bytes (R,G,B) per pixel - // - For indexed colored pictures: chunky buffer contains a single byte per pixel - u8 pixel_size = AK::max(1, context.bm_header.planes / 8); - - for (int row = 0; row < height; ++row) { - // Keep color: in HAM mode, current color - // may be based on previous color instead of coming from - // the palette. - Color color = Color::Black; - for (int col = 0; col < width; col++) { - size_t index = (width * row * pixel_size) + (col * pixel_size); - if (context.bm_header.planes == 24) { - color = Color(chunky[index], chunky[index + 1], chunky[index + 2]); - } else if (chunky[index] < context.color_table.size()) { - color = context.color_table[chunky[index]]; - if (context.bm_header.mask == MaskType::HasTransparentColor && chunky[index] == context.bm_header.transparent_color) - color = color.with_alpha(0); - } else if (has_flag(context.viewport_mode, ViewportMode::HAM)) { - // Get the control bit which will tell use how current pixel should be calculated - u8 control = (chunky[index] >> context.cmap_bits) & 0x3; - // Since we only have (cmap_bits - 2) bits to define the component, - // we need to pad it to 8 bits. - u8 component = (chunky[index] % context.color_table.size()) << (8 - context.cmap_bits); - - if (control == 1) { - color.set_blue(component); - } else if (control == 2) { - color.set_red(component); - } else { - color.set_green(component); - } - } else { - return Error::from_string_literal("Color map index out of bounds but HAM bit not set"); - } - bitmap->set_pixel(col, row, color); - } - } - - dbgln_if(ILBM_DEBUG, "filled Bitmap"); - - return bitmap; -} - -static ErrorOr planar_to_chunky(ReadonlyBytes bitplanes, ILBMLoadingContext& context) -{ - dbgln_if(ILBM_DEBUG, "planar_to_chunky"); - u16 pitch = context.pitch; - u16 width = context.bm_header.width; - u16 height = context.bm_header.height; - // 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(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. - if (planes == 24) - buffer_size *= 3; - - auto chunky = TRY(ByteBuffer::create_zeroed(buffer_size)); - - u8 const pixel_size = AK::max(1, planes / 8); - - for (u16 y = 0; y < height; y++) { - size_t scanline = static_cast(y) * width; - for (u8 p = 0; p < planes; p++) { - u8 const plane_mask = 1 << (p % 8); - size_t offset_base = (pitch * planes * y) + (p * pitch); - if (offset_base + pitch > bitplanes.size()) - return Error::from_string_literal("Malformed bitplane data"); - - for (u16 i = 0; i < pitch; i++) { - u8 bit = bitplanes[offset_base + i]; - u8 rgb_shift = p / 8; - - // Some encoders don't pad bytes rows with 0: make sure we stop - // 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: 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()) - return Error::from_string_literal("Malformed bitplane data"); - - chunky[offset] |= plane_mask; - } - } - } - } - } - - dbgln_if(ILBM_DEBUG, "planar_to_chunky: end"); - - return chunky; -} - -static ErrorOr uncompress_byte_run(ReadonlyBytes data, ILBMLoadingContext& context) -{ - auto length = data.size(); - dbgln_if(ILBM_DEBUG, "uncompress_byte_run pitch={} size={}", context.pitch, data.size()); - - 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::max() || ceil_div(plane_data_size, 127ul) > length) - return Error::from_string_literal("Uncompressed data size too large"); - - auto plane_data = TRY(Compress::PackBits::decode_all(data, plane_data_size)); - - return plane_data; -} - -static ErrorOr extend_ehb_palette(ILBMLoadingContext& context) -{ - dbgln_if(ILBM_DEBUG, "need to extend palette"); - for (size_t i = 0; i < 32; ++i) { - auto const color = context.color_table[i]; - TRY(context.color_table.try_append(color.darkened())); - } - - return {}; -} - -static ErrorOr reduce_ham_palette(ILBMLoadingContext& context) -{ - u8 bits = context.cmap_bits; - - dbgln_if(ILBM_DEBUG, "reduce palette planes={} bits={}", context.bm_header.planes, context.cmap_bits); - - if (bits > context.bm_header.planes) { - dbgln_if(ILBM_DEBUG, "need to reduce palette"); - bits -= (bits - context.bm_header.planes) + 2; - // bits shouldn't theorically be less than 4 bits in HAM mode. - if (bits < 4) - return Error::from_string_literal("Error while reducing CMAP for HAM: bits too small"); - - context.color_table.resize((context.color_table.size() >> bits)); - context.cmap_bits = bits; - } - - return {}; -} - -static ErrorOr decode_body_chunk(IFF::Chunk body_chunk, ILBMLoadingContext& context) -{ - dbgln_if(ILBM_DEBUG, "decode_body_chunk {}", body_chunk.size()); - - ByteBuffer pixel_data; - - if (context.bm_header.compression == CompressionType::ByteRun) { - auto plane_data = TRY(uncompress_byte_run(body_chunk.data(), context)); - if (context.format == Format::ILBM) - pixel_data = TRY(planar_to_chunky(plane_data, context)); - else - pixel_data = plane_data; - } else { - if (context.format == Format::ILBM) - pixel_data = TRY(planar_to_chunky(body_chunk.data(), context)); - else - pixel_data = TRY(ByteBuffer::copy(body_chunk.data().data(), body_chunk.size())); - } - - // Some files already have 64 colors defined in the palette, - // maybe for upward compatibility with 256 colors software/hardware. - // DPaint 4 & previous files only have 32 colors so the - // palette needs to be extended only for these files. - if (has_flag(context.viewport_mode, ViewportMode::EHB) && context.color_table.size() < 64) { - TRY(extend_ehb_palette(context)); - } else if (has_flag(context.viewport_mode, ViewportMode::HAM)) { - TRY(reduce_ham_palette(context)); - } - - context.bitmap = TRY(chunky_to_bitmap(context, pixel_data)); - - return {}; -} - -static ErrorOr decode_iff_chunks(ILBMLoadingContext& context) -{ - auto& chunks = context.chunks_cursor; - - dbgln_if(ILBM_DEBUG, "decode_iff_chunks"); - - while (!chunks.is_empty()) { - auto chunk = TRY(IFF::Chunk::decode_and_advance(chunks)); - if (chunk.id() == "CMAP"sv) { - // Some files (HAM mainly) have CMAP chunks larger than the planes they advertise: I'm not sure - // why but we should not return an error in this case. - - context.color_table = TRY(decode_cmap_chunk(chunk)); - context.cmap_bits = AK::ceil_log2(context.color_table.size()); - } else if (chunk.id() == "BODY"sv) { - if (context.color_table.is_empty() && context.bm_header.planes != 24) - return Error::from_string_literal("Decoding indexed BODY chunk without a color map is not currently supported"); - - // Apparently 32bit ilbm files exist: but I wasn't able to find any, - // nor is it documented anywhere, so let's make it clear it's not supported. - if (context.bm_header.planes != 24 && context.bm_header.planes > 8) - return Error::from_string_literal("Invalid number of bitplanes"); - - TRY(decode_body_chunk(chunk, context)); - context.state = ILBMLoadingContext::State::BitmapDecoded; - } else if (chunk.id() == "CRNG"sv) { - dbgln_if(ILBM_DEBUG, "Chunk:CRNG"); - } else if (chunk.id() == "CAMG"sv) { - context.viewport_mode = static_cast(AK::convert_between_host_and_big_endian(ByteReader::load32(chunk.data().data()))); - dbgln_if(ILBM_DEBUG, "Chunk:CAMG, Viewport={}, EHB={}, HAM={}", (u32)context.viewport_mode, has_flag(context.viewport_mode, ViewportMode::EHB), has_flag(context.viewport_mode, ViewportMode::HAM)); - } - } - - if (context.state != ILBMLoadingContext::State::BitmapDecoded) - return Error::from_string_literal("Missing body chunk"); - - return {}; -} - -static ErrorOr decode_bmhd_chunk(ILBMLoadingContext& context) -{ - context.chunks_cursor = context.data.slice(sizeof(IFF::FileHeader)); - auto first_chunk = TRY(IFF::Chunk::decode_and_advance(context.chunks_cursor)); - - if (first_chunk.id() != "BMHD"sv) - return Error::from_string_literal("IFFImageDecoderPlugin: Invalid chunk type, expected BMHD"); - - if (first_chunk.size() < sizeof(BMHDHeader)) - return Error::from_string_literal("IFFImageDecoderPlugin: Not enough data for header chunk"); - - context.bm_header = *bit_cast(first_chunk.data().data()); - - if (context.bm_header.mask >= MaskType::__Count) - return Error::from_string_literal("IFFImageDecoderPlugin: Unsupported mask type"); - - if (context.bm_header.compression >= CompressionType::__Count) - return Error::from_string_literal("IFFImageDecoderPlugin: Unsupported compression type"); - - context.pitch = ceil_div((u16)context.bm_header.width, (u16)16) * 2; - - context.state = ILBMLoadingContext::State::HeaderDecoded; - - dbgln_if(ILBM_DEBUG, "IFFImageDecoderPlugin: BMHD: {}x{} ({},{}), p={}, m={}, c={}", - context.bm_header.width, - context.bm_header.height, - context.bm_header.x, - context.bm_header.y, - context.bm_header.planes, - to_underlying(context.bm_header.mask), - to_underlying(context.bm_header.compression)); - - return {}; -} - -ILBMImageDecoderPlugin::ILBMImageDecoderPlugin(ReadonlyBytes data, NonnullOwnPtr context) - : m_context(move(context)) -{ - m_context->data = data; -} - -ILBMImageDecoderPlugin::~ILBMImageDecoderPlugin() = default; - -IntSize ILBMImageDecoderPlugin::size() -{ - return IntSize { m_context->bm_header.width, m_context->bm_header.height }; -} - -bool ILBMImageDecoderPlugin::sniff(ReadonlyBytes data) -{ - ILBMLoadingContext context; - context.data = data; - - return !decode_iff_ilbm_header(context).is_error(); -} - -ErrorOr> ILBMImageDecoderPlugin::create(ReadonlyBytes data) -{ - auto context = TRY(try_make()); - auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) ILBMImageDecoderPlugin(data, move(context)))); - TRY(decode_iff_ilbm_header(*plugin->m_context)); - TRY(decode_bmhd_chunk(*plugin->m_context)); - return plugin; -} - -ErrorOr ILBMImageDecoderPlugin::frame(size_t index, Optional) -{ - if (index > 0) - return Error::from_string_literal("ILBMImageDecoderPlugin: frame index must be 0"); - - if (m_context->state < ILBMLoadingContext::State::BitmapDecoded) - TRY(decode_iff_chunks(*m_context)); - - VERIFY(m_context->bitmap); - return ImageFrameDescriptor { m_context->bitmap, 0 }; -} - -} diff --git a/Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.h b/Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.h deleted file mode 100644 index 29c8fd70df3..00000000000 --- a/Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2023, Nicolas Ramz - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace Gfx { - -struct ILBMLoadingContext; - -class ILBMImageDecoderPlugin final : public ImageDecoderPlugin { -public: - static bool sniff(ReadonlyBytes); - static ErrorOr> create(ReadonlyBytes); - - virtual ~ILBMImageDecoderPlugin() override; - - virtual IntSize size() override; - - virtual ErrorOr frame(size_t index, Optional ideal_size = {}) override; - -private: - ILBMImageDecoderPlugin(ReadonlyBytes, NonnullOwnPtr); - NonnullOwnPtr m_context; -}; - -} diff --git a/Userland/Libraries/LibGfx/ImageFormats/ImageDecoder.cpp b/Userland/Libraries/LibGfx/ImageFormats/ImageDecoder.cpp index 926c7b5ce52..d7645c97ca7 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/ImageDecoder.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/ImageDecoder.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -31,7 +30,6 @@ static ErrorOr> probe_and_sniff_for_appropriate_plugi { BMPImageDecoderPlugin::sniff, BMPImageDecoderPlugin::create }, { GIFImageDecoderPlugin::sniff, GIFImageDecoderPlugin::create }, { ICOImageDecoderPlugin::sniff, ICOImageDecoderPlugin::create }, - { ILBMImageDecoderPlugin::sniff, ILBMImageDecoderPlugin::create }, { JPEG2000ImageDecoderPlugin::sniff, JPEG2000ImageDecoderPlugin::create }, { JPEGImageDecoderPlugin::sniff, JPEGImageDecoderPlugin::create }, { JPEGXLImageDecoderPlugin::sniff, JPEGXLImageDecoderPlugin::create },