Skip to content

Commit 1f8a42c

Browse files
InvalidUsernameExceptiongmta
authored andcommitted
LibGfx: Add a test for bitmap export
The verified pixel output in this test just reflects currently observed behavior, I have not verified that all cases output the correct data wrt what the spec expects.
1 parent a81e407 commit 1f8a42c

File tree

6 files changed

+294
-16
lines changed

6 files changed

+294
-16
lines changed

Libraries/LibGfx/Bitmap.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ struct BackingStore {
3131
size_t size_in_bytes { 0 };
3232
};
3333

34+
StringView bitmap_format_name(BitmapFormat format)
35+
{
36+
switch (format) {
37+
#define ENUMERATE_BITMAP_FORMAT(format) \
38+
case BitmapFormat::format: \
39+
return #format##sv;
40+
ENUMERATE_BITMAP_FORMATS(ENUMERATE_BITMAP_FORMAT)
41+
#undef ENUMERATE_BITMAP_FORMAT
42+
}
43+
VERIFY_NOT_REACHED();
44+
}
45+
3446
size_t Bitmap::minimum_pitch(size_t width, BitmapFormat format)
3547
{
3648
size_t element_size;

Libraries/LibGfx/Bitmap.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,21 @@ namespace Gfx {
2020
// A pixel value that does not express any information about its component order
2121
using RawPixel = u32;
2222

23+
#define ENUMERATE_BITMAP_FORMATS(X) \
24+
X(Invalid) \
25+
X(BGRx8888) \
26+
X(BGRA8888) \
27+
X(RGBx8888) \
28+
X(RGBA8888)
29+
2330
enum class BitmapFormat {
24-
Invalid,
25-
BGRx8888,
26-
BGRA8888,
27-
RGBx8888,
28-
RGBA8888,
31+
#define ENUMERATE_BITMAP_FORMAT(format) format,
32+
ENUMERATE_BITMAP_FORMATS(ENUMERATE_BITMAP_FORMAT)
33+
#undef ENUMERATE_BITMAP_FORMAT
2934
};
3035

36+
[[nodiscard]] StringView bitmap_format_name(BitmapFormat);
37+
3138
inline bool is_valid_bitmap_format(u32 const format)
3239
{
3340
switch (format) {

Libraries/LibGfx/ImmutableBitmap.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@
1616

1717
namespace Gfx {
1818

19+
StringView export_format_name(ExportFormat format)
20+
{
21+
switch (format) {
22+
#define ENUMERATE_EXPORT_FORMAT(format) \
23+
case Gfx::ExportFormat::format: \
24+
return #format##sv;
25+
ENUMERATE_EXPORT_FORMATS(ENUMERATE_EXPORT_FORMAT)
26+
#undef ENUMERATE_EXPORT_FORMAT
27+
}
28+
VERIFY_NOT_REACHED();
29+
}
30+
1931
struct ImmutableBitmapImpl {
2032
sk_sp<SkImage> sk_image;
2133
SkBitmap sk_bitmap;

Libraries/LibGfx/ImmutableBitmap.h

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,23 @@ namespace Gfx {
2121

2222
struct ImmutableBitmapImpl;
2323

24+
#define ENUMERATE_EXPORT_FORMATS(X) \
25+
X(Gray8) \
26+
X(Alpha8) \
27+
X(RGB565) \
28+
X(RGBA5551) \
29+
X(RGBA4444) \
30+
X(RGB888) \
31+
X(RGBA8888)
32+
2433
enum class ExportFormat : u8 {
25-
// 8 bit
26-
Gray8,
27-
Alpha8,
28-
// 16 bit
29-
RGB565,
30-
RGBA5551,
31-
RGBA4444,
32-
// 24 bit
33-
RGB888,
34-
// 32 bit
35-
RGBA8888,
34+
#define ENUMERATE_EXPORT_FORMAT(format) format,
35+
ENUMERATE_EXPORT_FORMATS(ENUMERATE_EXPORT_FORMAT)
36+
#undef ENUMERATE_EXPORT_FORMAT
3637
};
3738

39+
[[nodiscard]] StringView export_format_name(ExportFormat);
40+
3841
struct ExportFlags {
3942
enum : u8 {
4043
PremultiplyAlpha = 1 << 0,

Tests/LibGfx/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ set(TEST_SOURCES
33
TestColor.cpp
44
TestImageDecoder.cpp
55
TestImageWriter.cpp
6+
TestImmutableBitmap.cpp
67
TestQuad.cpp
78
TestRect.cpp
89
TestWOFF.cpp
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/*
2+
* Copyright (c) 2025, Ladybird contributors
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <AK/Try.h>
8+
#include <LibGfx/Bitmap.h>
9+
#include <LibGfx/Color.h>
10+
#include <LibGfx/ImmutableBitmap.h>
11+
#include <LibTest/TestCase.h>
12+
13+
TEST_CASE(export_to_byte_buffer)
14+
{
15+
enum class Premultiplied : u8 {
16+
Yes,
17+
No,
18+
};
19+
20+
struct TestData {
21+
Vector<Gfx::BitmapFormat> source_formats_to_test;
22+
Vector<Premultiplied> source_alpha_cases_to_test;
23+
Gfx::BGRA8888 source_pixels[4];
24+
Gfx::ExportFormat export_format;
25+
Vector<Premultiplied> target_alpha_cases_to_test;
26+
Vector<u8> expected_result;
27+
};
28+
29+
Vector<Gfx::BitmapFormat> all_bitmap_formats = {
30+
Gfx::BitmapFormat::BGRx8888,
31+
Gfx::BitmapFormat::BGRA8888,
32+
Gfx::BitmapFormat::RGBx8888,
33+
Gfx::BitmapFormat::RGBA8888,
34+
};
35+
36+
Vector<Gfx::BitmapFormat> alpha_bitmap_formats = {
37+
Gfx::BitmapFormat::BGRA8888,
38+
Gfx::BitmapFormat::RGBA8888,
39+
};
40+
41+
Vector<Gfx::BitmapFormat> non_alpha_bitmap_formats = {
42+
Gfx::BitmapFormat::BGRx8888,
43+
Gfx::BitmapFormat::RGBx8888,
44+
};
45+
46+
// FIXME: Some of these test cases seem suspect, particularly with regard to alpha-(un)premultiplication. We should
47+
// validate whether these actually have the correct behavior.
48+
TestData subtests[] = {
49+
{
50+
alpha_bitmap_formats,
51+
{ Premultiplied::No },
52+
{ 0x00FFFFFF, 0x55FFFFFF, 0xAAFFFFFF, 0xFFFFFFFF },
53+
Gfx::ExportFormat::Gray8,
54+
{ Premultiplied::No, Premultiplied::Yes },
55+
{ 0x00, 0x55, 0xAA, 0xFF },
56+
},
57+
{
58+
non_alpha_bitmap_formats,
59+
{ Premultiplied::No },
60+
{ 0x00000000, 0x55555555, 0xAAAAAAAA, 0xFFFFFFFF },
61+
Gfx::ExportFormat::Gray8,
62+
{ Premultiplied::No, Premultiplied::Yes },
63+
{ 0x00, 0x55, 0xAA, 0xFF },
64+
},
65+
{
66+
all_bitmap_formats,
67+
{ Premultiplied::Yes },
68+
{ 0x00000000, 0x55555555, 0xAAAAAAAA, 0xFFFFFFFF },
69+
Gfx::ExportFormat::Gray8,
70+
{ Premultiplied::No, Premultiplied::Yes },
71+
{ 0x00, 0x55, 0xAA, 0xFF },
72+
},
73+
{
74+
alpha_bitmap_formats,
75+
{ Premultiplied::No, Premultiplied::Yes },
76+
{ 0x00112233, 0x44556677, 0x8899AABB, 0xCCDDEEFF },
77+
Gfx::ExportFormat::Alpha8,
78+
{ Premultiplied::No, Premultiplied::Yes },
79+
{ 0x00, 0x44, 0x88, 0xCC },
80+
},
81+
{
82+
non_alpha_bitmap_formats,
83+
{ Premultiplied::No, Premultiplied::Yes },
84+
{ 0x00112233, 0x44556677, 0x8899AABB, 0xCCDDEEFF },
85+
Gfx::ExportFormat::Alpha8,
86+
{ Premultiplied::No, Premultiplied::Yes },
87+
{ 0xFF, 0xFF, 0xFF, 0xFF },
88+
},
89+
{
90+
non_alpha_bitmap_formats,
91+
{ Premultiplied::No, Premultiplied::Yes },
92+
{ 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFFF00FF },
93+
Gfx::ExportFormat::RGB565,
94+
{ Premultiplied::No, Premultiplied::Yes },
95+
{ 0x00, 0xF8, 0xE0, 0x07, 0x1F, 0x00, 0x1F, 0xF8 },
96+
},
97+
{
98+
alpha_bitmap_formats,
99+
{ Premultiplied::No },
100+
{ 0x33FFFFFF, 0x66FFFFFF, 0x99FFFFFF, 0xCCFFFFFF },
101+
Gfx::ExportFormat::RGB565,
102+
{ Premultiplied::No, Premultiplied::Yes },
103+
{ 0xA6, 0x31, 0x2C, 0x63, 0xD3, 0x9C, 0x59, 0xCE },
104+
},
105+
{
106+
alpha_bitmap_formats,
107+
{ Premultiplied::Yes },
108+
{ 0x33FF0000, 0x6600FF00, 0x990000FF, 0xCCFF00FF },
109+
Gfx::ExportFormat::RGB565,
110+
{ Premultiplied::No, Premultiplied::Yes },
111+
{ 0x00, 0xF8, 0xE0, 0x07, 0x1F, 0x00, 0x1F, 0xF8 },
112+
},
113+
{
114+
non_alpha_bitmap_formats,
115+
{ Premultiplied::No, Premultiplied::Yes },
116+
{ 0x00112233, 0x44556677, 0x8899AABB, 0xCCDDEEFF },
117+
Gfx::ExportFormat::RGBA4444,
118+
{ Premultiplied::No, Premultiplied::Yes },
119+
{ 0x3f, 0x12, 0x7f, 0x56, 0xBF, 0x9A, 0xFF, 0xDE },
120+
},
121+
{
122+
alpha_bitmap_formats,
123+
{ Premultiplied::No },
124+
{ 0x33001122, 0x77445566, 0xBB8899AA, 0xFFCCDDEE },
125+
Gfx::ExportFormat::RGBA4444,
126+
{ Premultiplied::No },
127+
{ 0x23, 0x01, 0x67, 0x45, 0xAB, 0x89, 0xEF, 0xCD },
128+
},
129+
{
130+
alpha_bitmap_formats,
131+
{ Premultiplied::No },
132+
{ 0x3355AAFF, 0x6655AAFF, 0x9955AAFF, 0xCC55AAFF },
133+
Gfx::ExportFormat::RGBA4444,
134+
{ Premultiplied::Yes },
135+
{ 0x33, 0x12, 0x66, 0x24, 0x99, 0x36, 0xCC, 0x48 },
136+
},
137+
{
138+
alpha_bitmap_formats,
139+
{ Premultiplied::Yes },
140+
{ 0x33112233, 0x66224466, 0x99336699, 0xCC4488CC },
141+
Gfx::ExportFormat::RGBA4444,
142+
{ Premultiplied::No },
143+
{ 0xF3, 0x5A, 0xF6, 0x5A, 0xF9, 0x5A, 0xFC, 0x5A },
144+
},
145+
{
146+
alpha_bitmap_formats,
147+
{ Premultiplied::Yes },
148+
{ 0x33001122, 0x77445566, 0xBB8899AA, 0xFFCCDDEE },
149+
Gfx::ExportFormat::RGBA4444,
150+
{ Premultiplied::Yes },
151+
{ 0x23, 0x01, 0x67, 0x45, 0xAB, 0x89, 0xEF, 0xCD },
152+
},
153+
{
154+
all_bitmap_formats,
155+
{ Premultiplied::No, Premultiplied::Yes },
156+
{ 0x00112233, 0x44556677, 0x8899AABB, 0xCCDDEEFF },
157+
Gfx::ExportFormat::RGB888,
158+
{ Premultiplied::No, Premultiplied::Yes },
159+
{ 0x11, 0x22, 0x33, 0x55, 0x66, 0x77, 0x99, 0xAA, 0xBB, 0xDD, 0xEE, 0xFF },
160+
},
161+
{
162+
alpha_bitmap_formats,
163+
{ Premultiplied::No },
164+
{ 0x33001122, 0x77445566, 0xBB8899AA, 0xFFCCDDEE },
165+
Gfx::ExportFormat::RGBA8888,
166+
{ Premultiplied::No },
167+
{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF },
168+
},
169+
{
170+
alpha_bitmap_formats,
171+
{ Premultiplied::No },
172+
{ 0x3355AAFF, 0x6655AAFF, 0x9955AAFF, 0xCC55AAFF },
173+
Gfx::ExportFormat::RGBA8888,
174+
{ Premultiplied::Yes },
175+
{ 0x11, 0x22, 0x33, 0x33, 0x22, 0x44, 0x66, 0x66, 0x33, 0x66, 0x99, 0x99, 0x44, 0x88, 0xCC, 0xCC },
176+
},
177+
{
178+
alpha_bitmap_formats,
179+
{ Premultiplied::Yes },
180+
{ 0x33112233, 0x66224466, 0x99336699, 0xCC4488CC },
181+
Gfx::ExportFormat::RGBA8888,
182+
{ Premultiplied::No },
183+
{ 0x55, 0xAA, 0xFF, 0x33, 0x55, 0xAA, 0xFF, 0x66, 0x55, 0xAA, 0xFF, 0x99, 0x55, 0xAA, 0xFF, 0xCC },
184+
},
185+
{
186+
alpha_bitmap_formats,
187+
{ Premultiplied::Yes },
188+
{ 0x33001122, 0x77445566, 0xBB8899AA, 0xFFCCDDEE },
189+
Gfx::ExportFormat::RGBA8888,
190+
{ Premultiplied::Yes },
191+
{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF },
192+
},
193+
{
194+
non_alpha_bitmap_formats,
195+
{ Premultiplied::No, Premultiplied::Yes },
196+
{ 0x00112233, 0x44556677, 0x8899AABB, 0xCCDDEEFF },
197+
Gfx::ExportFormat::RGBA8888,
198+
{ Premultiplied::No, Premultiplied::Yes },
199+
{ 0x11, 0x22, 0x33, 0xFF, 0x55, 0x66, 0x77, 0xFF, 0x99, 0xAA, 0xBB, 0xFF, 0xDD, 0xEE, 0xFF, 0xFF },
200+
},
201+
};
202+
203+
auto alpha_case_name = [](Premultiplied premultiplied) -> StringView {
204+
return premultiplied == Premultiplied::Yes ? "premultiplied"sv : "unpremultiplied"sv;
205+
};
206+
207+
auto flip_y_name = [](u32 flags) -> StringView {
208+
return flags & Gfx::ExportFlags::FlipY ? "flip Y"sv : "keep Y"sv;
209+
};
210+
211+
auto count = 0;
212+
for (auto const& subtest : subtests) {
213+
for (auto source_format : subtest.source_formats_to_test) {
214+
for (auto maybe_flip_y_flag : Vector<int> { 0, Gfx::ExportFlags::FlipY }) {
215+
for (auto source_alpha_case : subtest.source_alpha_cases_to_test) {
216+
for (auto target_alpha_case : subtest.target_alpha_cases_to_test) {
217+
auto export_flags = 0;
218+
export_flags |= maybe_flip_y_flag;
219+
export_flags |= target_alpha_case == Premultiplied::Yes ? Gfx::ExportFlags::PremultiplyAlpha : 0;
220+
221+
dbgln("Running subtest {}: {} -> {}, {} -> {}, {}", count++, bitmap_format_name(source_format), export_format_name(subtest.export_format), alpha_case_name(source_alpha_case), alpha_case_name(target_alpha_case), flip_y_name(export_flags));
222+
223+
auto source_alpha_type = source_alpha_case == Premultiplied::Yes ? Gfx::AlphaType::Premultiplied : Gfx::AlphaType::Unpremultiplied;
224+
auto bitmap = MUST(Gfx::Bitmap::create(source_format, source_alpha_type, { 2, 2 }));
225+
auto logical_y0 = maybe_flip_y_flag ? 1 : 0;
226+
auto logical_y1 = maybe_flip_y_flag ? 0 : 1;
227+
bitmap->set_pixel(0, logical_y0, Color::from_bgra(subtest.source_pixels[0]));
228+
bitmap->set_pixel(1, logical_y0, Color::from_bgra(subtest.source_pixels[1]));
229+
bitmap->set_pixel(0, logical_y1, Color::from_bgra(subtest.source_pixels[2]));
230+
bitmap->set_pixel(1, logical_y1, Color::from_bgra(subtest.source_pixels[3]));
231+
232+
auto immutable_bitmap = Gfx::ImmutableBitmap::create(bitmap);
233+
auto result = MUST(immutable_bitmap->export_to_byte_buffer(subtest.export_format, export_flags, 2, 2));
234+
235+
EXPECT_EQ(result.width, 2);
236+
EXPECT_EQ(result.height, 2);
237+
EXPECT_EQ(result.buffer.bytes(), subtest.expected_result);
238+
}
239+
}
240+
}
241+
}
242+
}
243+
}

0 commit comments

Comments
 (0)