summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGfx/PNGWriter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Userland/Libraries/LibGfx/PNGWriter.cpp')
-rw-r--r--Userland/Libraries/LibGfx/PNGWriter.cpp108
1 files changed, 102 insertions, 6 deletions
diff --git a/Userland/Libraries/LibGfx/PNGWriter.cpp b/Userland/Libraries/LibGfx/PNGWriter.cpp
index 950815cfb6..04541c6856 100644
--- a/Userland/Libraries/LibGfx/PNGWriter.cpp
+++ b/Userland/Libraries/LibGfx/PNGWriter.cpp
@@ -7,12 +7,15 @@
*/
#include <AK/Concepts.h>
+#include <AK/SIMDExtras.h>
#include <AK/String.h>
#include <LibCompress/Zlib.h>
#include <LibCrypto/Checksum/CRC32.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/PNGWriter.h>
+#pragma GCC diagnostic ignored "-Wpsabi"
+
namespace Gfx {
class PNGChunk {
@@ -134,6 +137,24 @@ void PNGWriter::add_IEND_chunk()
add_chunk(png_chunk);
}
+union [[gnu::packed]] Pixel {
+ ARGB32 rgba { 0 };
+ struct {
+ u8 red;
+ u8 green;
+ u8 blue;
+ u8 alpha;
+ };
+ AK::SIMD::u8x4 simd;
+
+ ALWAYS_INLINE static AK::SIMD::u8x4 gfx_to_png(Pixel pixel)
+ {
+ swap(pixel.red, pixel.blue);
+ return pixel.simd;
+ }
+};
+static_assert(AssertSize<Pixel, 4>());
+
void PNGWriter::add_IDAT_chunk(Gfx::Bitmap const& bitmap)
{
PNGChunk png_chunk { "IDAT" };
@@ -142,16 +163,91 @@ void PNGWriter::add_IDAT_chunk(Gfx::Bitmap const& bitmap)
ByteBuffer uncompressed_block_data;
uncompressed_block_data.ensure_capacity(bitmap.size_in_bytes() + bitmap.height());
+ Pixel const dummy_scanline[bitmap.width()] {};
+ auto* scanline_minus_1 = dummy_scanline;
+
for (int y = 0; y < bitmap.height(); ++y) {
- uncompressed_block_data.append(0);
+ auto* scanline = reinterpret_cast<Pixel const*>(bitmap.scanline(y));
+
+ struct Filter {
+ PNG::FilterType type;
+ ByteBuffer buffer {};
+ int sum = 0;
+
+ void append(u8 byte)
+ {
+ buffer.append(byte);
+ sum += static_cast<i8>(byte);
+ }
+
+ void append(AK::SIMD::u8x4 simd)
+ {
+ append(simd[0]);
+ append(simd[1]);
+ append(simd[2]);
+ append(simd[3]);
+ }
+ };
+
+ Filter none_filter { .type = PNG::FilterType::None };
+ none_filter.buffer.ensure_capacity(sizeof(Pixel) * bitmap.height());
+
+ Filter sub_filter { .type = PNG::FilterType::Sub };
+ sub_filter.buffer.ensure_capacity(sizeof(Pixel) * bitmap.height());
+
+ Filter up_filter { .type = PNG::FilterType::Up };
+ up_filter.buffer.ensure_capacity(sizeof(Pixel) * bitmap.height());
+
+ Filter average_filter { .type = PNG::FilterType::Average };
+ average_filter.buffer.ensure_capacity(sizeof(ARGB32) * bitmap.height());
+
+ Filter paeth_filter { .type = PNG::FilterType::Paeth };
+ paeth_filter.buffer.ensure_capacity(sizeof(ARGB32) * bitmap.height());
+
+ auto pixel_x_minus_1 = Pixel::gfx_to_png(*dummy_scanline);
+ auto pixel_xy_minus_1 = Pixel::gfx_to_png(*dummy_scanline);
for (int x = 0; x < bitmap.width(); ++x) {
- auto pixel = bitmap.get_pixel(x, y);
- uncompressed_block_data.append(pixel.red());
- uncompressed_block_data.append(pixel.green());
- uncompressed_block_data.append(pixel.blue());
- uncompressed_block_data.append(pixel.alpha());
+ auto pixel = Pixel::gfx_to_png(scanline[x]);
+ auto pixel_y_minus_1 = Pixel::gfx_to_png(scanline_minus_1[x]);
+
+ none_filter.append(pixel);
+
+ sub_filter.append(pixel - pixel_x_minus_1);
+
+ up_filter.append(pixel - pixel_y_minus_1);
+
+ // The sum Orig(a) + Orig(b) shall be performed without overflow (using at least nine-bit arithmetic).
+ auto sum = AK::SIMD::to_u16x4(pixel_x_minus_1) + AK::SIMD::to_u16x4(pixel_y_minus_1);
+ auto average = AK::SIMD::to_u8x4(sum / 2);
+ average_filter.append(pixel - average);
+
+ paeth_filter.append(pixel - PNG::paeth_predictor(pixel_x_minus_1, pixel_y_minus_1, pixel_xy_minus_1));
+
+ pixel_x_minus_1 = pixel;
+ pixel_xy_minus_1 = pixel_y_minus_1;
}
+
+ scanline_minus_1 = scanline;
+
+ // 12.8 Filter selection: https://www.w3.org/TR/PNG/#12Filter-selection
+ // For best compression of truecolour and greyscale images, the recommended approach
+ // is adaptive filtering in which a filter is chosen for each scanline.
+ // The following simple heuristic has performed well in early tests:
+ // compute the output scanline using all five filters, and select the filter that gives the smallest sum of absolute values of outputs.
+ // (Consider the output bytes as signed differences for this test.)
+ Filter& best_filter = none_filter;
+ if (abs(best_filter.sum) > abs(sub_filter.sum))
+ best_filter = sub_filter;
+ if (abs(best_filter.sum) > abs(up_filter.sum))
+ best_filter = up_filter;
+ if (abs(best_filter.sum) > abs(average_filter.sum))
+ best_filter = average_filter;
+ if (abs(best_filter.sum) > abs(paeth_filter.sum))
+ best_filter = paeth_filter;
+
+ uncompressed_block_data.append(to_underlying(best_filter.type));
+ uncompressed_block_data.append(best_filter.buffer);
}
auto maybe_zlib_buffer = Compress::ZlibCompressor::compress_all(uncompressed_block_data);