GUACAMOLE-188: Use Porter-Duff "over" operator when drawing with ARGB surfaces.


Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/commit/12d29569
Tree: 
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/tree/12d29569
Diff: 
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/diff/12d29569

Branch: refs/heads/master
Commit: 12d29569a6a52227704149b62935dfc2032100ca
Parents: 4da4ce7
Author: Michael Jumper <[email protected]>
Authored: Sat Feb 25 16:13:56 2017 -0800
Committer: Michael Jumper <[email protected]>
Committed: Sat Feb 25 19:57:10 2017 -0800

----------------------------------------------------------------------
 src/common/common/surface.h |  23 ++++++---
 src/common/surface.c        | 101 +++++++++++++++++++++++++++++++++++----
 2 files changed, 108 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/12d29569/src/common/common/surface.h
----------------------------------------------------------------------
diff --git a/src/common/common/surface.h b/src/common/common/surface.h
index 964f8d3..c778883 100644
--- a/src/common/common/surface.h
+++ b/src/common/common/surface.h
@@ -279,14 +279,25 @@ void guac_common_surface_free(guac_common_surface* 
surface);
 void guac_common_surface_resize(guac_common_surface* surface, int w, int h);
 
 /**
- * Draws the given data to the given guac_common_surface.
+ * Draws the given data to the given guac_common_surface. If the source surface
+ * is ARGB, the draw operation will be performed using the Porter-Duff "over"
+ * composite operator. If the source surface is RGB (no alpha channel), no
+ * compositing is performed and destination pixels are ignored.
  *
- * @param surface The surface to draw to.
- * @param x The X coordinate of the draw location.
- * @param y The Y coordinate of the draw location.
- * @param src The Cairo surface to retrieve data from.
+ * @param surface
+ *     The surface to draw to.
+ *
+ * @param x
+ *     The X coordinate of the draw location.
+ *
+ * @param y
+ *     The Y coordinate of the draw location.
+ *
+ * @param src
+ *     The Cairo surface to retrieve data from.
  */
-void guac_common_surface_draw(guac_common_surface* surface, int x, int y, 
cairo_surface_t* src);
+void guac_common_surface_draw(guac_common_surface* surface, int x, int y,
+        cairo_surface_t* src);
 
 /**
  * Paints to the given guac_common_surface using the given data as a stencil,

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/12d29569/src/common/surface.c
----------------------------------------------------------------------
diff --git a/src/common/surface.c b/src/common/surface.c
index ac66a81..bc0c693 100644
--- a/src/common/surface.c
+++ b/src/common/surface.c
@@ -863,6 +863,75 @@ static void __guac_common_surface_set(guac_common_surface* 
dst,
 }
 
 /**
+ * Applies the Porter-Duff "over" composite operator, blending the two given
+ * color components using the given alpha value.
+ *
+ * @param dst
+ *     The destination color component.
+ *
+ * @param src
+ *     The source color component.
+ *
+ * @param alpha
+ *     The alpha value which applies to the blending operation.
+ *
+ * @return
+ *     The result of applying the Porter-Duff "over" composite operator to the
+ *     given source and destination components.
+ */
+static int guac_common_surface_blend_component(int dst, int src, int alpha) {
+    return src + dst * (0xFF - alpha);
+}
+
+/**
+ * Applies the Porter-Duff "over" composite operator, blending each component
+ * of the two given ARGB colors.
+ *
+ * @param dst
+ *     The destination ARGB color.
+ *
+ * @param src
+ *     The source ARGB color.
+ *
+ * @return
+ *     The result of applying the Porter-Duff "over" composite operator to the
+ *     given source and destination colors.
+ */
+static uint32_t guac_common_surface_argb_blend(uint32_t dst, uint32_t src) {
+
+    /* Separate destination ARGB color into its components */
+    int dst_a = (dst >> 24) & 0xFF;
+    int dst_r = (dst >> 16) & 0xFF;
+    int dst_g = (dst >>  8) & 0xFF;
+    int dst_b =  dst        & 0xFF;
+
+    /* Separate source ARGB color into its components */
+    int src_a = (src >> 24) & 0xFF;
+    int src_r = (src >> 16) & 0xFF;
+    int src_g = (src >>  8) & 0xFF;
+    int src_b =  src        & 0xFF;
+
+    /* If source is fully opaque (or destination is fully transparent), the
+     * blended result is the source */
+    if (src_a == 0xFF || dst_a == 0x00)
+        return src;
+
+    /* If source is fully transparent, the blended result is the destination */
+    if (src_a == 0x00)
+        return dst;
+
+    /* Otherwise, blend each ARGB component, assuming pre-multiplied alpha */
+    int r = guac_common_surface_blend_component(dst_r, src_r, src_a);
+    int g = guac_common_surface_blend_component(dst_g, src_g, src_a);
+    int b = guac_common_surface_blend_component(dst_b, src_b, src_a);
+    int a = guac_common_surface_blend_component(dst_a, src_a, src_a);
+
+    /* Recombine blended components */
+    return (a << 24) | (r << 16) | (g << 8) | b;
+
+}
+
+/**
  * Copies data from the given buffer to the surface at the given coordinates.
  * The dimensions and location of the destination rectangle will be altered
  * to remove as many unchanged pixels as possible.
@@ -906,22 +975,34 @@ static void __guac_common_surface_put(unsigned char* 
src_buffer, int src_stride,
         /* Copy row */
         for (x=0; x < rect->width; x++) {
 
-            if (opaque || (*src_current & 0xFF000000)) {
+            uint32_t color;
 
-                uint32_t new_color = *src_current | 0xFF000000;
-                uint32_t old_color = *dst_current;
+            /* Get source and destination color values */
+            uint32_t src_color = *src_current;
+            uint32_t dst_color = *dst_current;
 
-                if (old_color != new_color) {
-                    if (x < min_x) min_x = x;
-                    if (y < min_y) min_y = y;
-                    if (x > max_x) max_x = x;
-                    if (y > max_y) max_y = y;
-                    *dst_current = new_color;
-                }
+            /* Ignore alpha channel if opaque */
+            if (opaque)
+                color = src_color | 0xFF000000;
+
+            /* Otherwise, perform alpha blending operation */
+            else
+                color = guac_common_surface_argb_blend(dst_color, src_color);
+
+            /* If the destination color is changing, update rectangle bounds
+             * and store the new color */
+            if (dst_color != color) {
+                if (x < min_x) min_x = x;
+                if (y < min_y) min_y = y;
+                if (x > max_x) max_x = x;
+                if (y > max_y) max_y = y;
+                *dst_current = color;
             }
 
+            /* Advance to next pixel */
             src_current++;
             dst_current++;
+
         }
 
         /* Next row */

Reply via email to