From cd6c7f3fc482611004449cde32cc1999a958cc99 Mon Sep 17 00:00:00 2001 From: Nicolas Ramz Date: Fri, 8 Dec 2023 13:50:53 +0100 Subject: [PATCH] LibGfx/ILBMLoader: Add support for PC DeluxePaint files --- Base/res/apps/ImageViewer.af | 2 +- Tests/LibGfx/TestImageDecoder.cpp | 11 ++++++++ Tests/LibGfx/test-inputs/ilbm/serenity.lbm | Bin 0 -> 112910 bytes Userland/Libraries/LibCore/MimeData.cpp | 2 +- Userland/Libraries/LibGfx/Bitmap.h | 1 + .../LibGfx/ImageFormats/ILBMLoader.cpp | 24 +++++++++++++++--- 6 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 Tests/LibGfx/test-inputs/ilbm/serenity.lbm diff --git a/Base/res/apps/ImageViewer.af b/Base/res/apps/ImageViewer.af index fd99c575d5..60298be3f8 100644 --- a/Base/res/apps/ImageViewer.af +++ b/Base/res/apps/ImageViewer.af @@ -4,4 +4,4 @@ Executable=/bin/ImageViewer Category=Graphics [Launcher] -FileTypes=bmp,dds,gif,ico,iff,jpeg,jpg,jxl,pbm,pgm,png,ppm,qoi,tga,tiff,tif,tvg +FileTypes=bmp,dds,gif,ico,iff,jpeg,jpg,jxl,lbm,pbm,pgm,png,ppm,qoi,tga,tiff,tif,tvg diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index 40479c0eb3..796f8ed592 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -149,6 +149,17 @@ TEST_CASE(test_ilbm_ham6) EXPECT_EQ(frame.image->get_pixel(77, 107), Gfx::Color(0xf0, 0x40, 0x40, 0xff)); } +TEST_CASE(test_ilbm_dos) +{ + auto file = MUST(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_ilbm_malformed_header) { Array test_inputs = { diff --git a/Tests/LibGfx/test-inputs/ilbm/serenity.lbm b/Tests/LibGfx/test-inputs/ilbm/serenity.lbm new file mode 100644 index 0000000000000000000000000000000000000000..b44a31d53bedfafa2bfdd029bc3145ddbed0a1f7 GIT binary patch literal 112910 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 literal 0 HcmV?d00001 diff --git a/Userland/Libraries/LibCore/MimeData.cpp b/Userland/Libraries/LibCore/MimeData.cpp index 0dcccf426a..9a68f9f39c 100644 --- a/Userland/Libraries/LibCore/MimeData.cpp +++ b/Userland/Libraries/LibCore/MimeData.cpp @@ -123,7 +123,7 @@ static Array const s_registered_mime_type = { MimeType { .name = "image/vnd.ms-dds"sv, .common_extensions = { ".dds"sv }, .description = "DDS image data"sv, .magic_bytes = Vector { 'D', 'D', 'S', ' ' } }, MimeType { .name = "image/webp"sv, .common_extensions = { ".webp"sv }, .description = "WebP image data"sv, .magic_bytes = Vector { 'W', 'E', 'B', 'P' }, .offset = 8 }, MimeType { .name = "image/x-icon"sv, .common_extensions = { ".ico"sv }, .description = "ICO image data"sv }, - MimeType { .name = "image/x-ilbm"sv, .common_extensions = { ".iff"sv }, .description = "Interleaved bitmap image data"sv, .magic_bytes = Vector { 0x46, 0x4F, 0x52, 0x4F } }, + MimeType { .name = "image/x-ilbm"sv, .common_extensions = { ".iff"sv, ".lbm"sv }, .description = "Interleaved bitmap image data"sv, .magic_bytes = Vector { 0x46, 0x4F, 0x52, 0x4F } }, MimeType { .name = "image/x-portable-bitmap"sv, .common_extensions = { ".pbm"sv }, .description = "PBM image data"sv, .magic_bytes = Vector { 0x50, 0x31, 0x0A } }, MimeType { .name = "image/x-portable-graymap"sv, .common_extensions = { ".pgm"sv }, .description = "PGM image data"sv, .magic_bytes = Vector { 0x50, 0x32, 0x0A } }, MimeType { .name = "image/x-portable-pixmap"sv, .common_extensions = { ".ppm"sv }, .description = "PPM image data"sv, .magic_bytes = Vector { 0x50, 0x33, 0x0A } }, diff --git a/Userland/Libraries/LibGfx/Bitmap.h b/Userland/Libraries/LibGfx/Bitmap.h index 75369b2dde..33afb3fe62 100644 --- a/Userland/Libraries/LibGfx/Bitmap.h +++ b/Userland/Libraries/LibGfx/Bitmap.h @@ -25,6 +25,7 @@ __ENUMERATE_IMAGE_FORMAT(jpeg, ".jpeg") \ __ENUMERATE_IMAGE_FORMAT(jpeg, ".jpg") \ __ENUMERATE_IMAGE_FORMAT(jxl, ".jxl") \ + __ENUMERATE_IMAGE_FORMAT(iff, ".lbm") \ __ENUMERATE_IMAGE_FORMAT(pbm, ".pbm") \ __ENUMERATE_IMAGE_FORMAT(pgm, ".pgm") \ __ENUMERATE_IMAGE_FORMAT(png, ".png") \ diff --git a/Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.cpp index f00832c0b0..4eb005fe37 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/ILBMLoader.cpp @@ -46,6 +46,13 @@ enum class ViewportMode : u32 { HAM = 0x800 }; +enum class Format : u8 { + // Amiga interleaved format + ILBM = 0, + // PC-DeluxePaint chunky format + PBM = 1 +}; + AK_ENUM_BITWISE_OPERATORS(ViewportMode); struct ChunkHeader { @@ -96,6 +103,8 @@ struct ILBMLoadingContext { RefPtr bitmap; BMHDHeader bm_header; + + Format format; }; static ErrorOr decode_iff_ilbm_header(ILBMLoadingContext& context) @@ -107,9 +116,12 @@ static ErrorOr decode_iff_ilbm_header(ILBMLoadingContext& context) return Error::from_string_literal("Missing IFF header"); auto& header = *bit_cast(context.data.data()); - if (header.form != FourCC("FORM") || header.format != FourCC("ILBM")) + + if (header.form != FourCC("FORM") || (header.format != FourCC("ILBM") && header.format != FourCC("PBM "))) return Error::from_string_literal("Invalid IFF-ILBM header"); + context.format = header.format == FourCC("ILBM") ? Format::ILBM : Format::PBM; + return {}; } @@ -292,9 +304,15 @@ static ErrorOr decode_body_chunk(Chunk body_chunk, ILBMLoadingContext& con if (context.bm_header.compression == CompressionType::ByteRun) { auto plane_data = TRY(uncompress_byte_run(body_chunk.data, context)); - pixel_data = TRY(planar_to_chunky(plane_data, context)); + if (context.format == Format::ILBM) + pixel_data = TRY(planar_to_chunky(plane_data, context)); + else + pixel_data = plane_data; } else { - pixel_data = TRY(planar_to_chunky(body_chunk.data, context)); + 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.data.size())); } // Some files already have 64 colors defined in the palette,