Skip to content

Commit d988b6f

Browse files
nicoawesomekling
authored andcommitted
LibGfx/WebPWriter+TestImageWriter: Fix bugs writing VP8X header
Two bugs: 1. Correctly set bits in VP8X header. Turns out these were set in the wrong order. 2. Correctly set the `has_alpha` flag. Also add a test for writing webp files with icc data. With the additional checks in other commits in this PR, this test catches the bug in WebPWriter. Rearrange some existing functions to make it easier to write this test: * Extract encode_bitmap() from get_roundtrip_bitmap(). encode_bitmap() allows passing extra_args that the test uses to pass in ICC data. * Extract expect_bitmaps_equal() from test_roundtrip()
1 parent 0805319 commit d988b6f

File tree

2 files changed

+55
-22
lines changed

2 files changed

+55
-22
lines changed

Tests/LibGfx/TestImageWriter.cpp

+39-14
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
#include <AK/MemoryStream.h>
88
#include <LibGfx/Bitmap.h>
9+
#include <LibGfx/ICC/BinaryWriter.h>
10+
#include <LibGfx/ICC/Profile.h>
11+
#include <LibGfx/ICC/WellKnownProfiles.h>
912
#include <LibGfx/ImageFormats/BMPLoader.h>
1013
#include <LibGfx/ImageFormats/BMPWriter.h>
1114
#include <LibGfx/ImageFormats/JPEGLoader.h>
@@ -37,31 +40,38 @@ static ErrorOr<NonnullRefPtr<Gfx::Bitmap>> expect_single_frame_of_size(Gfx::Imag
3740
return frame;
3841
}
3942

40-
template<class Writer, class Loader>
41-
static ErrorOr<NonnullRefPtr<Gfx::Bitmap>> get_roundtrip_bitmap(Gfx::Bitmap const& bitmap)
43+
template<class Writer, class... ExtraArgs>
44+
static ErrorOr<ByteBuffer> encode_bitmap(Gfx::Bitmap const& bitmap, ExtraArgs... extra_args)
4245
{
43-
ByteBuffer encoded_data;
44-
if constexpr (requires(AllocatingMemoryStream stream) { Writer::encode(stream, bitmap); }) {
46+
if constexpr (requires(AllocatingMemoryStream stream) { Writer::encode(stream, bitmap, extra_args...); }) {
4547
AllocatingMemoryStream stream;
46-
TRY(Writer::encode(stream, bitmap));
47-
encoded_data = TRY(stream.read_until_eof());
48+
TRY(Writer::encode(stream, bitmap, extra_args...));
49+
return stream.read_until_eof();
4850
} else {
49-
encoded_data = TRY(Writer::encode(bitmap));
51+
return Writer::encode(bitmap, extra_args...);
5052
}
53+
}
54+
55+
template<class Writer, class Loader>
56+
static ErrorOr<NonnullRefPtr<Gfx::Bitmap>> get_roundtrip_bitmap(Gfx::Bitmap const& bitmap)
57+
{
58+
auto encoded_data = TRY(encode_bitmap<Writer>(bitmap));
59+
return expect_single_frame_of_size(*TRY(Loader::create(encoded_data)), bitmap.size());
60+
}
5161

52-
auto plugin = TRY(Loader::create(encoded_data));
53-
return expect_single_frame_of_size(*plugin, bitmap.size());
62+
static void expect_bitmaps_equal(Gfx::Bitmap const& a, Gfx::Bitmap const& b)
63+
{
64+
VERIFY(a.size() == b.size());
65+
for (int y = 0; y < a.height(); ++y)
66+
for (int x = 0; x < a.width(); ++x)
67+
EXPECT_EQ(a.get_pixel(x, y), b.get_pixel(x, y));
5468
}
5569

5670
template<class Writer, class Loader>
5771
static ErrorOr<void> test_roundtrip(Gfx::Bitmap const& bitmap)
5872
{
5973
auto decoded = TRY((get_roundtrip_bitmap<Writer, Loader>(bitmap)));
60-
61-
for (int y = 0; y < bitmap.height(); ++y)
62-
for (int x = 0; x < bitmap.width(); ++x)
63-
EXPECT_EQ(decoded->get_pixel(x, y), bitmap.get_pixel(x, y));
64-
74+
expect_bitmaps_equal(*decoded, bitmap);
6575
return {};
6676
}
6777

@@ -120,3 +130,18 @@ TEST_CASE(test_webp)
120130
TRY_OR_FAIL((test_roundtrip<Gfx::WebPWriter, Gfx::WebPImageDecoderPlugin>(TRY_OR_FAIL(create_test_rgb_bitmap()))));
121131
TRY_OR_FAIL((test_roundtrip<Gfx::WebPWriter, Gfx::WebPImageDecoderPlugin>(TRY_OR_FAIL(create_test_rgba_bitmap()))));
122132
}
133+
134+
TEST_CASE(test_webp_icc)
135+
{
136+
auto sRGB_icc_profile = MUST(Gfx::ICC::sRGB());
137+
auto sRGB_icc_data = MUST(Gfx::ICC::encode(sRGB_icc_profile));
138+
139+
auto rgba_bitmap = TRY_OR_FAIL(create_test_rgba_bitmap());
140+
auto encoded_rgba_bitmap = TRY_OR_FAIL((encode_bitmap<Gfx::WebPWriter>(rgba_bitmap, Gfx::WebPEncoderOptions { .icc_data = sRGB_icc_data })));
141+
142+
auto decoded_rgba_plugin = TRY_OR_FAIL(Gfx::WebPImageDecoderPlugin::create(encoded_rgba_bitmap));
143+
expect_bitmaps_equal(*TRY_OR_FAIL(expect_single_frame_of_size(*decoded_rgba_plugin, rgba_bitmap->size())), rgba_bitmap);
144+
auto decoded_rgba_profile = TRY_OR_FAIL(Gfx::ICC::Profile::try_load_from_externally_owned_memory(TRY_OR_FAIL(decoded_rgba_plugin->icc_data()).value()));
145+
auto reencoded_icc_data = TRY_OR_FAIL(Gfx::ICC::encode(decoded_rgba_profile));
146+
EXPECT_EQ(sRGB_icc_data, reencoded_icc_data);
147+
}

