From 8d68f942825d2bbc5c473854a572681a8e21c3b0 Mon Sep 17 00:00:00 2001 From: Nicolas Ramz Date: Wed, 13 Sep 2023 12:17:58 +0200 Subject: [PATCH] LibGfx/ILBMLoader: Add HAM6/HAM8 support --- Tests/LibGfx/TestImageDecoder.cpp | 11 ++++ Tests/LibGfx/test-inputs/ilbm/ham6.iff | Bin 0 -> 37696 bytes .../LibGfx/ImageFormats/ILBMLoader.cpp | 62 ++++++++++++++++-- 3 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 Tests/LibGfx/test-inputs/ilbm/ham6.iff diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index fbec22eab9..7135720d63 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -138,6 +138,17 @@ TEST_CASE(test_ilbm_uncompressed) EXPECT_EQ(frame.image->get_pixel(8, 0), Gfx::Color(0xee, 0xbb, 0, 255)); } +TEST_CASE(test_ilbm_ham6) +{ + auto file = MUST(Core::MappedFile::map(TEST_INPUT("ilbm/ham6.iff"sv))); + EXPECT(Gfx::ILBMImageDecoderPlugin::sniff(file->bytes())); + auto plugin_decoder = MUST(Gfx::ILBMImageDecoderPlugin::create(file->bytes())); + + auto frame = 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_malformed_header) { Array test_inputs = { diff --git a/Tests/LibGfx/test-inputs/ilbm/ham6.iff b/Tests/LibGfx/test-inputs/ilbm/ham6.iff new file mode 100644 index 0000000000000000000000000000000000000000..6f0678ad8fd86e51c1b1bf697955ced444f64f34 GIT binary patch literal 37696 zcmZ?s5AtPTm~7$c*2z{z#ziNz{mgsY+#z33&eMJ^mS)oU|?Zj;9zj}bqru& zU@%}|Vc}rs6y@d<6PHLA5ZEOopsl5~C?Md5q{wp#;k!vmU#3j?^5@SV76OV5(fsRhOZ0^@jw2v*6)9G|9AICsG5iWL8@4nI7_}dSNRd5 zqWaIXy*t07s$f5}GmVQek3r-!14I1x|BTP>ze6#Bec8*PrWth%4N&=Q?~&!dGq5rR zU7mRCz$XTV_;3GN*4O|4yZ;l40USyKA78zcIMC4W1*+!%_wVRxPD*WjJhQ_=AqAx7 z>wngN@Bi1w-~WPS(02w7_pjgo|8HP01AF+}f3{z@Zms{~5&i2Sdw0hR;G^6Iex#nWNRS6;IJL!dZy1h2mUfJya%xv|LOl>V37X`HkRkd z&yN?FwGO@66L9&+0gzjN{O1w>rCeWib?x`{tf7DFfBgUUpXJTGL*~E#{b%?MGMe+> z?H~^p1`a0vdXTk@Dog$_FtCA*KCV!X{m-x;WD-Zc4+9ee3!`HGxBuV%Gku)2;Wq{KBbPbg*1b%#D8`vklh>};e|P^{(_8PRb*%@R{LDZ4-@m{A8NP$`bC&;e zV`OAt6mXFPahVzxH2h{@;03W6e=PpRz`zVKndklS7>jqQt$P;iIG_%Wx*z|!e>Hvg z{k*R}>;38l-rxU!|IhZbK+yi@zq$YZGraru|NDRLzxTghU}H>T;9v;c4^qJNFU9K@ z0|O)2_$59+85run|7S4x#lR=c_)mI+wrAU!Q(HB7bim>L>p$PO|F2B%KHL9)|MutK zSDDuT0NL?-0c-t_1l49pOF1S2)~ zuRr>2e&>G6{ky;R?%nr>)9Zi!|M8#WZviX+7X=0; z|9}4(et}G5fB%08_x=BWH~eE@aQ^=P$A9L3qO%-+GB7fNLx5@OtcD*95UYe7T!Q1W zQl%?R!;;fhXRR?2`f&i{rQiQKfA8B8K6~N6cYp8wyUTKWjcRQDum3;(vpj9!vH$vy zfx-RXf5t!dpft?%=LFZk{|r0+F)*^_|M>soKhqD@OOV9#<3H0s)yv-*80>%iXE6A~ zAjH}y_VbpGO6HjaQ@1s_X#yQj!6EqPzu>>xTF=t!#s91Czx#Xp_T8VoV9&AqQ_LxQ z^N)dn^WT5Q-RnW7v)umwb*1pX|E%8_I2ahDK|)M#jzxWEV2}s1zZ{GC&cMJ9u~6_% z`|8J*3w(nK`~R<6@|+wTIr7FgMNJ9y;k* z?$*_2Dsz}%q50kZOZ@+@pI_~+-FrLl?pxJQx?tP(wwX=+{fB|o|NsB5F7`kFfBVlk zhvm=ZtD_xsf44Etium~vM~ zN3Awo>XHsp8~^9OP~G)OukP>7UA^{qblJDJ+uqkd+Yh$(&PzS}AAcAaxc~iUywDCZ zlx6e(e+le={TFZ5t*b2b9ey(~#Q*s(T)x_C-tX#Z+xP$3U3Tx?-P`3quJ8Z-|JQ%^e;d+r z>K-uEL6htQ#-IPc|7ZGhQ2x(LW8JePv)^ zgjmhP_}@A$NAB;^z{Q@cv;N*9e+Gwt42%lZ zATj3OYJ47l7#Ns-F)+x31^%k>`Fvquu!mSKEPvzX#|=fdlODHSjoP|x)vdMdO$pEd z|2DB~{oTxM_xIKA{GRvr?)GY*F0j7^AGn6j{Kc^U|L+C{hWY>h|7Oq!nauL~|Nm$0 zzy32OF#Tg-5C;o=*|n;G|ndT|Nnni z^w)pJ7mWYF=KuQ7dUMh11V(|s3=E(�^r#{BqIjfG-RT>>&I9GKes4aEldLps}`c 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 |nkk1E7?hxn+{C~z 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 #include #include +#include #include #include @@ -89,6 +90,9 @@ struct ILBMLoadingContext { Vector color_table; + // number of bits needed to describe current palette + u8 cmap_bits; + RefPtr bitmap; BMHDHeader bm_header; @@ -132,9 +136,32 @@ static ErrorOr> chunky_to_bitmap(ILBMLoadingContext& context dbgln_if(ILBM_DEBUG, "created Bitmap {}x{}", width, height); 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) { u8 index = chunky[(width * row) + col]; - bitmap->set_pixel(col, row, context.color_table[index]); + if (index < context.color_table.size()) { + color = context.color_table[index]; + } 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 = (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 = (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); } } @@ -237,6 +264,26 @@ static ErrorOr extend_ehb_palette(ILBMLoadingContext& context) 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(Chunk body_chunk, ILBMLoadingContext& context) { dbgln_if(ILBM_DEBUG, "decode_body_chunk {}", body_chunk.data.size()); @@ -250,12 +297,14 @@ static ErrorOr decode_body_chunk(Chunk body_chunk, ILBMLoadingContext& con pixel_data = TRY(planar_to_chunky(body_chunk.data, context)); } - // Some files already have 64 colours defined in the palette, - // maybe for upward compatibility with 256 colours software/hardware. - // DPaint 4 & previous files only have 32 colours so the + // 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)); @@ -303,10 +352,11 @@ static ErrorOr decode_iff_chunks(ILBMLoadingContext& context) while (!chunks.is_empty()) { auto chunk = TRY(decode_iff_advance_chunk(chunks)); if (chunk.type == FourCC("CMAP")) { - if (chunk.data.size() != (1ul << context.bm_header.planes) * 3) - return Error::from_string_literal("Invalid CMAP chunk size"); + // 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.type == FourCC("BODY")) { if (context.color_table.is_empty()) return Error::from_string_literal("Decoding BODY chunk without a color map is not currently supported");