diff --git a/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp index cfa6f3a832..e10523f6d3 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp @@ -923,6 +923,85 @@ ARGB32 PredictorTransform::inverse_transform(ARGB32 pixel, ARGB32 prediction) .value(); } +// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#42_color_transform +class ColorTransform : public Transform { +public: + static ErrorOr> read(WebPLoadingContext&, LittleEndianInputBitStream&, IntSize const& image_size); + virtual ErrorOr transform(Bitmap&) override; + +private: + ColorTransform(int size_bits, NonnullRefPtr color_bitmap) + : m_size_bits(size_bits) + , m_color_bitmap(move(color_bitmap)) + { + } + + static i8 ColorTransformDelta(i8 transform, i8 color) + { + return (transform * color) >> 5; + } + + static ARGB32 inverse_transform(ARGB32 pixel, ARGB32 transform); + + int m_size_bits; + NonnullRefPtr m_color_bitmap; +}; + +ErrorOr> ColorTransform::read(WebPLoadingContext& context, LittleEndianInputBitStream& bit_stream, IntSize const& image_size) +{ + // color-image = 3BIT ; sub-pixel code + // entropy-coded-image + int size_bits = TRY(bit_stream.read_bits(3)) + 2; + int block_size = 1 << size_bits; + IntSize color_image_size { ceil_div(image_size.width(), block_size), ceil_div(image_size.height(), block_size) }; + + auto color_bitmap = TRY(decode_webp_chunk_VP8L_image(context, ImageKind::EntropyCoded, BitmapFormat::BGRx8888, color_image_size, bit_stream)); + + return adopt_nonnull_own_or_enomem(new (nothrow) ColorTransform(size_bits, move(color_bitmap))); +} + +ErrorOr ColorTransform::transform(Bitmap& bitmap) +{ + for (int y = 0; y < bitmap.height(); ++y) { + ARGB32* bitmap_scanline = bitmap.scanline(y); + + int color_y = y >> m_size_bits; + ARGB32* color_scanline = m_color_bitmap->scanline(color_y); + + for (int x = 0; x < bitmap.width(); ++x) { + int color_x = x >> m_size_bits; + bitmap_scanline[x] = inverse_transform(bitmap_scanline[x], color_scanline[color_x]); + } + } + return {}; +} + +ARGB32 ColorTransform::inverse_transform(ARGB32 pixel, ARGB32 transform) +{ + // https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#51_roles_of_image_data + // "Each ColorTransformElement 'cte' is treated as a pixel whose alpha component is 255, + // red component is cte.red_to_blue, green component is cte.green_to_blue + // and blue component is cte.green_to_red." + auto transform_color = Color::from_argb(transform); + i8 red_to_blue = static_cast(transform_color.red()); + i8 green_to_blue = static_cast(transform_color.green()); + i8 green_to_red = static_cast(transform_color.blue()); + + auto pixel_color = Color::from_argb(pixel); + + // "Transformed values of red and blue components" + int tmp_red = pixel_color.red(); + int green = pixel_color.green(); + int tmp_blue = pixel_color.blue(); + + // "Applying the inverse transform is just adding the color transform deltas" + tmp_red += ColorTransformDelta(green_to_red, green); + tmp_blue += ColorTransformDelta(green_to_blue, green); + tmp_blue += ColorTransformDelta(red_to_blue, tmp_red & 0xff); + + return Color(tmp_red & 0xff, green, tmp_blue & 0xff, pixel_color.alpha()).value(); +} + // https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#43_subtract_green_transform class SubtractGreenTransform : public Transform { public: @@ -1005,7 +1084,8 @@ static ErrorOr decode_webp_chunk_VP8L(WebPLoadingContext& context, Chunk c TRY(transforms.try_append(TRY(PredictorTransform::read(context, bit_stream, context.size.value())))); break; case COLOR_TRANSFORM: - return context.error("WebPImageDecoderPlugin: VP8L COLOR_TRANSFORM handling not yet implemented"); + TRY(transforms.try_append(TRY(ColorTransform::read(context, bit_stream, context.size.value())))); + break; case SUBTRACT_GREEN_TRANSFORM: TRY(transforms.try_append(TRY(try_make()))); break;