Userland/Libraries/LibGfx/ImageFormats/WebPWriter.cpp

+16-8
Original file line numberDiff line numberDiff line change
@@ -215,33 +215,41 @@ static ErrorOr<void> write_VP8X_header(Stream& stream, VP8XHeader const& header)
215215

216216
LittleEndianOutputBitStream bit_stream { MaybeOwned<Stream>(stream) };
217217

218+
// Don't use bit_stream.write_bits() to write individual flags here:
219+
// The spec describes bit flags in MSB to LSB order, but write_bits() writes LSB to MSB.
220+
u8 flags = 0;
218221
// "Reserved (Rsv): 2 bits
219222
// MUST be 0. Readers MUST ignore this field."
220-
TRY(bit_stream.write_bits(0u, 2u));
221223

222224
// "ICC profile (I): 1 bit
223225
// Set if the file contains an 'ICCP' Chunk."
224-
TRY(bit_stream.write_bits(header.has_icc, 1u));
226+
if (header.has_icc)
227+
flags |= 0x20;
225228

226229
// "Alpha (L): 1 bit
227230
// Set if any of the frames of the image contain transparency information ("alpha")."
228-
TRY(bit_stream.write_bits(header.has_alpha, 1u));
231+
if (header.has_alpha)
232+
flags |= 0x10;
229233

230234
// "Exif metadata (E): 1 bit
231235
// Set if the file contains Exif metadata."
232-
TRY(bit_stream.write_bits(header.has_exif, 1u));
236+
if (header.has_exif)
237+
flags |= 0x8;
233238

234239
// "XMP metadata (X): 1 bit
235240
// Set if the file contains XMP metadata."
236-
TRY(bit_stream.write_bits(header.has_xmp, 1u));
241+
if (header.has_xmp)
242+
flags |= 0x4;
237243

238244
// "Animation (A): 1 bit
239245
// Set if this is an animated image. Data in 'ANIM' and 'ANMF' Chunks should be used to control the animation."
240-
TRY(bit_stream.write_bits(header.has_animation, 1u));
246+
if (header.has_animation)
247+
flags |= 0x2;
241248

242249
// "Reserved (R): 1 bit
243250
// MUST be 0. Readers MUST ignore this field."
244-
TRY(bit_stream.write_bits(0u, 1u));
251+
252+
TRY(bit_stream.write_bits(flags, 8u));
245253

246254
// "Reserved: 24 bits
247255
// MUST be 0. Readers MUST ignore this field."
@@ -304,7 +312,7 @@ ErrorOr<void> WebPWriter::encode(Stream& stream, Bitmap const& bitmap, Options c
304312
iccp_chunk_bytes = TRY(iccp_chunk_stream.read_until_eof());
305313

306314
AllocatingMemoryStream vp8x_header_stream;
307-
TRY(write_VP8X_header(vp8x_header_stream, { .has_icc = true, .width = (u32)bitmap.width(), .height = (u32)bitmap.height() }));
315+
TRY(write_VP8X_header(vp8x_header_stream, { .has_icc = true, .has_alpha = alpha_is_used_hint, .width = (u32)bitmap.width(), .height = (u32)bitmap.height() }));
308316
auto vp8x_header_bytes = TRY(vp8x_header_stream.read_until_eof());
309317

310318
AllocatingMemoryStream vp8x_chunk_stream;

0 commit comments

Comments
 (0)