summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGfx
diff options
context:
space:
mode:
authorNico Weber <thakis@chromium.org>2023-04-07 18:14:19 -0400
committerLinus Groh <mail@linusgroh.de>2023-04-08 19:24:13 +0200
commit1dab48001590f59aecfa2067cc6f47d739a5f4b2 (patch)
tree1fa1477d06055e91f262bca8dd20fa421304faad /Userland/Libraries/LibGfx
parent7309441b312c5dd8ba5d2cf7316aba83fc4e4f40 (diff)
downloadserenity-1dab48001590f59aecfa2067cc6f47d739a5f4b2.zip
LibGfx: Implement color index pixel bundling in webp decoder
See the lengthy comment added in this commit for details. With this, the webp lossless decoder is feature complete :^) (...except for bug fixes and performance improvements, as always.)
Diffstat (limited to 'Userland/Libraries/LibGfx')
-rw-r--r--Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp91
1 files changed, 71 insertions, 20 deletions
diff --git a/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp
index 443041746e..87083ec6f1 100644
--- a/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp
+++ b/Userland/Libraries/LibGfx/ImageFormats/WebPLoader.cpp
@@ -1086,12 +1086,29 @@ public:
static ErrorOr<NonnullOwnPtr<ColorIndexingTransform>> read(WebPLoadingContext&, LittleEndianInputBitStream&);
virtual ErrorOr<NonnullRefPtr<Bitmap>> transform(NonnullRefPtr<Bitmap>) 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.
+ // If the palette is small enough, several output pixels are bundled into a single input pixel.
+ // If the palette has just 2 colors, every index needs just a single bit, and the 8 bits of the green channel of one input pixel can encode 8 output pixels.
+ // If the palette has 3 or 4 colors, every index needs 2 bits and every pixel can encode 4 output pixels.
+ // If the palette has 5 to 16 colors, every index needs 4 bits and every pixel can encode 2 output pixels.
+ // This returns how many output pixels one input pixel can encode after the color indexing transform.
+ //
+ // The spec isn't very explicit about this, but this affects all images after the color indexing transform:
+ // If a webp file contains a 32x32 image and it contains a color indexing transform with a 4-color palette, then the in-memory size of all images
+ // after the color indexing transform assume a bitmap size of (32/4)x32 = 8x32.
+ // That is, the sizes of transforms after the color indexing transform are computed relative to the size 8x32,
+ // the main image's meta prefix image's size (if present) is comptued relative to the size 8x32,
+ // the main image is 8x32, and only applying the color indexing transform resizes the image back to 32x32.
+ int pixels_per_pixel() const { return m_pixels_per_pixel; }
+
private:
- explicit ColorIndexingTransform(NonnullRefPtr<Bitmap> palette_bitmap)
- : m_palette_bitmap(palette_bitmap)
+ ColorIndexingTransform(unsigned pixels_per_pixel, NonnullRefPtr<Bitmap> palette_bitmap)
+ : m_pixels_per_pixel(pixels_per_pixel)
+ , m_palette_bitmap(palette_bitmap)
{
}
+ int m_pixels_per_pixel;
NonnullRefPtr<Bitmap> m_palette_bitmap;
};
@@ -1102,35 +1119,64 @@ ErrorOr<NonnullOwnPtr<ColorIndexingTransform>> ColorIndexingTransform::read(WebP
int color_table_size = TRY(bit_stream.read_bits(8)) + 1;
dbgln_if(WEBP_DEBUG, "colorindexing color_table_size {}", color_table_size);
- // "When the color table is small (equal to or less than 16 colors), several pixels are bundled into a single pixel...."
- // FIXME: Implement support for this pixel packing.
- if (color_table_size <= 16)
- return Error::from_string_literal("WebPImageDecoderPlugin: COLOR_INDEXING_TRANSFORM pixel packing not yet implemented");
-
IntSize palette_image_size { color_table_size, 1 };
auto palette_bitmap = TRY(decode_webp_chunk_VP8L_image(context, ImageKind::EntropyCoded, BitmapFormat::BGRA8888, palette_image_size, bit_stream));
+ // "When the color table is small (equal to or less than 16 colors), several pixels are bundled into a single pixel...."
+ int pixels_per_pixel = 1;
+ if (color_table_size <= 2)
+ pixels_per_pixel = 8;
+ else if (color_table_size <= 4)
+ pixels_per_pixel = 4;
+ else if (color_table_size <= 16)
+ pixels_per_pixel = 2;
+
// "The color table is always subtraction-coded to reduce image entropy. [...] In decoding, every final color in the color table
// can be obtained by adding the previous color component values by each ARGB component separately,
// and storing the least significant 8 bits of the result."
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(move(palette_bitmap)));
+ return adopt_nonnull_own_or_enomem(new (nothrow) ColorIndexingTransform(pixels_per_pixel, move(palette_bitmap)));
}
ErrorOr<NonnullRefPtr<Bitmap>> ColorIndexingTransform::transform(NonnullRefPtr<Bitmap> bitmap)
{
// FIXME: If this is the last transform, consider returning an Indexed8 bitmap here?
- for (ARGB32& pixel : *bitmap) {
- // "The inverse transform for the image is simply replacing the pixel values (which are indices to the color table)
- // with the actual color table values. The indexing is done based on the green component of the ARGB color. [...]
- // If the index is equal or larger than color_table_size, the argb color value should be set to 0x00000000 (transparent black)."
- u8 index = Color::from_argb(pixel).green();
- pixel = index < m_palette_bitmap->width() ? m_palette_bitmap->scanline(0)[index] : 0;
+ if (pixels_per_pixel() == 1) {
+ for (ARGB32& pixel : *bitmap) {
+ // "The inverse transform for the image is simply replacing the pixel values (which are indices to the color table)
+ // with the actual color table values. The indexing is done based on the green component of the ARGB color. [...]
+ // If the index is equal or larger than color_table_size, the argb color value should be set to 0x00000000 (transparent black)."
+ u8 index = Color::from_argb(pixel).green();
+ pixel = index < m_palette_bitmap->width() ? m_palette_bitmap->scanline(0)[index] : 0;
+ }
+ return bitmap;
}
- return bitmap;
+
+ // Pixel bundling case.
+ IntSize unbundled_size = { bitmap->size().width() * pixels_per_pixel(), bitmap->size().height() };
+ auto new_bitmap = TRY(Bitmap::create(BitmapFormat::BGRA8888, unbundled_size));
+
+ unsigned bits_per_pixel = 8 / pixels_per_pixel();
+ unsigned pixel_mask = (1 << bits_per_pixel) - 1;
+ for (int y = 0; y < bitmap->height(); ++y) {
+ ARGB32* bitmap_scanline = bitmap->scanline(y);
+ ARGB32* new_bitmap_scanline = new_bitmap->scanline(y);
+
+ for (int x = 0, new_x = 0; x < bitmap->width(); ++x, new_x += pixels_per_pixel()) {
+ u8 indexes = Color::from_argb(bitmap_scanline[x]).green();
+
+ for (int i = 0; i < pixels_per_pixel(); ++i) {
+ u8 index = indexes & pixel_mask;
+ new_bitmap_scanline[new_x + i] = index < m_palette_bitmap->width() ? m_palette_bitmap->scanline(0)[index] : 0;
+ indexes >>= bits_per_pixel;
+ }
+ }
+ }
+
+ return new_bitmap;
}
}
@@ -1162,6 +1208,8 @@ static ErrorOr<void> decode_webp_chunk_VP8L(WebPLoadingContext& context, Chunk c
// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#4_transformations
// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#72_structure_of_transforms
+ auto stored_size = context.size.value();
+
// optional-transform = (%b1 transform optional-transform) / %b0
// "Each transform is allowed to be used only once."
u8 seen_transforms = 0;
@@ -1195,22 +1243,25 @@ static ErrorOr<void> decode_webp_chunk_VP8L(WebPLoadingContext& context, Chunk c
switch (transform_type) {
case PREDICTOR_TRANSFORM:
- TRY(transforms.try_append(TRY(PredictorTransform::read(context, bit_stream, context.size.value()))));
+ TRY(transforms.try_append(TRY(PredictorTransform::read(context, bit_stream, stored_size))));
break;
case COLOR_TRANSFORM:
- TRY(transforms.try_append(TRY(ColorTransform::read(context, bit_stream, context.size.value()))));
+ TRY(transforms.try_append(TRY(ColorTransform::read(context, bit_stream, stored_size))));
break;
case SUBTRACT_GREEN_TRANSFORM:
TRY(transforms.try_append(TRY(try_make<SubtractGreenTransform>())));
break;
- case COLOR_INDEXING_TRANSFORM:
- TRY(transforms.try_append(TRY(ColorIndexingTransform::read(context, bit_stream))));
+ 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());
+ TRY(transforms.try_append(move(color_indexing_transform)));
break;
}
+ }
}
auto format = vp8l_header.is_alpha_used ? BitmapFormat::BGRA8888 : BitmapFormat::BGRx8888;
- context.bitmap = TRY(decode_webp_chunk_VP8L_image(context, ImageKind::SpatiallyCoded, format, context.size.value(), bit_stream));
+ context.bitmap = TRY(decode_webp_chunk_VP8L_image(context, ImageKind::SpatiallyCoded, format, stored_size, bit_stream));
// Transforms have to be applied in the reverse order they appear in in the file.
// (As far as I can tell, this isn't mentioned in the spec.)