Skip to content

Commit 7b54fe6

Browse files
LucasCholletnico
authored andcommitted
LibGfx/PNG: Only write a grayscale channel when sufficient
Using this [1] test image, it reduces its size from 2.3 MB to 1.5 MB, a nice 33% improvement :^) [1] https://github.com/libjxl/conformance/blob/master/testcases/grayscale_public_university/ref.png
1 parent bb8c3a5 commit 7b54fe6

File tree

2 files changed

+59
-11
lines changed

2 files changed

+59
-11
lines changed

Tests/LibGfx/TestImageWriter.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,27 @@ static void add_alpha_channel(Gfx::Bitmap& bitmap)
9090
}
9191
}
9292

93+
static ErrorOr<AK::NonnullRefPtr<Gfx::Bitmap>> create_test_grayscale_bitmap()
94+
{
95+
auto bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { 47, 33 }));
96+
97+
for (int y = 0; y < bitmap->height(); ++y) {
98+
for (int x = 0; x < bitmap->width(); ++x) {
99+
auto gray = (x + y) * 255 / (bitmap->width() + bitmap->height());
100+
bitmap->set_pixel(x, y, Gfx::Color(gray, gray, gray));
101+
}
102+
}
103+
104+
return bitmap;
105+
}
106+
107+
static ErrorOr<AK::NonnullRefPtr<Gfx::Bitmap>> create_test_grayscale_alpha_bitmap()
108+
{
109+
auto bitmap = TRY(create_test_grayscale_bitmap());
110+
add_alpha_channel(*bitmap);
111+
return bitmap;
112+
}
113+
93114
static ErrorOr<AK::NonnullRefPtr<Gfx::Bitmap>> create_test_rgb_bitmap()
94115
{
95116
auto bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { 47, 33 }));
@@ -174,6 +195,8 @@ TEST_CASE(test_jpeg)
174195

175196
TEST_CASE(test_png)
176197
{
198+
TRY_OR_FAIL((test_roundtrip<Gfx::PNGWriter, Gfx::PNGImageDecoderPlugin>(TRY_OR_FAIL(create_test_grayscale_bitmap()))));
199+
TRY_OR_FAIL((test_roundtrip<Gfx::PNGWriter, Gfx::PNGImageDecoderPlugin>(TRY_OR_FAIL(create_test_grayscale_alpha_bitmap()))));
177200
TRY_OR_FAIL((test_roundtrip<Gfx::PNGWriter, Gfx::PNGImageDecoderPlugin>(TRY_OR_FAIL(create_test_rgb_bitmap()))));
178201
TRY_OR_FAIL((test_roundtrip<Gfx::PNGWriter, Gfx::PNGImageDecoderPlugin>(TRY_OR_FAIL(create_test_rgba_bitmap()))));
179202
}

Userland/Libraries/LibGfx/ImageFormats/PNGWriter.cpp

+36-11
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ union [[gnu::packed]] Pixel {
214214
};
215215
static_assert(AssertSize<Pixel, 4>());
216216

