[Pixman] [PATCH 1/4] Implement basic dithering for the wide pipeline
basile-pixman at clement.pm
basile-pixman at clement.pm
Tue Apr 9 21:29:24 UTC 2019
From: Basile Clement <basile-pixman at clement.pm>
This patch implements dithering in pixman. A "dither" property is added
to BITS images, which is used to:
- Force rendering to the image to go through the floating point
pipeline. Note that this is different from FAST_PATH_NARROW_FORMAT
as it should not enable the floating point pipeline when reading from
the image.
- Enable dithering in dest_write_back_wide. The dithering uses the
destination format to determine noise amplitude.
This does not change pixman's behavior when dithering is disabled (the
default).
Additional types and functions are added to the public API:
- The `pixman_dither_t` enum exposes the available dithering methods.
Currently a single dithering method based on 8x8 Bayer matrices is
implemented (PIXMAN_DITHER_ORDERED_BAYER_8). The PIXMAN_DITHER_FAST,
PIXMAN_DITHER_GOOD and PIXMAN_DITHER_BEST aliases are provided and
should be used to benefit from future specializations.
- The `pixman_image_set_dither` function allows to set the dithering
method to use when rendering to a bits image.
- The `pixman_image_set_dither_offset` function allows to set a
vertical and horizontal offsets for the dither matrix. This can be
used after scrolling to ensure a consistent spatial positioning of
the dither matrix.
Changes since previous version:
- Renamed PIXMAN_DITHER_BAYER_8 to PIXMAN_DITHER_ORDERED_BAYER_8
- Disable dithering for channels with 32bpp or more (since they can
represent exactly the wide values already). This makes the patches
compatible with the newly added floating point format.
---
pixman/pixman-bits-image.c | 122 ++++++++++++++++++++++++++++++++
pixman/pixman-general.c | 3 +-
pixman/pixman-image.c | 35 +++++++++
pixman/pixman-linear-gradient.c | 11 +--
pixman/pixman-private.h | 4 ++
pixman/pixman.h | 14 ++++
6 files changed, 183 insertions(+), 6 deletions(-)
diff --git a/pixman/pixman-bits-image.c b/pixman/pixman-bits-image.c
index 7bc2ba8..fe3919b 100644
--- a/pixman/pixman-bits-image.c
+++ b/pixman/pixman-bits-image.c
@@ -1048,6 +1048,112 @@ dest_write_back_narrow (pixman_iter_t *iter)
iter->y++;
}
+static const float
+dither_factor_bayer_8 (int x, int y)
+{
+ uint32_t m;
+
+ y ^= x;
+
+ /* Compute reverse(interleave(xor(x mod n, y mod n), x mod n))
+ * Here n = 8 and `mod n` is the bottom 3 bits.
+ */
+ m = ((y & 0b001) << 5) | ((x & 0b001) << 4) |
+ ((y & 0b010) << 2) | ((x & 0b010) << 1) |
+ ((y & 0b100) >> 1) | ((x & 0b100) >> 2);
+
+ /* m is in range [0, 63]. We scale it to [0, 63.0f/64.0f], then
+ * shift it to to [1.0f/128.0f, 127.0f/128.0f] so that 0 < d < 1.
+ * This ensures exact values are not changed by dithering.
+ */
+ return (float)(m) * (1 / 64.0f) + (1.0f / 128.0f);
+}
+
+typedef float (* dither_factor_t)(int x, int y);
+
+static force_inline float
+dither_apply_channel (float f, float d, float s)
+{
+ /* float_to_unorm splits the [0, 1] segment in (1 << n_bits)
+ * subsections of equal length; however unorm_to_float does not
+ * map to the center of those sections. In fact, pixel value u is
+ * mapped to:
+ *
+ * u u u 1
+ * -------------- = ---------- + -------------- * ----------
+ * 2^n_bits - 1 2^n_bits 2^n_bits - 1 2^n_bits
+ *
+ * Hence if f = u / (2^n_bits - 1) is exactly representable on a
+ * n_bits palette, all the numbers between
+ *
+ * u
+ * ---------- = f - f * 2^n_bits = f + (0 - f) * 2^n_bits
+ * 2^n_bits
+ *
+ * and
+ *
+ * u + 1
+ * ---------- = f - (f - 1) * 2^n_bits = f + (1 - f) * 2^n_bits
+ * 2^n_bits
+ *
+ * are also mapped back to u.
+ *
+ * Hence the following calculation ensures that we add as much
+ * noise as possible without perturbing values which are exactly
+ * representable in the target colorspace. Note that this corresponds to
+ * mixing the original color with noise with a ratio of `1 / 2^n_bits`.
+ */
+ return f + (d - f) * s;
+}
+
+static force_inline float
+dither_compute_scale (int n_bits)
+{
+ // No dithering for wide formats
+ if (n_bits == 0 || n_bits >= 32)
+ return 0.f;
+
+ return 1.f / (float)(1 << n_bits);
+}
+
+static const uint32_t *
+dither_apply_ordered (pixman_iter_t *iter, dither_factor_t factor)
+{
+ bits_image_t *image = &iter->image->bits;
+ int x = iter->x + image->dither_offset_x;
+ int y = iter->y + image->dither_offset_y;
+ int width = iter->width;
+ argb_t *buffer = (argb_t *)iter->buffer;
+
+ pixman_format_code_t format = image->format;
+ int a_size = PIXMAN_FORMAT_A (format);
+ int r_size = PIXMAN_FORMAT_R (format);
+ int g_size = PIXMAN_FORMAT_G (format);
+ int b_size = PIXMAN_FORMAT_B (format);
+
+ float a_scale = dither_compute_scale (a_size);
+ float r_scale = dither_compute_scale (r_size);
+ float g_scale = dither_compute_scale (g_size);
+ float b_scale = dither_compute_scale (b_size);
+
+ int i;
+ float d;
+
+ for (i = 0; i < width; ++i)
+ {
+ d = factor (x + i, y);
+
+ buffer->a = dither_apply_channel (buffer->a, d, a_scale);
+ buffer->r = dither_apply_channel (buffer->r, d, r_scale);
+ buffer->g = dither_apply_channel (buffer->g, d, g_scale);
+ buffer->b = dither_apply_channel (buffer->b, d, b_scale);
+
+ buffer++;
+ }
+
+ return iter->buffer;
+}
+
static void
dest_write_back_wide (pixman_iter_t *iter)
{
@@ -1057,6 +1163,19 @@ dest_write_back_wide (pixman_iter_t *iter)
int width = iter->width;
const uint32_t *buffer = iter->buffer;
+ switch (image->dither)
+ {
+ case PIXMAN_DITHER_NONE:
+ break;
+
+ case PIXMAN_DITHER_FAST:
+ case PIXMAN_DITHER_GOOD:
+ case PIXMAN_DITHER_BEST:
+ case PIXMAN_DITHER_ORDERED_BAYER_8:
+ buffer = dither_apply_ordered (iter, dither_factor_bayer_8);
+ break;
+ }
+
image->store_scanline_float (image, x, y, width, buffer);
if (image->common.alpha_map)
@@ -1172,6 +1291,9 @@ _pixman_bits_image_init (pixman_image_t * image,
image->bits.height = height;
image->bits.bits = bits;
image->bits.free_me = free_me;
+ image->bits.dither = PIXMAN_DITHER_NONE;
+ image->bits.dither_offset_x = 0;
+ image->bits.dither_offset_y = 0;
image->bits.read_func = NULL;
image->bits.write_func = NULL;
image->bits.rowstride = rowstride;
diff --git a/pixman/pixman-general.c b/pixman/pixman-general.c
index 6141cb0..7d74f98 100644
--- a/pixman/pixman-general.c
+++ b/pixman/pixman-general.c
@@ -141,7 +141,8 @@ general_composite_rect (pixman_implementation_t *imp,
if ((src_image->common.flags & FAST_PATH_NARROW_FORMAT) &&
(!mask_image || mask_image->common.flags & FAST_PATH_NARROW_FORMAT) &&
(dest_image->common.flags & FAST_PATH_NARROW_FORMAT) &&
- !(operator_needs_division (op)))
+ !(operator_needs_division (op)) &&
+ (dest_image->bits.dither == PIXMAN_DITHER_NONE))
{
width_flag = ITER_NARROW;
Bpp = 4;
diff --git a/pixman/pixman-image.c b/pixman/pixman-image.c
index 7a851e2..461164a 100644
--- a/pixman/pixman-image.c
+++ b/pixman/pixman-image.c
@@ -684,6 +684,41 @@ pixman_image_set_repeat (pixman_image_t *image,
image_property_changed (image);
}
+PIXMAN_EXPORT void
+pixman_image_set_dither (pixman_image_t *image,
+ pixman_dither_t dither)
+{
+ if (image->type == BITS)
+ {
+ if (image->bits.dither == dither)
+ return;
+
+ image->bits.dither = dither;
+
+ image_property_changed (image);
+ }
+}
+
+PIXMAN_EXPORT void
+pixman_image_set_dither_offset (pixman_image_t *image,
+ int offset_x,
+ int offset_y)
+{
+ if (image->type == BITS)
+ {
+ if (image->bits.dither_offset_x == offset_x &&
+ image->bits.dither_offset_y == offset_y)
+ {
+ return;
+ }
+
+ image->bits.dither_offset_x = offset_x;
+ image->bits.dither_offset_y = offset_y;
+
+ image_property_changed (image);
+ }
+}
+
PIXMAN_EXPORT pixman_bool_t
pixman_image_set_filter (pixman_image_t * image,
pixman_filter_t filter,
diff --git a/pixman/pixman-linear-gradient.c b/pixman/pixman-linear-gradient.c
index 3f52850..b6bf17b 100644
--- a/pixman/pixman-linear-gradient.c
+++ b/pixman/pixman-linear-gradient.c
@@ -241,13 +241,14 @@ linear_get_scanline_wide (pixman_iter_t *iter, const uint32_t *mask)
void
_pixman_linear_gradient_iter_init (pixman_image_t *image, pixman_iter_t *iter)
{
- if (linear_gradient_is_horizontal (
+ /* Disable horizontal gradient optimization for wide iterators as
+ * it interferes with dithering.
+ */
+ if ((iter->iter_flags & ITER_NARROW) &&
+ linear_gradient_is_horizontal (
iter->image, iter->x, iter->y, iter->width, iter->height))
{
- if (iter->iter_flags & ITER_NARROW)
- linear_get_scanline_narrow (iter, NULL);
- else
- linear_get_scanline_wide (iter, NULL);
+ linear_get_scanline_narrow (iter, NULL);
iter->get_scanline = _pixman_iter_get_scanline_noop;
}
diff --git a/pixman/pixman-private.h b/pixman/pixman-private.h
index 1bd9695..41f430d 100644
--- a/pixman/pixman-private.h
+++ b/pixman/pixman-private.h
@@ -180,6 +180,10 @@ struct bits_image
uint32_t * free_me;
int rowstride; /* in number of uint32_t's */
+ pixman_dither_t dither;
+ uint32_t dither_offset_y;
+ uint32_t dither_offset_x;
+
fetch_scanline_t fetch_scanline_32;
fetch_pixel_32_t fetch_pixel_32;
store_scanline_t store_scanline_32;
diff --git a/pixman/pixman.h b/pixman/pixman.h
index d644589..1f63f55 100644
--- a/pixman/pixman.h
+++ b/pixman/pixman.h
@@ -285,6 +285,15 @@ typedef enum
PIXMAN_REPEAT_REFLECT
} pixman_repeat_t;
+typedef enum
+{
+ PIXMAN_DITHER_NONE,
+ PIXMAN_DITHER_FAST,
+ PIXMAN_DITHER_GOOD,
+ PIXMAN_DITHER_BEST,
+ PIXMAN_DITHER_ORDERED_BAYER_8,
+} pixman_dither_t;
+
typedef enum
{
PIXMAN_FILTER_FAST,
@@ -826,6 +835,11 @@ pixman_bool_t pixman_image_set_transform (pixman_image_t
const pixman_transform_t *transform);
void pixman_image_set_repeat (pixman_image_t *image,
pixman_repeat_t repeat);
+void pixman_image_set_dither (pixman_image_t *image,
+ pixman_dither_t dither);
+void pixman_image_set_dither_offset (pixman_image_t *image,
+ int offset_x,
+ int offset_y);
pixman_bool_t pixman_image_set_filter (pixman_image_t *image,
pixman_filter_t filter,
const pixman_fixed_t *filter_params,
--
2.21.0
More information about the Pixman
mailing list