diff --git a/src/cairo-pattern.c b/src/cairo-pattern.c index 59309f8c1..1b79011be 100644 --- a/src/cairo-pattern.c +++ b/src/cairo-pattern.c @@ -1816,6 +1816,116 @@ _gradient_is_clear (const cairo_gradient_pattern_t *gradient, return TRUE; } +static void +_gradient_color_average (const cairo_gradient_pattern_t *gradient, + cairo_color_t *color) +{ + double delta0, delta1; + double r, g, b, a; + unsigned int i, start = 1, end; + + assert (gradient->n_stops > 0); + assert (gradient->base.extend != CAIRO_EXTEND_NONE); + + if (gradient->n_stops == 1) { + _cairo_color_init_rgba (color, + gradient->stops[0].color.red, + gradient->stops[0].color.green, + gradient->stops[0].color.blue, + gradient->stops[0].color.alpha); + return; + } + + end = gradient->n_stops - 1; + + switch (gradient->base.extend) { + case CAIRO_EXTEND_REPEAT: + /* + * Sa, Sb and Sy, Sz are the first two and last two stops respectively. + * The weight of the first and last stop can be computed as the area of + * the following triangles (taken with height 1, since the whole [0-1] + * will have total weight 1 this way): b*h/2 + * + * + + + * / |\ / | \ + * / | \ / | \ + * / | \ / | \ + * ~~~~~+---+---+---+~~~~~~~+-------+---+---+~~~~~ + * -1+Sz 0 Sa Sb Sy Sz 1 1+Sa + * + * For the first stop: (Sb-(-1+Sz)/2 = (1+Sb-Sz)/2 + * For the last stop: ((1+Sa)-Sy)/2 = (1+Sa-Sy)/2 + * Halving the result is done after summing up all the areas. + */ + delta0 = 1.0 + gradient->stops[1].offset - gradient->stops[end].offset; + delta1 = 1.0 + gradient->stops[0].offset - gradient->stops[end-1].offset; + break; + + case CAIRO_EXTEND_REFLECT: + /* + * Sa, Sb and Sy, Sz are the first two and last two stops respectively. + * The weight of the first and last stop can be computed as the area of + * the following trapezoids (taken with height 1, since the whole [0-1] + * will have total weight 1 this way): (b+B)*h/2 + * + * +-------+ +---+ + * | |\ / | | + * | | \ / | | + * | | \ / | | + * +-------+---+~~~~~~~+-------+---+ + * 0 Sa Sb Sy Sz 1 + * + * For the first stop: (Sa+Sb)/2 + * For the last stop: ((1-Sz) + (1-Sy))/2 = (2-Sy-Sz)/2 + * Halving the result is done after summing up all the areas. + */ + delta0 = gradient->stops[0].offset + gradient->stops[1].offset; + delta1 = 2.0 - gradient->stops[end-1].offset - gradient->stops[end].offset; + break; + + case CAIRO_EXTEND_PAD: + /* PAD is computed as the average of the first and last stop: + * - take both of them with weight 1 (they will be halved + * after the whole sum has been computed). + * - avoid summing any of the inner stops. + */ + delta0 = delta1 = 1.0; + start = end; + break; + + case CAIRO_EXTEND_NONE: + default: + ASSERT_NOT_REACHED; + _cairo_color_init_rgba (color, 0, 0, 0, 0); + return; + } + + r = delta0 * gradient->stops[0].color.red; + g = delta0 * gradient->stops[0].color.green; + b = delta0 * gradient->stops[0].color.blue; + a = delta0 * gradient->stops[0].color.alpha; + + for (i = start; i < end; ++i) { + /* Inner stops weight is the same as the area of the triangle they influence + * (which goes from the stop before to the stop after), again with height 1 + * since the whole must sum up to 1: b*h/2 + * Halving is done after the whole sum has been computed. + */ + double delta = gradient->stops[i+1].offset - gradient->stops[i-1].offset; + r += delta * gradient->stops[i].color.red; + g += delta * gradient->stops[i].color.green; + b += delta * gradient->stops[i].color.blue; + a += delta * gradient->stops[i].color.alpha; + } + + r += delta1 * gradient->stops[end].color.red; + g += delta1 * gradient->stops[end].color.green; + b += delta1 * gradient->stops[end].color.blue; + a += delta1 * gradient->stops[end].color.alpha; + + _cairo_color_init_rgba (color, r * .5, g * .5, b * .5, a * .5); +} + /** * _cairo_gradient_pattern_is_solid * @@ -1838,10 +1948,15 @@ _cairo_gradient_pattern_is_solid (const cairo_gradient_pattern_t *gradient, assert (gradient->base.type == CAIRO_PATTERN_TYPE_LINEAR || gradient->base.type == CAIRO_PATTERN_TYPE_RADIAL); - /* TODO: radial, degenerate linear */ + /* TODO: radial */ if (gradient->base.type == CAIRO_PATTERN_TYPE_LINEAR) { + cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) gradient; + if (_linear_pattern_is_degenerate (linear)) { + _gradient_color_average (gradient, color); + return TRUE; + } + if (gradient->base.extend == CAIRO_EXTEND_NONE) { - cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) gradient; double t[2]; /* We already know that the pattern is not clear, thus if some diff --git a/test/degenerate-linear-gradient.ref.png b/test/degenerate-linear-gradient.ref.png index e44cc335e..1de7ca67b 100644 Binary files a/test/degenerate-linear-gradient.ref.png and b/test/degenerate-linear-gradient.ref.png differ