mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 15:57:45 +00:00
LibGfx: Unfilter PNG data before unpacking it to RGBA
The order of PNG compression is raw pixel data -> filter -> compress. For decompression, the order is reversed, so that means uncompress -> unfilter -> raw pixel data. Previously, the PNG decoder was converting to raw pixel data before unfiltering, which was a problem when using indexed color palettes, since each pixel's palette index could change during unfiltering (e.g. it was unfiltering after already choosing the color from the palette index). This was leading to 'Palette index out of range' errors on files that both: - Had scanlines with some sort of filtering - Didn't use the full range of possible palette indices for their bit depth. Also, because filtering now happens before converting to pixel data, filtering acts on bytes instead of pixels, meaning that the implementation of each filter type now maps much more directly to the specification: http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html
This commit is contained in:
parent
77a135491e
commit
b19f3b5106
1 changed files with 87 additions and 111 deletions
|
@ -95,6 +95,7 @@ struct PNGLoadingContext {
|
|||
bool has_seen_zlib_header { false };
|
||||
bool has_alpha() const { return to_underlying(color_type) & 4 || palette_transparency_data.size() > 0; }
|
||||
Vector<Scanline> scanlines;
|
||||
ByteBuffer unfiltered_data;
|
||||
RefPtr<Gfx::Bitmap> bitmap;
|
||||
ByteBuffer* decompression_buffer { nullptr };
|
||||
Vector<u8> compressed_data;
|
||||
|
@ -176,85 +177,53 @@ union [[gnu::packed]] Pixel {
|
|||
};
|
||||
static_assert(AssertSize<Pixel, 4>());
|
||||
|
||||
template<bool has_alpha, PNG::FilterType filter_type>
|
||||
ALWAYS_INLINE static void unfilter_impl(Gfx::Bitmap& bitmap, int y, void const* dummy_scanline_data)
|
||||
static void unfilter_scanline(PNG::FilterType filter, Bytes scanline_data, ReadonlyBytes previous_scanlines_data, u8 bytes_per_complete_pixel)
|
||||
{
|
||||
auto* dummy_scanline = (Pixel const*)dummy_scanline_data;
|
||||
if constexpr (filter_type == PNG::FilterType::None) {
|
||||
auto* pixels = (Pixel*)bitmap.scanline(y);
|
||||
for (int i = 0; i < bitmap.width(); ++i) {
|
||||
auto& x = pixels[i];
|
||||
swap(x.r, x.b);
|
||||
}
|
||||
}
|
||||
VERIFY(filter != PNG::FilterType::None);
|
||||
|
||||
if constexpr (filter_type == PNG::FilterType::Sub) {
|
||||
auto* pixels = (Pixel*)bitmap.scanline(y);
|
||||
swap(pixels[0].r, pixels[0].b);
|
||||
for (int i = 1; i < bitmap.width(); ++i) {
|
||||
auto& x = pixels[i];
|
||||
swap(x.r, x.b);
|
||||
auto& a = (Pixel const&)pixels[i - 1];
|
||||
x.v[0] += a.v[0];
|
||||
x.v[1] += a.v[1];
|
||||
x.v[2] += a.v[2];
|
||||
if constexpr (has_alpha)
|
||||
x.v[3] += a.v[3];
|
||||
switch (filter) {
|
||||
case PNG::FilterType::Sub:
|
||||
for (size_t i = 0; i < scanline_data.size(); ++i) {
|
||||
u8 left = (i < bytes_per_complete_pixel) ? 0 : scanline_data[i - bytes_per_complete_pixel];
|
||||
scanline_data[i] += left;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if constexpr (filter_type == PNG::FilterType::Up) {
|
||||
auto* pixels = (Pixel*)bitmap.scanline(y);
|
||||
auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (Pixel const*)bitmap.scanline(y - 1);
|
||||
for (int i = 0; i < bitmap.width(); ++i) {
|
||||
auto& x = pixels[i];
|
||||
swap(x.r, x.b);
|
||||
Pixel const& b = pixels_y_minus_1[i];
|
||||
x.v[0] += b.v[0];
|
||||
x.v[1] += b.v[1];
|
||||
x.v[2] += b.v[2];
|
||||
if constexpr (has_alpha)
|
||||
x.v[3] += b.v[3];
|
||||
break;
|
||||
case PNG::FilterType::Up:
|
||||
for (size_t i = 0; i < scanline_data.size(); ++i) {
|
||||
u8 above = previous_scanlines_data[i];
|
||||
scanline_data[i] += above;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if constexpr (filter_type == PNG::FilterType::Average) {
|
||||
auto* pixels = (Pixel*)bitmap.scanline(y);
|
||||
auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (Pixel const*)bitmap.scanline(y - 1);
|
||||
for (int i = 0; i < bitmap.width(); ++i) {
|
||||
auto& x = pixels[i];
|
||||
swap(x.r, x.b);
|
||||
Pixel a;
|
||||
if (i != 0)
|
||||
a = pixels[i - 1];
|
||||
Pixel const& b = pixels_y_minus_1[i];
|
||||
x.v[0] = x.v[0] + ((a.v[0] + b.v[0]) / 2);
|
||||
x.v[1] = x.v[1] + ((a.v[1] + b.v[1]) / 2);
|
||||
x.v[2] = x.v[2] + ((a.v[2] + b.v[2]) / 2);
|
||||
if constexpr (has_alpha)
|
||||
x.v[3] = x.v[3] + ((a.v[3] + b.v[3]) / 2);
|
||||
break;
|
||||
case PNG::FilterType::Average:
|
||||
for (size_t i = 0; i < scanline_data.size(); ++i) {
|
||||
u32 left = (i < bytes_per_complete_pixel) ? 0 : scanline_data[i - bytes_per_complete_pixel];
|
||||
u32 above = previous_scanlines_data[i];
|
||||
u8 average = (left + above) / 2;
|
||||
scanline_data[i] += average;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if constexpr (filter_type == PNG::FilterType::Paeth) {
|
||||
auto* pixels = (Pixel*)bitmap.scanline(y);
|
||||
auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (Pixel*)bitmap.scanline(y - 1);
|
||||
for (int i = 0; i < bitmap.width(); ++i) {
|
||||
auto& x = pixels[i];
|
||||
swap(x.r, x.b);
|
||||
Pixel a;
|
||||
Pixel const& b = pixels_y_minus_1[i];
|
||||
Pixel c;
|
||||
if (i != 0) {
|
||||
a = pixels[i - 1];
|
||||
c = pixels_y_minus_1[i - 1];
|
||||
break;
|
||||
case PNG::FilterType::Paeth:
|
||||
for (size_t i = 0; i < scanline_data.size(); ++i) {
|
||||
u8 left = (i < bytes_per_complete_pixel) ? 0 : scanline_data[i - bytes_per_complete_pixel];
|
||||
u8 above = previous_scanlines_data[i];
|
||||
u8 upper_left = (i < bytes_per_complete_pixel) ? 0 : previous_scanlines_data[i - bytes_per_complete_pixel];
|
||||
i32 predictor = left + above - upper_left;
|
||||
u32 predictor_left = abs(predictor - left);
|
||||
u32 predictor_above = abs(predictor - above);
|
||||
u32 predictor_upper_left = abs(predictor - upper_left);
|
||||
u8 nearest;
|
||||
if (predictor_left <= predictor_above && predictor_left <= predictor_upper_left) {
|
||||
nearest = left;
|
||||
} else if (predictor_above <= predictor_upper_left) {
|
||||
nearest = above;
|
||||
} else {
|
||||
nearest = upper_left;
|
||||
}
|
||||
x.v[0] += PNG::paeth_predictor(a.v[0], b.v[0], c.v[0]);
|
||||
x.v[1] += PNG::paeth_predictor(a.v[1], b.v[1], c.v[1]);
|
||||
x.v[2] += PNG::paeth_predictor(a.v[2], b.v[2], c.v[2]);
|
||||
if constexpr (has_alpha)
|
||||
x.v[3] += PNG::paeth_predictor(a.v[3], b.v[3], c.v[3]);
|
||||
scanline_data[i] += nearest;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,7 +292,47 @@ ALWAYS_INLINE static void unpack_triplets_with_transparency_value(PNGLoadingCont
|
|||
|
||||
NEVER_INLINE FLATTEN static ErrorOr<void> unfilter(PNGLoadingContext& context)
|
||||
{
|
||||
// First unpack the scanlines to RGBA:
|
||||
// First unfilter the scanlines:
|
||||
|
||||
// FIXME: Instead of creating a separate buffer for the scanlines that need to be
|
||||
// mutated, the mutation could be done in place (if the data was non-const).
|
||||
size_t bytes_per_scanline = context.scanlines[0].data.size();
|
||||
size_t bytes_needed_for_all_unfiltered_scanlines = 0;
|
||||
for (int y = 0; y < context.height; ++y) {
|
||||
if (context.scanlines[y].filter != PNG::FilterType::None) {
|
||||
bytes_needed_for_all_unfiltered_scanlines += bytes_per_scanline;
|
||||
}
|
||||
}
|
||||
context.unfiltered_data = TRY(ByteBuffer::create_uninitialized(bytes_needed_for_all_unfiltered_scanlines));
|
||||
|
||||
// From section 6.3 of http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html
|
||||
// "bpp is defined as the number of bytes per complete pixel, rounding up to one.
|
||||
// For example, for color type 2 with a bit depth of 16, bpp is equal to 6
|
||||
// (three samples, two bytes per sample); for color type 0 with a bit depth of 2,
|
||||
// bpp is equal to 1 (rounding up); for color type 4 with a bit depth of 16, bpp
|
||||
// is equal to 4 (two-byte grayscale sample, plus two-byte alpha sample)."
|
||||
u8 bytes_per_complete_pixel = (context.bit_depth + 7) / 8 * context.channels;
|
||||
|
||||
u8 dummy_scanline_bytes[bytes_per_scanline];
|
||||
memset(dummy_scanline_bytes, 0, sizeof(dummy_scanline_bytes));
|
||||
auto previous_scanlines_data = ReadonlyBytes { dummy_scanline_bytes, sizeof(dummy_scanline_bytes) };
|
||||
|
||||
for (int y = 0, data_start = 0; y < context.height; ++y) {
|
||||
if (context.scanlines[y].filter != PNG::FilterType::None) {
|
||||
auto scanline_data_slice = context.unfiltered_data.bytes().slice(data_start, bytes_per_scanline);
|
||||
|
||||
// Copy the current values over and set the scanline's data to the to-be-mutated slice
|
||||
context.scanlines[y].data.copy_to(scanline_data_slice);
|
||||
context.scanlines[y].data = scanline_data_slice;
|
||||
|
||||
unfilter_scanline(context.scanlines[y].filter, scanline_data_slice, previous_scanlines_data, bytes_per_complete_pixel);
|
||||
|
||||
data_start += bytes_per_scanline;
|
||||
}
|
||||
previous_scanlines_data = context.scanlines[y].data;
|
||||
}
|
||||
|
||||
// Now unpack the scanlines to RGBA:
|
||||
switch (context.color_type) {
|
||||
case PNG::ColorType::Greyscale:
|
||||
if (context.bit_depth == 8) {
|
||||
|
@ -448,45 +457,12 @@ NEVER_INLINE FLATTEN static ErrorOr<void> unfilter(PNGLoadingContext& context)
|
|||
break;
|
||||
}
|
||||
|
||||
u8 dummy_scanline[context.width * sizeof(ARGB32)];
|
||||
memset(dummy_scanline, 0, sizeof(dummy_scanline));
|
||||
|
||||
// Swap r and b values:
|
||||
for (int y = 0; y < context.height; ++y) {
|
||||
auto filter = context.scanlines[y].filter;
|
||||
if (filter == PNG::FilterType::None) {
|
||||
if (context.has_alpha())
|
||||
unfilter_impl<true, PNG::FilterType::None>(*context.bitmap, y, dummy_scanline);
|
||||
else
|
||||
unfilter_impl<false, PNG::FilterType::None>(*context.bitmap, y, dummy_scanline);
|
||||
continue;
|
||||
}
|
||||
if (filter == PNG::FilterType::Sub) {
|
||||
if (context.has_alpha())
|
||||
unfilter_impl<true, PNG::FilterType::Sub>(*context.bitmap, y, dummy_scanline);
|
||||
else
|
||||
unfilter_impl<false, PNG::FilterType::Sub>(*context.bitmap, y, dummy_scanline);
|
||||
continue;
|
||||
}
|
||||
if (filter == PNG::FilterType::Up) {
|
||||
if (context.has_alpha())
|
||||
unfilter_impl<true, PNG::FilterType::Up>(*context.bitmap, y, dummy_scanline);
|
||||
else
|
||||
unfilter_impl<false, PNG::FilterType::Up>(*context.bitmap, y, dummy_scanline);
|
||||
continue;
|
||||
}
|
||||
if (filter == PNG::FilterType::Average) {
|
||||
if (context.has_alpha())
|
||||
unfilter_impl<true, PNG::FilterType::Average>(*context.bitmap, y, dummy_scanline);
|
||||
else
|
||||
unfilter_impl<false, PNG::FilterType::Average>(*context.bitmap, y, dummy_scanline);
|
||||
continue;
|
||||
}
|
||||
if (filter == PNG::FilterType::Paeth) {
|
||||
if (context.has_alpha())
|
||||
unfilter_impl<true, PNG::FilterType::Paeth>(*context.bitmap, y, dummy_scanline);
|
||||
else
|
||||
unfilter_impl<false, PNG::FilterType::Paeth>(*context.bitmap, y, dummy_scanline);
|
||||
continue;
|
||||
auto* pixels = (Pixel*)context.bitmap->scanline(y);
|
||||
for (int i = 0; i < context.bitmap->width(); ++i) {
|
||||
auto& x = pixels[i];
|
||||
swap(x.r, x.b);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue