[1/4] Implement basic dithering for the wide pipeline

Submitted by basile-pixman@clement.pm on April 9, 2019, 9:29 p.m.

Details

Message ID 20190409212927.12731-2-basile-pixman@clement.pm
State New
Headers show
Series "Series without cover letter" ( rev: 1 ) in Pixman

Not browsing as part of any series.

Commit Message

basile-pixman@clement.pm April 9, 2019, 9:29 p.m.
From: Basile Clement <basile-pixman@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(-)

Patch hide | download patch | download mbox

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,

Comments

v2 was disabling the `linear_gradient_is_horizontal` optimization when
the wide pipeline is used because a very old version of this patch was
performing dithering in `linear_get_scanline`, which required it to be
recomputed for each scanline even for horizontal gradients.  This is no
longer needed with the current approach.