From 70cb8e180b8f3fe021cb85eaaa43f126e1e725bb Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Sun, 20 Sep 2009 18:03:18 -0400 Subject: [PATCH] Match CSS for background extents The CSS specification says that the background extends to the edge of the border (settable in CSS3 with border-clip), make BigRectangle match this by computing an "effective border color" as 'border OVER background'. (If we don't want this behavior - e.g., to be able to use the transparent borders as margins, then alternatively transparent border handling would have to be fixed in nbtk-widget.c, since prior to this transparent and translucent borders were handled differently.) --- src/big/rectangle.c | 123 ++++++++++++++++++++++++++++------- tests/interactive/borders.js | 12 +++- 2 files changed, 109 insertions(+), 26 deletions(-) diff --git a/src/big/rectangle.c b/src/big/rectangle.c index 8ca104d50..fa594e6be 100644 --- a/src/big/rectangle.c +++ b/src/big/rectangle.c @@ -268,6 +268,52 @@ corner_get(guint radius, return corner; } +/* To match the CSS specification, we want the border to look like it was + * drawn over the background. But actually drawing the border over the + * background will produce slightly bad antialiasing at the edges, so + * compute the effective border color instead. + */ +#define NORM(x) (t = (x) + 127, (t + (t >> 8)) >> 8) +#define MULT(c,a) NORM(c*a) + +static void +premultiply (ClutterColor *color) +{ + guint t; + color->red = MULT (color->red, color->alpha); + color->green = MULT (color->green, color->alpha); + color->blue = MULT (color->blue, color->alpha); +} + +static void +unpremultiply (ClutterColor *color) +{ + if (color->alpha != 0) { + color->red = (color->red * 255 + 127) / color->alpha; + color->green = (color->green * 255 + 127) / color->alpha; + color->blue = (color->blue * 255 + 127) / color->alpha; + } +} + +static void +over (const ClutterColor *source, + const ClutterColor *destination, + ClutterColor *result) +{ + guint t; + ClutterColor src = *source; + ClutterColor dst = *destination; + premultiply (&src); + premultiply (&dst); + + result->alpha = src.alpha + NORM ((255 - src.alpha) * dst.alpha); + result->red = src.red + NORM ((255 - src.alpha) * dst.red); + result->green = src.green + NORM ((255 - src.alpha) * dst.green); + result->blue = src.blue + NORM ((255 - src.alpha) * dst.blue); + + unpremultiply (result); +} + static void big_rectangle_update_corners(BigRectangle *rectangle) { @@ -278,6 +324,7 @@ big_rectangle_update_corners(BigRectangle *rectangle) if (rectangle->radius != 0) { ClutterColor *color; ClutterColor *border_color; + ClutterColor effective_border; guint border_width; g_object_get(rectangle, @@ -286,10 +333,12 @@ big_rectangle_update_corners(BigRectangle *rectangle) "color", &color, NULL); + over (border_color, color, &effective_border); + corner = corner_get(rectangle->radius, color, border_width, - border_color); + &effective_border); clutter_color_free(border_color); clutter_color_free(color); @@ -329,12 +378,10 @@ big_rectangle_paint(ClutterActor *actor) rectangle = BIG_RECTANGLE(actor); - if (rectangle->radius == 0) { - /* In that case we are no different than our parent class, - * so don't bother */ - CLUTTER_ACTOR_CLASS(big_rectangle_parent_class)->paint(actor); - return; - } + /* We can't chain up, even when we the radius is 0, because of the different + * interpretation of the border/background relationship here than for + * ClutterRectangle. + */ if (rectangle->corners_dirty) big_rectangle_update_corners(rectangle); @@ -345,6 +392,9 @@ big_rectangle_paint(ClutterActor *actor) "color", &color, NULL); + if (border_color->alpha == 0 && color->alpha == 0) + goto out; + actor_opacity = clutter_actor_get_paint_opacity (actor); clutter_actor_get_allocation_box(actor, &box); @@ -358,6 +408,11 @@ big_rectangle_paint(ClutterActor *actor) radius = rectangle->radius; + /* Optimization; if the border is transparent, it just looks like part of + * the background */ + if (radius == 0 && border_color->alpha == 0) + border_width = 0; + max = MAX(border_width, radius); if (radius != 0) { @@ -393,33 +448,54 @@ big_rectangle_paint(ClutterActor *actor) } if (border_width != 0) { + ClutterColor effective_border; + over (border_color, color, &effective_border); + if (!rectangle->border_material) rectangle->border_material = cogl_material_new (); cogl_color_set_from_4ub(&tmp_color, - border_color->red, - border_color->green, - border_color->blue, - actor_opacity * border_color->alpha / 255); + effective_border.red, + effective_border.green, + effective_border.blue, + actor_opacity * effective_border.alpha / 255); cogl_color_premultiply (&tmp_color); cogl_material_set_color(rectangle->border_material, &tmp_color); cogl_set_source(rectangle->border_material); - /* NORTH */ - cogl_rectangle(max, 0, - width - max, border_width); + if (radius > 0) { /* skip corners */ + /* NORTH */ + cogl_rectangle(max, 0, + width - max, border_width); - /* EAST */ - cogl_rectangle(width - border_width, max, - width, height - max); + /* EAST */ + cogl_rectangle(width - border_width, max, + width, height - max); - /* SOUTH */ - cogl_rectangle(max, height - border_width, - width - max, height); + /* SOUTH */ + cogl_rectangle(max, height - border_width, + width - max, height); - /* WEST */ - cogl_rectangle(0, max, - border_width, height - max); + /* WEST */ + cogl_rectangle(0, max, + border_width, height - max); + } else { /* include corners */ + /* NORTH */ + cogl_rectangle(0, 0, + width, border_width); + + /* EAST */ + cogl_rectangle(width - border_width, border_width, + width, height - border_width); + + /* SOUTH */ + cogl_rectangle(0, height - border_width, + width, height); + + /* WEST */ + cogl_rectangle(0, border_width, + border_width, height - border_width); + } } if (!rectangle->background_material) @@ -455,6 +531,7 @@ big_rectangle_paint(ClutterActor *actor) cogl_rectangle(border_width, max, width - border_width, height - max); +out: clutter_color_free(border_color); clutter_color_free(color); } diff --git a/tests/interactive/borders.js b/tests/interactive/borders.js index e1bcaf7bf..d4744856b 100644 --- a/tests/interactive/borders.js +++ b/tests/interactive/borders.js @@ -8,13 +8,13 @@ const UI = imports.testcommon.ui; UI.init(); let stage = Clutter.Stage.get_default(); stage.width = 600; -stage.height = 600; +stage.height = 700; let vbox = new Nbtk.BoxLayout({ vertical: true, width: stage.width, height: stage.height, spacing: 20, - style: 'padding: 10px; background: #ffee88' }); + style: 'padding: 10px; background: #ffee88;' }); stage.add_actor(vbox); vbox.add(new Nbtk.Label({ text: "Hello World", @@ -43,8 +43,14 @@ vbox.add(b1); b1.add(new Nbtk.BoxLayout({ width: 20, height: 20, style: 'background: black' })); -vbox.add(new Nbtk.Label({ text: "Translucent blue border", +vbox.add(new Nbtk.Label({ text: "Translucent blue border, with rounding", style: 'border: 20px solid rgba(0, 0, 255, 0.2); ' + + 'border-radius: 10px; ' + + 'background: white; ' + + 'padding: 10px;' })); + +vbox.add(new Nbtk.Label({ text: "Transparent border", + style: 'border: 20px solid transparent; ' + 'background: white; ' + 'padding: 10px;' }));