diff --git a/doc/public/cairo-sections.txt b/doc/public/cairo-sections.txt index 81c18a2de..f6dc84813 100644 --- a/doc/public/cairo-sections.txt +++ b/doc/public/cairo-sections.txt @@ -409,6 +409,9 @@ cairo_pattern_get_type cairo_pattern_get_reference_count cairo_pattern_set_user_data cairo_pattern_get_user_data +cairo_dither_t +cairo_set_dither +cairo_get_dither
diff --git a/src/cairo-image-surface.c b/src/cairo-image-surface.c index 553e32605..fe64cd76c 100644 --- a/src/cairo-image-surface.c +++ b/src/cairo-image-surface.c @@ -248,6 +248,27 @@ _pixman_format_from_masks (cairo_format_masks_t *masks, return TRUE; } +#if PIXMAN_VERSION >= PIXMAN_VERSION_ENCODE(0,39,0) +/* Convenience function to convert #cairo_dither_t into #pixman_dither_t */ +static pixman_dither_t +_cairo_dither_to_pixman_dither (cairo_dither_t dither) +{ + switch (dither) { + case CAIRO_DITHER_FAST: + return PIXMAN_DITHER_FAST; + case CAIRO_DITHER_GOOD: + return PIXMAN_DITHER_GOOD; + case CAIRO_DITHER_BEST: + return PIXMAN_DITHER_BEST; + case CAIRO_DITHER_NONE: + case CAIRO_DITHER_DEFAULT: + default: + return PIXMAN_DITHER_NONE; + } +} +#endif + + /* A mask consisting of N bits set to 1. */ #define MASK(N) ((1UL << (N))-1) @@ -930,6 +951,8 @@ _cairo_image_surface_paint (void *abstract_surface, const cairo_clip_t *clip) { cairo_image_surface_t *surface = abstract_surface; + pixman_dither_t pixman_dither = _cairo_dither_to_pixman_dither (source->dither); + pixman_image_set_dither (surface->pixman_image, pixman_dither); TRACE ((stderr, "%s (surface=%d)\n", __FUNCTION__, surface->base.unique_id)); diff --git a/src/cairo-pattern-private.h b/src/cairo-pattern-private.h index d061b39c4..0e69d0177 100644 --- a/src/cairo-pattern-private.h +++ b/src/cairo-pattern-private.h @@ -52,6 +52,7 @@ enum { CAIRO_PATTERN_NOTIFY_FILTER = 0x2, CAIRO_PATTERN_NOTIFY_EXTEND = 0x4, CAIRO_PATTERN_NOTIFY_OPACITY = 0x9, + CAIRO_PATTERN_NOTIFY_DITHER = 0x12, }; struct _cairo_pattern_observer { @@ -73,6 +74,7 @@ struct _cairo_pattern { cairo_extend_t extend; cairo_bool_t has_component_alpha; cairo_bool_t is_foreground_marker; + cairo_dither_t dither; cairo_matrix_t matrix; double opacity; diff --git a/src/cairo-pattern.c b/src/cairo-pattern.c index 1933fb80a..23c43855c 100644 --- a/src/cairo-pattern.c +++ b/src/cairo-pattern.c @@ -77,6 +77,7 @@ static const cairo_solid_pattern_t _cairo_pattern_nil = { CAIRO_EXTEND_GRADIENT_DEFAULT, /* extend */ FALSE, /* has component alpha */ FALSE, /* is_foreground_marker */ + CAIRO_DITHER_DEFAULT, /* dither */ { 1., 0., 0., 1., 0., 0., }, /* matrix */ 1.0 /* opacity */ } @@ -94,6 +95,7 @@ static const cairo_solid_pattern_t _cairo_pattern_nil_null_pointer = { CAIRO_EXTEND_GRADIENT_DEFAULT, /* extend */ FALSE, /* has component alpha */ FALSE, /* is_foreground_marker */ + CAIRO_DITHER_DEFAULT, /* dither */ { 1., 0., 0., 1., 0., 0., }, /* matrix */ 1.0 /* opacity */ } @@ -111,6 +113,7 @@ const cairo_solid_pattern_t _cairo_pattern_black = { CAIRO_EXTEND_REPEAT, /* extend */ FALSE, /* has component alpha */ FALSE, /* is_foreground_marker */ + CAIRO_DITHER_DEFAULT, /* dither */ { 1., 0., 0., 1., 0., 0., }, /* matrix */ 1.0 /* opacity */ }, @@ -129,6 +132,7 @@ const cairo_solid_pattern_t _cairo_pattern_clear = { CAIRO_EXTEND_REPEAT, /* extend */ FALSE, /* has component alpha */ FALSE, /* is_foreground_marker */ + CAIRO_DITHER_DEFAULT, /* dither */ { 1., 0., 0., 1., 0., 0., }, /* matrix */ 1.0 /* opacity */ }, @@ -147,6 +151,7 @@ const cairo_solid_pattern_t _cairo_pattern_white = { CAIRO_EXTEND_REPEAT, /* extend */ FALSE, /* has component alpha */ FALSE, /* is_foreground_marker */ + CAIRO_DITHER_DEFAULT, /* dither */ { 1., 0., 0., 1., 0., 0., }, /* matrix */ 1.0 /* opacity */ }, @@ -240,6 +245,8 @@ _cairo_pattern_init (cairo_pattern_t *pattern, cairo_pattern_type_t type) pattern->has_component_alpha = FALSE; pattern->is_foreground_marker = FALSE; + pattern->dither = CAIRO_DITHER_DEFAULT; + cairo_matrix_init_identity (&pattern->matrix); cairo_list_init (&pattern->observers); @@ -2090,6 +2097,45 @@ cairo_pattern_get_filter (cairo_pattern_t *pattern) return pattern->filter; } +/** + * cairo_pattern_get_dither: + * @pattern: a #cairo_pattern_t + * + * Gets the current dithering mode, as set by + * cairo_pattern_set_dither(). + * + * Return value: the current dithering mode. + * + * Since: 1.18 + **/ +cairo_dither_t +cairo_pattern_get_dither (cairo_pattern_t *pattern) +{ + return pattern->dither; +} + +/** + * cairo_pattern_set_dither: + * @pattern: a #cairo_pattern_t + * @dither: a #cairo_dither_t describing the new dithering mode + * + * Set the dithering mode of the rasterizer used for drawing shapes. + * This value is a hint, and a particular backend may or may not support + * a particular value. At the current time, only pixman is supported. + * + * Since: 1.18 + **/ +void +cairo_pattern_set_dither (cairo_pattern_t *pattern, cairo_dither_t dither) +{ + if (pattern->status) + return; + + pattern->dither = dither; + _cairo_pattern_notify_observers (pattern, CAIRO_PATTERN_NOTIFY_DITHER); + +} + /** * cairo_pattern_set_extend: * @pattern: a #cairo_pattern_t diff --git a/src/cairo-script-surface.c b/src/cairo-script-surface.c index ca4db5a69..46790ab9a 100644 --- a/src/cairo-script-surface.c +++ b/src/cairo-script-surface.c @@ -370,6 +370,21 @@ _filter_to_string (cairo_filter_t filter) return names[filter]; } +static const char * +_dither_to_string (cairo_dither_t dither) +{ + static const char *names[] = { + "DITHER_DEFAULT", /* CAIRO_FILTER_FAST */ + "DITHER_NONE", /* CAIRO_FILTER_GOOD */ + "DITHER_FAST", /* CAIRO_FILTER_BEST */ + "DITHER_GOOD", /* CAIRO_FILTER_NEAREST */ + "DITHER_BEST", /* CAIRO_FILTER_BILINEAR */ + }; + assert (dither < ARRAY_LENGTH (names)); + return names[dither]; +} + + static const char * _fill_rule_to_string (cairo_fill_rule_t rule) { @@ -1731,6 +1746,17 @@ _emit_pattern (cairo_script_surface_t *surface, " //%s set-filter\n ", _filter_to_string (pattern->filter)); } + /* XXX need to discriminate the user explicitly setting the default */ + if (pattern->dither != CAIRO_DITHER_DEFAULT) { + if (need_newline) { + _cairo_output_stream_puts (ctx->stream, "\n "); + need_newline = FALSE; + } + + _cairo_output_stream_printf (ctx->stream, + " //%s set-dither\n ", + _dither_to_string (pattern->dither)); + } if (! is_default_extend ){ if (need_newline) { _cairo_output_stream_puts (ctx->stream, "\n "); diff --git a/src/cairo-surface.c b/src/cairo-surface.c index c208f99f2..2f5a6ac3d 100644 --- a/src/cairo-surface.c +++ b/src/cairo-surface.c @@ -127,13 +127,18 @@ const cairo_surface_t name = { \ NULL, /* snapshot_detach */ \ { NULL, NULL }, /* snapshots */ \ { NULL, NULL }, /* snapshot */ \ - { CAIRO_ANTIALIAS_DEFAULT, /* antialias */ \ + { /* font options begin */\ + CAIRO_ANTIALIAS_DEFAULT, /* antialias */ \ CAIRO_SUBPIXEL_ORDER_DEFAULT, /* subpixel_order */ \ CAIRO_LCD_FILTER_DEFAULT, /* lcd_filter */ \ CAIRO_HINT_STYLE_DEFAULT, /* hint_style */ \ CAIRO_HINT_METRICS_DEFAULT, /* hint_metrics */ \ - CAIRO_ROUND_GLYPH_POS_DEFAULT /* round_glyph_positions */ \ - }, /* font_options */ \ + CAIRO_ROUND_GLYPH_POS_DEFAULT, /* round_glyph_positions */ \ + NULL, /* variations */ \ + CAIRO_COLOR_MODE_DEFAULT, /* color mode */ \ + CAIRO_COLOR_PALETTE_DEFAULT, /* color palette */ \ + NULL, 0, /* custom palette */ \ + }, /* font_options end */ \ NULL, /* foreground_source */ \ FALSE, /* foreground_used */ \ } diff --git a/src/cairo.h b/src/cairo.h index f6028a243..cd529c26e 100644 --- a/src/cairo.h +++ b/src/cairo.h @@ -440,6 +440,39 @@ typedef enum _cairo_format { CAIRO_FORMAT_RGBA128F = 7 } cairo_format_t; +/** + * cairo_dither_t: + * @CAIRO_DITHER_NONE: No dithering. + * @CAIRO_DITHER_DEFAULT: Default choice at cairo compile time. Currently NONE. + * @CAIRO_DITHER_FAST: Fastest dithering algorithm supported by the backend + * @CAIRO_DITHER_GOOD: An algorithm with smoother dithering than FAST + * @CAIRO_DITHER_BEST: Best algorithm available in the backend + * + * Dither is an intentionally applied form of noise used to randomize + * quantization error, preventing large-scale patterns such as color banding + * in images (e.g. for gradients). Ordered dithering applies a precomputed + * threshold matrix to spread the errors smoothly. + * + * #cairo_dither_t is modeled on pixman dithering algorithm choice. + * As of Pixman 0.40, FAST corresponds to a 8x8 ordered bayer noise and GOOD + * and BEST use an ordered 64x64 precomputed blue noise. + * + * Since: 1.18 + **/ +#define CAIRO_HAS_DITHER +typedef enum _cairo_dither { + CAIRO_DITHER_NONE, + CAIRO_DITHER_DEFAULT, + CAIRO_DITHER_FAST, + CAIRO_DITHER_GOOD, + CAIRO_DITHER_BEST +} cairo_dither_t; + +cairo_public void +cairo_pattern_set_dither (cairo_pattern_t *pattern, cairo_dither_t dither); + +cairo_public cairo_dither_t +cairo_pattern_get_dither (cairo_pattern_t *pattern); /** * cairo_write_func_t: diff --git a/src/cairoint.h b/src/cairoint.h index c906c25c1..cac7f56d4 100644 --- a/src/cairoint.h +++ b/src/cairoint.h @@ -1606,6 +1606,7 @@ cairo_private cairo_bool_t _pixman_format_to_masks (pixman_format_code_t pixman_format, cairo_format_masks_t *masks); + cairo_private void _cairo_image_scaled_glyph_fini (cairo_scaled_font_t *scaled_font, cairo_scaled_glyph_t *scaled_glyph); diff --git a/test/dithergradient.c b/test/dithergradient.c new file mode 100644 index 000000000..1ad1aadc7 --- /dev/null +++ b/test/dithergradient.c @@ -0,0 +1,80 @@ +/* + * Copyright © 2023 Marc Jeanmougin + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of + * Red Hat, Inc. not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. Red Hat, Inc. makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Author: Marc Jeanmougin + */ + +#include "cairo-test.h" + +static void +set_dither_source (cairo_t *cr, int width) +{ + cairo_pattern_t *gradient = cairo_pattern_create_linear (0, 0, width, 0); + cairo_pattern_add_color_stop_rgba (gradient, 0., 25./255, 25./255, 25./255, 1.0); + cairo_pattern_add_color_stop_rgba (gradient, 1., 45./255, 45./255, 45./255, 1.0); + + cairo_set_source (cr, gradient); + cairo_pattern_set_dither (gradient, CAIRO_DITHER_BEST); + cairo_pattern_destroy (gradient); +} + +/* History: + * + * 2023: v3 of a patch to use pixman dithering with cairo + */ +static cairo_test_status_t +draw (cairo_t *cr, int width, int height) +{ + set_dither_source (cr, width); + cairo_paint (cr); + + return CAIRO_TEST_SUCCESS; +} + +static cairo_test_status_t +draw2 (cairo_t *cr, int width, int height) +{ + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_paint (cr); + + set_dither_source (cr, width); + + cairo_set_operator (cr, CAIRO_OPERATOR_ADD); + for (int i = 0; i < 5; i++) { + cairo_paint (cr); + } + + return CAIRO_TEST_SUCCESS; +} + +CAIRO_TEST (dithergradient, + "Testing the creation of a dithered gradient (in argb32)", + "gradient, dither", /* keywords */ + NULL, /* requirements */ + 400, 100, + NULL, draw) +CAIRO_TEST (dithergradient2, + "Testing the creation of a dithered gradient (in argb32)", + "gradient, dither", /* keywords */ + NULL, /* requirements */ + 400, 100, + NULL, draw2) diff --git a/test/meson.build b/test/meson.build index 70f517f3c..53a8eb675 100644 --- a/test/meson.build +++ b/test/meson.build @@ -124,6 +124,7 @@ test_sources = [ 'device-offset-fractional.c', 'device-offset-positive.c', 'device-offset-scale.c', + 'dithergradient.c', 'error-setters.c', 'extend-pad.c', 'extend-pad-border.c', diff --git a/test/reference/dithergradient.image.argb32.ref.png b/test/reference/dithergradient.image.argb32.ref.png new file mode 100644 index 000000000..c44e12380 Binary files /dev/null and b/test/reference/dithergradient.image.argb32.ref.png differ diff --git a/test/reference/dithergradient.image.rgb24.ref.png b/test/reference/dithergradient.image.rgb24.ref.png new file mode 100644 index 000000000..c44e12380 Binary files /dev/null and b/test/reference/dithergradient.image.rgb24.ref.png differ diff --git a/test/reference/dithergradient.image16.rgb24.ref.png b/test/reference/dithergradient.image16.rgb24.ref.png new file mode 100644 index 000000000..fa1950a88 Binary files /dev/null and b/test/reference/dithergradient.image16.rgb24.ref.png differ