217-
template<bool include_alpha>
217+
template<bool include_alpha, bool include_colors>
218218
static ErrorOr<void> add_image_data_to_chunk_impl(Gfx::Bitmap const& bitmap, PNGChunk& png_chunk, Compress::ZlibCompressionLevel compression_level)
219219
{
220220
ByteBuffer uncompressed_block_data;
@@ -259,7 +259,9 @@ static ErrorOr<void> add_image_data_to_chunk_impl(Gfx::Bitmap const& bitmap, PNG
259259

260260
u32 sum_of_abs_values() const
261261
{
262-
u32 result = sum[0] + sum[1] + sum[2];
262+
u32 result = sum[0];
263+
if constexpr (include_colors)
264+
result += sum[1] + sum[2];
263265
if constexpr (include_alpha)
264266
result += sum[3];
265267
return result;
@@ -315,8 +317,10 @@ static ErrorOr<void> add_image_data_to_chunk_impl(Gfx::Bitmap const& bitmap, PNG
315317
auto pixel_y_minus_1 = Pixel::argb32_to_simd(scanline_minus_1[x]);
316318

317319
auto predicted_pixel = best_filter.predict(pixel, pixel_x_minus_1, pixel_y_minus_1, pixel_xy_minus_1);
318-
TRY(uncompressed_block_data.try_append(predicted_pixel[2]));
319-
TRY(uncompressed_block_data.try_append(predicted_pixel[1]));
320+
if constexpr (include_colors) {
321+
TRY(uncompressed_block_data.try_append(predicted_pixel[2]));
322+
TRY(uncompressed_block_data.try_append(predicted_pixel[1]));
323+
}
320324
TRY(uncompressed_block_data.try_append(predicted_pixel[0]));
321325
if constexpr (include_alpha)
322326
TRY(uncompressed_block_data.try_append(predicted_pixel[3]));
@@ -335,15 +339,15 @@ static ErrorOr<void> add_image_data_to_chunk(Gfx::Bitmap const& bitmap, PNG::Col
335339
{
336340
switch (color_type) {
337341
case PNG::ColorType::Greyscale:
338-
VERIFY_NOT_REACHED();
342+
return add_image_data_to_chunk_impl<false, false>(bitmap, png_chunk, compression_level);
339343
case PNG::ColorType::Truecolor:
340-
return add_image_data_to_chunk_impl<false>(bitmap, png_chunk, compression_level);
344+
return add_image_data_to_chunk_impl<false, true>(bitmap, png_chunk, compression_level);
341345
case PNG::ColorType::IndexedColor:
342346
VERIFY_NOT_REACHED();
343347
case PNG::ColorType::GreyscaleWithAlpha:
344-
VERIFY_NOT_REACHED();
348+
return add_image_data_to_chunk_impl<true, false>(bitmap, png_chunk, compression_level);
345349
case PNG::ColorType::TruecolorWithAlpha:
346-
return add_image_data_to_chunk_impl<true>(bitmap, png_chunk, compression_level);
350+
return add_image_data_to_chunk_impl<true, true>(bitmap, png_chunk, compression_level);
347351
}
348352
VERIFY_NOT_REACHED();
349353
}
@@ -375,13 +379,34 @@ static bool bitmap_has_transparency(Bitmap const& bitmap)
375379
return false;
376380
}
377381

378-
ErrorOr<void> PNGWriter::encode(Stream& stream, Bitmap const& bitmap, Options const& options)
382+
static bool bitmap_has_color(Bitmap const& bitmap)
379383
{
380-
bool has_transparency = options.force_alpha || bitmap_has_transparency(bitmap);
384+
for (auto pixel : bitmap) {
385+
auto color = Color::from_argb(pixel);
386+
if (color.red() != color.green() || color.green() != color.blue())
387+
return true;
388+
}
389+
return false;
390+
}
381391

392+
static PNG::ColorType find_color_type(Bitmap const& bitmap, bool force_alpha)
393+
{
394+
bool has_alpha = force_alpha || bitmap_has_transparency(bitmap);
395+
if (bitmap_has_color(bitmap)) {
396+
if (has_alpha)
397+
return PNG::ColorType::TruecolorWithAlpha;
398+
return PNG::ColorType::Truecolor;
399+
}
400+
if (has_alpha)
401+
return PNG::ColorType::GreyscaleWithAlpha;
402+
return PNG::ColorType::Greyscale;
403+
}
404+
405+
ErrorOr<void> PNGWriter::encode(Stream& stream, Bitmap const& bitmap, Options const& options)
406+
{
382407
PNGWriter writer { stream };
383408
TRY(writer.add_png_header());
384-
auto color_type = has_transparency ? PNG::ColorType::TruecolorWithAlpha : PNG::ColorType::Truecolor;
409+
auto color_type = find_color_type(bitmap, options.force_alpha);
385410
TRY(writer.add_IHDR_chunk(bitmap.width(), bitmap.height(), 8, color_type, 0, 0, 0));
386411
if (options.icc_data.has_value())
387412
TRY(writer.add_iCCP_chunk(options.icc_data.value(), options.compression_level));

0 commit comments

Comments
 (0)