mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 18:17:45 +00:00
LibGfx: Add StackBlurFilter, an efficient almost gaussian blur
This adds an implementation of StackBlur which is a very efficient blur that closely approximates a gaussian blur. It has a number of benefits over the existing FastBoxBlurFilter: 1. It's faster (~half the pixel lookups of a single box blur pass) 2. It only requires a single pass over image to produce good results - (Rather than the 3 the box blur requires) 3. It only needs to allocate a buffer of `blur_radius * 2 + 1` colors - These easily fits on the stack for any reasonable radius For a detailed explanation of the algorithm check out this link: https://observablehq.com/@jobleonard/mario-klingemans-stackblur
This commit is contained in:
parent
072a78b958
commit
7f8cf81f7a
3 changed files with 345 additions and 6 deletions
|
@ -1,10 +1,10 @@
|
|||
set(SOURCES
|
||||
AffineTransform.cpp
|
||||
AntiAliasingPainter.cpp
|
||||
Bitmap.cpp
|
||||
BitmapMixer.cpp
|
||||
BMPLoader.cpp
|
||||
BMPWriter.cpp
|
||||
Bitmap.cpp
|
||||
BitmapMixer.cpp
|
||||
ClassicStylePainter.cpp
|
||||
ClassicWindowTheme.cpp
|
||||
Color.cpp
|
||||
|
@ -14,27 +14,28 @@ set(SOURCES
|
|||
Filters/ColorBlindnessFilter.cpp
|
||||
Filters/FastBoxBlurFilter.cpp
|
||||
Filters/LumaFilter.cpp
|
||||
Filters/StackBlurFilter.cpp
|
||||
Font/BitmapFont.cpp
|
||||
Font/Emoji.cpp
|
||||
Font/FontDatabase.cpp
|
||||
Font/ScaledFont.cpp
|
||||
Font/TrueType/Cmap.cpp
|
||||
Font/TrueType/Font.cpp
|
||||
Font/TrueType/Glyf.cpp
|
||||
Font/TrueType/Cmap.cpp
|
||||
Font/Typeface.cpp
|
||||
Font/WOFF/Font.cpp
|
||||
GIFLoader.cpp
|
||||
ICOLoader.cpp
|
||||
ImageDecoder.cpp
|
||||
JPGLoader.cpp
|
||||
Painter.cpp
|
||||
Palette.cpp
|
||||
Path.cpp
|
||||
PBMLoader.cpp
|
||||
PGMLoader.cpp
|
||||
PNGLoader.cpp
|
||||
PNGWriter.cpp
|
||||
PPMLoader.cpp
|
||||
Painter.cpp
|
||||
Palette.cpp
|
||||
Path.cpp
|
||||
Point.cpp
|
||||
QOILoader.cpp
|
||||
QOIWriter.cpp
|
||||
|
|
311
Userland/Libraries/LibGfx/Filters/StackBlurFilter.cpp
Normal file
311
Userland/Libraries/LibGfx/Filters/StackBlurFilter.cpp
Normal file
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* Copyright (c) 2010, Mario Klingemann <mario@quasimondo.com>
|
||||
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
# pragma GCC optimize("O3")
|
||||
#endif
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/Math.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGfx/Filters/StackBlurFilter.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
using uint = unsigned;
|
||||
|
||||
constexpr size_t MAX_RADIUS = 256;
|
||||
|
||||
// Magic lookup tables!
|
||||
// `(value * sum_mult[radius - 2]) >> shift_table[radius - 2]` closely approximates value/(radius*radius)
|
||||
// These LUTs are the same as the original, but converted to constexpr functions rather than magic numbers.
|
||||
|
||||
constexpr auto shift_table = [] {
|
||||
Array<u8, MAX_RADIUS> lut {};
|
||||
for (size_t r = 2; r <= MAX_RADIUS + 1; r++)
|
||||
lut[r - 2] = static_cast<u8>(AK::ceil_log2(256 * (r * r + 1)));
|
||||
return lut;
|
||||
}();
|
||||
|
||||
constexpr auto mult_table = [] {
|
||||
Array<u16, MAX_RADIUS> lut {};
|
||||
for (size_t r = 2; r <= MAX_RADIUS + 1; r++)
|
||||
lut[r - 2] = static_cast<u16>(AK::ceil(static_cast<double>(1 << shift_table[r - 2]) / (r * r)));
|
||||
return lut;
|
||||
}();
|
||||
|
||||
// Note: This is named to be consistent with the algorithm, but it's actually a simple circular buffer.
|
||||
struct BlurStack {
|
||||
BlurStack(size_t size)
|
||||
{
|
||||
m_data.resize(size);
|
||||
}
|
||||
|
||||
struct Iterator {
|
||||
friend BlurStack;
|
||||
|
||||
ALWAYS_INLINE Color& operator*()
|
||||
{
|
||||
return m_data.at(m_idx);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Color* operator->()
|
||||
{
|
||||
return &m_data.at(m_idx);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Iterator operator++()
|
||||
{
|
||||
// Note: This seemed to profile slightly better than %
|
||||
if (++m_idx >= m_data.size())
|
||||
m_idx = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Iterator operator++(int)
|
||||
{
|
||||
auto prev_it = *this;
|
||||
++*(this);
|
||||
return prev_it;
|
||||
}
|
||||
|
||||
private:
|
||||
Iterator(size_t idx, Span<Color> data)
|
||||
: m_idx(idx)
|
||||
, m_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
size_t m_idx;
|
||||
Span<Color> m_data;
|
||||
};
|
||||
|
||||
Iterator iterator_from_position(size_t position)
|
||||
{
|
||||
VERIFY(position < m_data.size());
|
||||
return Iterator(position, m_data);
|
||||
}
|
||||
|
||||
private:
|
||||
Vector<Color, 512> m_data;
|
||||
};
|
||||
|
||||
// This is an implementation of StackBlur by Mario Klingemann (https://observablehq.com/@jobleonard/mario-klingemans-stackblur)
|
||||
// (Link is to a secondary source as the original site is now down)
|
||||
FLATTEN void StackBlurFilter::process_rgba(u8 radius)
|
||||
{
|
||||
// TODO: Implement a plain RGB version of this (if required)
|
||||
|
||||
if (radius == 0)
|
||||
return;
|
||||
|
||||
constexpr auto transparent_white = Color(Color::White).with_alpha(0);
|
||||
|
||||
uint width = m_bitmap.width();
|
||||
uint height = m_bitmap.height();
|
||||
|
||||
uint div = 2 * radius + 1;
|
||||
uint radius_plus_1 = radius + 1;
|
||||
uint sum_factor = radius_plus_1 * (radius_plus_1 + 1) / 2;
|
||||
|
||||
auto get_pixel = [&](int x, int y) {
|
||||
auto color = m_bitmap.get_pixel<StorageFormat::BGRA8888>(x, y);
|
||||
if (color.alpha() == 0)
|
||||
return transparent_white;
|
||||
return color;
|
||||
};
|
||||
|
||||
auto set_pixel = [&](int x, int y, Color color) {
|
||||
return m_bitmap.set_pixel<StorageFormat::BGRA8888>(x, y, color);
|
||||
};
|
||||
|
||||
BlurStack blur_stack { div };
|
||||
auto const stack_start = blur_stack.iterator_from_position(0);
|
||||
auto const stack_end = blur_stack.iterator_from_position(radius_plus_1);
|
||||
auto stack_iterator = stack_start;
|
||||
|
||||
auto const sum_mult = mult_table[radius - 1];
|
||||
auto const sum_shift = shift_table[radius - 1];
|
||||
|
||||
for (uint y = 0; y < height; y++) {
|
||||
stack_iterator = stack_start;
|
||||
|
||||
auto color = get_pixel(0, y);
|
||||
for (uint i = 0; i < radius_plus_1; i++)
|
||||
*(stack_iterator++) = color;
|
||||
|
||||
// All the sums here work to approximate a gaussian.
|
||||
// Note: Only about 17 bits are actually used in each sum.
|
||||
uint red_in_sum = 0;
|
||||
uint green_in_sum = 0;
|
||||
uint blue_in_sum = 0;
|
||||
uint alpha_in_sum = 0;
|
||||
uint red_out_sum = radius_plus_1 * color.red();
|
||||
uint green_out_sum = radius_plus_1 * color.green();
|
||||
uint blue_out_sum = radius_plus_1 * color.blue();
|
||||
uint alpha_out_sum = radius_plus_1 * color.alpha();
|
||||
uint red_sum = sum_factor * color.red();
|
||||
uint green_sum = sum_factor * color.green();
|
||||
uint blue_sum = sum_factor * color.blue();
|
||||
uint alpha_sum = sum_factor * color.alpha();
|
||||
|
||||
for (uint i = 1; i <= radius; i++) {
|
||||
auto color = get_pixel(min(i, width - 1), y);
|
||||
|
||||
auto bias = radius_plus_1 - i;
|
||||
*stack_iterator = color;
|
||||
red_sum += color.red() * bias;
|
||||
green_sum += color.green() * bias;
|
||||
blue_sum += color.blue() * bias;
|
||||
alpha_sum += color.alpha() * bias;
|
||||
|
||||
red_in_sum += color.red();
|
||||
green_in_sum += color.green();
|
||||
blue_in_sum += color.blue();
|
||||
alpha_in_sum += color.alpha();
|
||||
|
||||
++stack_iterator;
|
||||
}
|
||||
|
||||
auto stack_in_iterator = stack_start;
|
||||
auto stack_out_iterator = stack_end;
|
||||
|
||||
for (uint x = 0; x < width; x++) {
|
||||
auto alpha = (alpha_sum * sum_mult) >> sum_shift;
|
||||
if (alpha != 0)
|
||||
set_pixel(x, y, Color((red_sum * sum_mult) >> sum_shift, (green_sum * sum_mult) >> sum_shift, (blue_sum * sum_mult) >> sum_shift, alpha));
|
||||
else
|
||||
set_pixel(x, y, transparent_white);
|
||||
|
||||
red_sum -= red_out_sum;
|
||||
green_sum -= green_out_sum;
|
||||
blue_sum -= blue_out_sum;
|
||||
alpha_sum -= alpha_out_sum;
|
||||
|
||||
red_out_sum -= stack_in_iterator->red();
|
||||
green_out_sum -= stack_in_iterator->green();
|
||||
blue_out_sum -= stack_in_iterator->blue();
|
||||
alpha_out_sum -= stack_in_iterator->alpha();
|
||||
|
||||
auto color = get_pixel(min(x + radius_plus_1, width - 1), y);
|
||||
*stack_in_iterator = color;
|
||||
red_in_sum += color.red();
|
||||
green_in_sum += color.green();
|
||||
blue_in_sum += color.blue();
|
||||
alpha_in_sum += color.alpha();
|
||||
|
||||
red_sum += red_in_sum;
|
||||
green_sum += green_in_sum;
|
||||
blue_sum += blue_in_sum;
|
||||
alpha_sum += alpha_in_sum;
|
||||
|
||||
++stack_in_iterator;
|
||||
|
||||
color = *stack_out_iterator;
|
||||
red_out_sum += color.red();
|
||||
green_out_sum += color.green();
|
||||
blue_out_sum += color.blue();
|
||||
alpha_out_sum += color.alpha();
|
||||
|
||||
red_in_sum -= color.red();
|
||||
green_in_sum -= color.green();
|
||||
blue_in_sum -= color.blue();
|
||||
alpha_in_sum -= color.alpha();
|
||||
|
||||
++stack_out_iterator;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint x = 0; x < width; x++) {
|
||||
stack_iterator = stack_start;
|
||||
|
||||
auto color = get_pixel(x, 0);
|
||||
for (uint i = 0; i < radius_plus_1; i++)
|
||||
*(stack_iterator++) = color;
|
||||
|
||||
uint red_in_sum = 0;
|
||||
uint green_in_sum = 0;
|
||||
uint blue_in_sum = 0;
|
||||
uint alpha_in_sum = 0;
|
||||
uint red_out_sum = radius_plus_1 * color.red();
|
||||
uint green_out_sum = radius_plus_1 * color.green();
|
||||
uint blue_out_sum = radius_plus_1 * color.blue();
|
||||
uint alpha_out_sum = radius_plus_1 * color.alpha();
|
||||
uint red_sum = sum_factor * color.red();
|
||||
uint green_sum = sum_factor * color.green();
|
||||
uint blue_sum = sum_factor * color.blue();
|
||||
uint alpha_sum = sum_factor * color.alpha();
|
||||
|
||||
for (uint i = 1; i <= radius; i++) {
|
||||
auto color = get_pixel(x, min(i, height - 1));
|
||||
|
||||
auto bias = radius_plus_1 - i;
|
||||
*stack_iterator = color;
|
||||
red_sum += color.red() * bias;
|
||||
green_sum += color.green() * bias;
|
||||
blue_sum += color.blue() * bias;
|
||||
alpha_sum += color.alpha() * bias;
|
||||
|
||||
red_in_sum += color.red();
|
||||
green_in_sum += color.green();
|
||||
blue_in_sum += color.blue();
|
||||
alpha_in_sum += color.alpha();
|
||||
|
||||
++stack_iterator;
|
||||
}
|
||||
|
||||
auto stack_in_iterator = stack_start;
|
||||
auto stack_out_iterator = stack_end;
|
||||
|
||||
for (uint y = 0; y < height; y++) {
|
||||
auto alpha = (alpha_sum * sum_mult) >> sum_shift;
|
||||
if (alpha != 0)
|
||||
set_pixel(x, y, Color((red_sum * sum_mult) >> sum_shift, (green_sum * sum_mult) >> sum_shift, (blue_sum * sum_mult) >> sum_shift, alpha));
|
||||
else
|
||||
set_pixel(x, y, transparent_white);
|
||||
|
||||
red_sum -= red_out_sum;
|
||||
green_sum -= green_out_sum;
|
||||
blue_sum -= blue_out_sum;
|
||||
alpha_sum -= alpha_out_sum;
|
||||
|
||||
red_out_sum -= stack_in_iterator->red();
|
||||
green_out_sum -= stack_in_iterator->green();
|
||||
blue_out_sum -= stack_in_iterator->blue();
|
||||
alpha_out_sum -= stack_in_iterator->alpha();
|
||||
|
||||
auto color = get_pixel(x, min(y + radius_plus_1, height - 1));
|
||||
*stack_in_iterator = color;
|
||||
red_in_sum += color.red();
|
||||
green_in_sum += color.green();
|
||||
blue_in_sum += color.blue();
|
||||
alpha_in_sum += color.alpha();
|
||||
|
||||
red_sum += red_in_sum;
|
||||
green_sum += green_in_sum;
|
||||
blue_sum += blue_in_sum;
|
||||
alpha_sum += alpha_in_sum;
|
||||
|
||||
++stack_in_iterator;
|
||||
|
||||
color = *stack_out_iterator;
|
||||
red_out_sum += color.red();
|
||||
green_out_sum += color.green();
|
||||
blue_out_sum += color.blue();
|
||||
alpha_out_sum += color.alpha();
|
||||
|
||||
red_in_sum -= color.red();
|
||||
green_in_sum -= color.green();
|
||||
blue_in_sum -= color.blue();
|
||||
alpha_in_sum -= color.alpha();
|
||||
|
||||
++stack_out_iterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
27
Userland/Libraries/LibGfx/Filters/StackBlurFilter.h
Normal file
27
Userland/Libraries/LibGfx/Filters/StackBlurFilter.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
class StackBlurFilter {
|
||||
public:
|
||||
StackBlurFilter(Bitmap& bitmap)
|
||||
: m_bitmap(bitmap)
|
||||
{
|
||||
}
|
||||
|
||||
// Note: The radius is a u8 for reason! This implementation can only handle radii from 0 to 255.
|
||||
void process_rgba(u8 radius);
|
||||
|
||||
private:
|
||||
Bitmap& m_bitmap;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue