From 95e35b7f5ecc4528a2feda60b371ddae71a4203e Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Sat, 8 Apr 2023 17:50:54 -0400 Subject: [PATCH] LibGfx: Correctly decode webp lossless with small palette and odd width WebP lossless files that use a color indexing transform with <= 16 colors use pixel bundling to pack 2, 4, or 8 pixels into a single pixel. If the image's width doesn't happen to be an exact multiple of the bundling factor, we need to: 1. Use ceil_div() instead of just dividing the width by the bundling factor 2. Remember the original width and use it instead of computing reduced width times bundling factor This does these changes, and adds a simple test for it -- it at least checks that the decoded images have the right size. (I created these images myself in Photoshop, and used the same technique as for Tests/LibGfx/test-inputs/catdog-alert-*.webp to create images with a certain number of colors.) --- Tests/LibGfx/TestImageDecoder.cpp | 21 ++++++++++++++++++ .../width11-height11-colors15.webp | Bin 0 -> 228 bytes .../test-inputs/width11-height11-colors2.webp | Bin 0 -> 172 bytes .../test-inputs/width11-height11-colors3.webp | Bin 0 -> 190 bytes .../LibGfx/ImageFormats/WebPLoader.cpp | 17 ++++++++------ 5 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 Tests/LibGfx/test-inputs/width11-height11-colors15.webp create mode 100644 Tests/LibGfx/test-inputs/width11-height11-colors2.webp create mode 100644 Tests/LibGfx/test-inputs/width11-height11-colors3.webp diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index c8e2ced9a3..4a843d37e0 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -420,6 +420,27 @@ TEST_CASE(test_webp_simple_lossless_color_index_transform_pixel_bundling) } } +TEST_CASE(test_webp_simple_lossless_color_index_transform_pixel_bundling_odd_width) +{ + StringView file_names[] = { + "width11-height11-colors2.webp"sv, + "width11-height11-colors3.webp"sv, + "width11-height11-colors15.webp"sv, + }; + + for (auto file_name : file_names) { + auto file = MUST(Core::MappedFile::map(MUST(String::formatted("{}{}", TEST_INPUT(""), file_name)))); + auto plugin_decoder = MUST(Gfx::WebPImageDecoderPlugin::create(file->bytes())); + EXPECT(plugin_decoder->initialize()); + + EXPECT_EQ(plugin_decoder->frame_count(), 1u); + EXPECT_EQ(plugin_decoder->size(), Gfx::IntSize(11, 11)); + + auto frame = MUST(plugin_decoder->frame(0)); + EXPECT_EQ(frame.image->size(), Gfx::IntSize(11, 11)); + } +} + TEST_CASE(test_webp_extended_lossless_animated) { auto file = MUST(Core::MappedFile::map(TEST_INPUT("extended-lossless-animated.webp"sv))); diff --git a/Tests/LibGfx/test-inputs/width11-height11-colors15.webp b/Tests/LibGfx/test-inputs/width11-height11-colors15.webp new file mode 100644 index 0000000000000000000000000000000000000000..cfb395d11a6a937a97ca1f7eafa71ebf69483b46 GIT binary patch literal 228 zcmWIYbaT7Iz`zjh>J$(bU=hK^00CSK3|tHhVF4CC84L^z`dkf64CMy@v!py5i?4jY zq|9uZD}8X~yZ<#ZdH)_SsDF3k(ximsWM@UjOA{9{6zAN2_2qqyi-%&XNDzcbJ$(bU=hK^00CSK3|tHhVF4CC`V0&V`dkf64EzTF&v*Xjx%0W0gtP|d|}l^8WoC>>#VDgQ3V<%|6!sZA?hY_33nU_C6(U^Z_+QffG(00S!{mJ$(bU=hK^00CSK3|tHhVF4CCjtmS8`dkf64B`g=lMb33*{a{g z!j#FO)gn6YzlVQg)3)9@lPoy)cRdnsvCd$BsQ#$qANwi?R^tOsrmLpE+~WHCi~D~T zhJav4Pd^3*1`8)oU*@-9I|LX!7#J8B85m(SNRCAVEXTvZz`$hx0n7&LVQ~htc>|JC M!x;q_SQ)`w0Iwb^lmGw# literal 0 HcmV?d00001 diff --git a/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp index 87083ec6f1..d4c8a28748 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp @@ -1083,7 +1083,7 @@ ErrorOr> SubtractGreenTransform::transform(NonnullRefPtr> read(WebPLoadingContext&, LittleEndianInputBitStream&); + static ErrorOr> read(WebPLoadingContext&, LittleEndianInputBitStream&, int original_width); virtual ErrorOr> transform(NonnullRefPtr) override; // For a color indexing transform, the green channel of the source image is used as the index into a palette to produce an output color. @@ -1102,17 +1102,19 @@ public: int pixels_per_pixel() const { return m_pixels_per_pixel; } private: - ColorIndexingTransform(unsigned pixels_per_pixel, NonnullRefPtr palette_bitmap) + ColorIndexingTransform(int pixels_per_pixel, int original_width, NonnullRefPtr palette_bitmap) : m_pixels_per_pixel(pixels_per_pixel) + , m_original_width(original_width) , m_palette_bitmap(palette_bitmap) { } int m_pixels_per_pixel; + int m_original_width; NonnullRefPtr m_palette_bitmap; }; -ErrorOr> ColorIndexingTransform::read(WebPLoadingContext& context, LittleEndianInputBitStream& bit_stream) +ErrorOr> ColorIndexingTransform::read(WebPLoadingContext& context, LittleEndianInputBitStream& bit_stream, int original_width) { // color-indexing-image = 8BIT ; color count // entropy-coded-image @@ -1137,7 +1139,7 @@ ErrorOr> ColorIndexingTransform::read(WebP for (ARGB32* pixel = palette_bitmap->begin() + 1; pixel != palette_bitmap->end(); ++pixel) *pixel = add_argb32(*pixel, pixel[-1]); - return adopt_nonnull_own_or_enomem(new (nothrow) ColorIndexingTransform(pixels_per_pixel, move(palette_bitmap))); + return adopt_nonnull_own_or_enomem(new (nothrow) ColorIndexingTransform(pixels_per_pixel, original_width, move(palette_bitmap))); } ErrorOr> ColorIndexingTransform::transform(NonnullRefPtr bitmap) @@ -1156,7 +1158,8 @@ ErrorOr> ColorIndexingTransform::transform(NonnullRefPtrsize().width() * pixels_per_pixel(), bitmap->size().height() }; + VERIFY(ceil_div(m_original_width, pixels_per_pixel()) == bitmap->size().width()); + IntSize unbundled_size = { m_original_width, bitmap->size().height() }; auto new_bitmap = TRY(Bitmap::create(BitmapFormat::BGRA8888, unbundled_size)); unsigned bits_per_pixel = 8 / pixels_per_pixel(); @@ -1252,8 +1255,8 @@ static ErrorOr decode_webp_chunk_VP8L(WebPLoadingContext& context, Chunk c TRY(transforms.try_append(TRY(try_make()))); break; case COLOR_INDEXING_TRANSFORM: { - auto color_indexing_transform = TRY(ColorIndexingTransform::read(context, bit_stream)); - stored_size.set_width(stored_size.width() / color_indexing_transform->pixels_per_pixel()); + auto color_indexing_transform = TRY(ColorIndexingTransform::read(context, bit_stream, stored_size.width())); + stored_size.set_width(ceil_div(stored_size.width(), color_indexing_transform->pixels_per_pixel())); TRY(transforms.try_append(move(color_indexing_transform))); break; }