ector: first implementation of an Cairo shape renderer.
authorCedric BAIL <cedric@osg.samsung.com>
Fri, 3 Apr 2015 14:22:11 +0000 (16:22 +0200)
committerCedric BAIL <cedric@osg.samsung.com>
Fri, 3 Apr 2015 14:22:11 +0000 (16:22 +0200)
Note: still a lot on the todo, but it should display something that follow
the correct path at least. Thanks librsvg for helping a lot.

src/lib/ector/cairo/ector_renderer_cairo_shape.c
src/lib/ector/cairo/ector_renderer_cairo_shape.eo

index 070ba16..499a01b 100644 (file)
@@ -2,6 +2,9 @@
 # include "config.h"
 #endif
 
+#include <math.h>
+#include <float.h>
+
 #include <Eina.h>
 #include <Ector.h>
 #include <cairo/Ector_Cairo.h>
 #include "ector_private.h"
 #include "ector_cairo_private.h"
 
+typedef struct _cairo_path_t cairo_path_t;
+
+static void (*cairo_move_to)(cairo_t *cr, double x, double y) = NULL;
+static void (*cairo_line_to)(cairo_t *cr, double x, double y) = NULL;
+static void (*cairo_curve_to)(cairo_t *cr,
+                              double x1, double y1,
+                              double x2, double y2,
+                              double x3, double y3) = NULL;
+static void (*cairo_close_path)(cairo_t *cr) = NULL;
+
+static void (*cairo_fill)(cairo_t *cr) = NULL;
+static void (*cairo_fill_preserve)(cairo_t *cr) = NULL;
+static void (*cairo_stroke)(cairo_t *cr) = NULL;
+
+static void (*cairo_set_source_rgba)(cairo_t *cr,
+                                     double red, double green,
+                                     double blue, double alpha) = NULL;
+
+
+static cairo_path_t *(*cairo_copy_path)(cairo_t *cr) = NULL;
+static void (*cairo_path_destroy)(cairo_path_t *path) = NULL;
+static void (*cairo_new_path)(cairo_t *cr) = NULL;
+static void (*cairo_append_path)(cairo_t *cr, const cairo_path_t *path) = NULL;
+
 typedef struct _Ector_Renderer_Cairo_Shape_Data Ector_Renderer_Cairo_Shape_Data;
 struct _Ector_Renderer_Cairo_Shape_Data
 {
+   Ector_Cairo_Surface_Data *parent;
+   Ector_Renderer_Generic_Shape_Data *shape;
+   Ector_Renderer_Generic_Base_Data *base;
+   cairo_path_t *path;
 };
 
+// This function come from librsvg rsvg-path.c
+static void
+_ector_arc_segment(Eo *obj, cairo_t* ctx,
+                   double xc, double yc,
+                   double th0, double th1, double rx, double ry,
+                   double x_axis_rotation)
+{
+   double x1, y1, x2, y2, x3, y3;
+   double t;
+   double th_half;
+   double f, sinf, cosf;
+
+   f = x_axis_rotation * M_PI / 180.0;
+   sinf = sin(f);
+   cosf = cos(f);
+
+   th_half = 0.5 * (th1 - th0);
+   t = (8.0 / 3.0) * sin(th_half * 0.5) * sin(th_half * 0.5) / sin(th_half);
+   x1 = rx * (cos(th0) - t * sin(th0));
+   y1 = ry * (sin(th0) + t * cos(th0));
+   x3 = rx* cos(th1);
+   y3 = ry* sin(th1);
+   x2 = x3 + rx * (t * sin(th1));
+   y2 = y3 + ry * (-t * cos(th1));
+
+   USE(obj, cairo_curve_to, );
+
+   cairo_curve_to(ctx,
+                  xc + cosf * x1 - sinf * y1,
+                  yc + sinf * x1 + cosf * y1,
+                  xc + cosf * x2 - sinf * y2,
+                  yc + sinf * x2 + cosf * y2,
+                  xc + cosf * x3 - sinf * y3,
+                  yc + sinf * x3 + cosf * y3);
+}
+
+// This function come from librsvg rsvg-path.c
+static void
+_ector_arc_to(Eo *obj, cairo_t* ctx,
+              double *current_x, double *current_y,
+              double rx, double ry, double x_axis_rotation,
+              Eina_Bool large_arc_flag, Eina_Bool sweep_flag,
+              double x, double y)
+{
+   /* See Appendix F.6 Elliptical arc implementation notes
+      http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes */
+
+   double f, sinf, cosf;
+   double x1, y1, x2, y2;
+   double x1_, y1_;
+   double cx_, cy_, cx, cy;
+   double gamma;
+   double theta1, delta_theta;
+   double k1, k2, k3, k4, k5;
+
+   int i, n_segs;
+
+   /* Start and end of path segment */
+   x1 = *current_x;
+   y1 = *current_y;
+
+   x2 = x;
+   y2 = y;
+
+   if (x1 == x2 && y1 == y2)
+     return;
+
+   /* X-axis */
+   f = x_axis_rotation * M_PI / 180.0;
+   sinf = sin(f);
+   cosf = cos(f);
+
+   /* Check the radius against floading point underflow.
+      See http://bugs.debian.org/508443 */
+   if ((fabs(rx) < DBL_EPSILON) || (fabs(ry) < DBL_EPSILON)) {
+      USE(obj, cairo_line_to, );
+
+      cairo_line_to(ctx, x, y);
+
+      *current_x = x;
+      *current_y = y;
+      return;
+   }
+
+   if (rx < 0) rx = -rx;
+   if (ry < 0) ry = -ry;
+
+   k1 = (x1 - x2) / 2;
+   k2 = (y1 - y2) / 2;
+
+   x1_ = cosf * k1 + sinf * k2;
+   y1_ = -sinf * k1 + cosf * k2;
+
+   gamma = (x1_ * x1_) / (rx * rx) + (y1_ * y1_) / (ry * ry);
+   if (gamma > 1) {
+      rx *= sqrt(gamma);
+      ry *= sqrt(gamma);
+   }
+
+   /* Compute the center */
+   k1 = rx * rx * y1_ * y1_ + ry * ry * x1_ * x1_;
+   if (k1 == 0)
+     return;
+
+   k1 = sqrt(fabs((rx * rx * ry * ry) / k1 - 1));
+   if (sweep_flag == large_arc_flag)
+     k1 = -k1;
+
+   cx_ = k1 * rx * y1_ / ry;
+   cy_ = -k1 * ry * x1_ / rx;
+
+   cx = cosf * cx_ - sinf * cy_ + (x1 + x2) / 2;
+   cy = sinf * cx_ + cosf * cy_ + (y1 + y2) / 2;
+
+   /* Compute start angle */
+   k1 = (x1_ - cx_) / rx;
+   k2 = (y1_ - cy_) / ry;
+   k3 = (-x1_ - cx_) / rx;
+   k4 = (-y1_ - cy_) / ry;
+
+   k5 = sqrt(fabs(k1 * k1 + k2 * k2));
+   if (k5 == 0) return;
+
+   k5 = k1 / k5;
+   if (k5 < -1) k5 = -1;
+   else if(k5 > 1) k5 = 1;
+
+   theta1 = acos(k5);
+   if(k2 < 0) theta1 = -theta1;
+
+   /* Compute delta_theta */
+   k5 = sqrt(fabs((k1 * k1 + k2 * k2) * (k3 * k3 + k4 * k4)));
+   if (k5 == 0) return;
+
+   k5 = (k1 * k3 + k2 * k4) / k5;
+   if (k5 < -1) k5 = -1;
+   else if (k5 > 1) k5 = 1;
+   delta_theta = acos(k5);
+   if(k1 * k4 - k3 * k2 < 0) delta_theta = -delta_theta;
+
+   if (sweep_flag && delta_theta < 0)
+     delta_theta += M_PI*2;
+   else if (!sweep_flag && delta_theta > 0)
+     delta_theta -= M_PI*2;
+
+   /* Now draw the arc */
+   n_segs = ceil (fabs (delta_theta / (M_PI * 0.5 + 0.001)));
+
+   for (i = 0; i < n_segs; i++)
+     _ector_arc_segment(obj, ctx,
+                        cx, cy,
+                        theta1 + i * delta_theta / n_segs,
+                        theta1 + (i + 1) * delta_theta / n_segs,
+                        rx, ry, x_axis_rotation);
+
+   *current_x = x;
+   *current_y = y;
+}
+
 static Eina_Bool
 _ector_renderer_cairo_shape_ector_renderer_generic_base_prepare(Eo *obj, Ector_Renderer_Cairo_Shape_Data *pd, Ector_Surface *s)
 {
+   // FIXME: shouldn't that be part of the shape generic implementation ?
+   if (pd->shape->fill)
+     eo_do(pd->shape->fill, ector_renderer_prepare(s));
+   if (pd->shape->stroke.fill)
+     eo_do(pd->shape->stroke.fill, ector_renderer_prepare(s));
+   if (pd->shape->stroke.marker)
+     eo_do(pd->shape->stroke.marker, ector_renderer_prepare(s));
+   eo_do_super(obj, ECTOR_RENDERER_CAIRO_SHAPE_CLASS, ector_renderer_prepare(s));
+
+   // shouldn't that be moved to the cairo base object
+   if (!pd->parent)
+     {
+        Eo *parent;
+
+        eo_do(obj, parent = eo_parent_get());
+        if (!parent) return EINA_FALSE;
+        pd->parent = eo_data_xref(parent, ECTOR_CAIRO_SURFACE_CLASS, obj);
+        if (!pd->parent) return EINA_FALSE;
+     }
+
+   if (!pd->path && pd->shape->path.cmd)
+     {
+        double *pts;
+        double current_x = 0, current_y = 0;
+        double current_ctrl_x = 0, current_ctrl_y = 0;
+        unsigned int i;
+
+        USE(obj, cairo_new_path, EINA_FALSE);
+
+        cairo_new_path(pd->parent->cairo);
+
+        pts = pd->shape->path.pts;
+        for (i = 0; pd->shape->path.cmd[i] != EFL_GRAPHICS_PATH_COMMAND_TYPE_END; i++)
+          {
+             switch (pd->shape->path.cmd[i])
+               {
+                case EFL_GRAPHICS_PATH_COMMAND_TYPE_MOVE_TO:
+                   USE(obj, cairo_move_to, EINA_FALSE);
+
+                   cairo_move_to(pd->parent->cairo, pts[0], pts[1]);
+
+                   current_ctrl_x = current_x = pts[0];
+                   current_ctrl_y = current_y = pts[1];
+
+                   pts += 2;
+                   break;
+                case EFL_GRAPHICS_PATH_COMMAND_TYPE_LINE_TO:
+                   USE(obj, cairo_line_to, EINA_FALSE);
+
+                   cairo_line_to(pd->parent->cairo, pts[0], pts[1]);
+
+                   current_ctrl_x = current_x = pts[0];
+                   current_ctrl_y = current_y = pts[1];
+
+                   pts += 2;
+                   break;
+                case EFL_GRAPHICS_PATH_COMMAND_TYPE_CUBIC_TO:
+                   USE(obj, cairo_curve_to, EINA_FALSE);
+
+                   // Be careful, we do have a different order than
+                   // cairo, first is destination point, followed by
+                   // the control point. The opposite of cairo.
+                   cairo_curve_to(pd->parent->cairo,
+                                  pts[2], pts[3], pts[4], pts[5], // control points
+                                  pts[0], pts[1]); // destination point
+
+                   current_ctrl_x = pts[4];
+                   current_ctrl_y = pts[5];
+                   current_x = pts[0];
+                   current_y = pts[1];
+
+                   pts += 6;
+                   break;
+                case EFL_GRAPHICS_PATH_COMMAND_TYPE_ARC_TO:
+                   _ector_arc_to(obj, pd->parent->cairo,
+                                 &current_x, &current_y,
+                                 pts[2], pts[3], pts[4],
+                                 0, 0, // FIXME: need to get the large arc and sweep flag
+                                 pts[0], pts[1]);
+
+                   pts += 5;
+                   break;
+                case EFL_GRAPHICS_PATH_COMMAND_TYPE_CLOSE:
+                   USE(obj, cairo_close_path, EINA_FALSE);
+
+                   cairo_close_path(pd->parent->cairo);
+                   break;
+                case EFL_GRAPHICS_PATH_COMMAND_TYPE_QUADRATIC_TO:
+                  {
+                     double x1, y1, x2, y2, x3, y3;
+                     // This code come from librsvg rsvg-path.c
+                     // Be careful, we do have a different order than
+                     // cairo, first is destination point, followed by
+                     // the control point. The opposite of cairo.
+                     /* raise quadratic bezier to cubic */
+                     x1 = (current_x + 2 * pts[2]) * (1.0 / 3.0);
+                     y1 = (current_y + 2 * pts[3]) * (1.0 / 3.0);
+                     x3 = pts[0];
+                     y3 = pts[1];
+                     x2 = (x3 + 2 * pts[2]) * (1.0 / 3.0);
+                     y2 = (y3 + 2 * pts[3]) * (1.0 / 3.0);
+
+                     cairo_curve_to(pd->parent->cairo,
+                                    x1, y1, x2, y2, // control points
+                                    x3, y3); // destination point
+
+                     current_ctrl_x = pts[2];
+                     current_ctrl_y = pts[3];
+
+                     current_x = x3;
+                     current_y = y3;
+                     break;
+                  }
+                case EFL_GRAPHICS_PATH_COMMAND_TYPE_SQUADRATIC_TO:
+                  {
+                     // This code come from librsvg rsvg-path.c
+                     // Smooth quadratic basically reusing the last control
+                     // point in a meaningful way.
+                     double xc, yc; /* quadratic control point */
+                     double x1, y1, x2, y2, x3, y3;
+
+                     xc = 2 * current_x - current_ctrl_x;
+                     yc = 2 * current_y - current_ctrl_y;
+                     /* generate a quadratic bezier with control point = xc, yc */
+                     x1 = (current_x + 2 * xc) * (1.0 / 3.0);
+                     y1 = (current_y + 2 * yc) * (1.0 / 3.0);
+                     x3 = pts[0];
+                     y3 = pts[1];
+                     x2 = (x3 + 2 * xc) * (1.0 / 3.0);
+                     y2 = (y3 + 2 * yc) * (1.0 / 3.0);
+
+                     USE(obj, cairo_curve_to, EINA_FALSE);
+
+                     cairo_curve_to(pd->parent->cairo,
+                                    x1, y1, x2, y2, x3, y3);
+
+                     current_ctrl_x = xc;
+                     current_ctrl_y = yc;
+
+                     current_x = x3;
+                     current_y = y3;
+
+                     break;
+                  }
+                case EFL_GRAPHICS_PATH_COMMAND_TYPE_SCUBIC_TO:
+                  {
+                     // This code come from librsvg rsvg-path.c
+                     // Smooth cubic basically reusing the last control point
+                     // in a meaningful way.
+                     double x1, y1, x2, y2, x3, y3;
+
+                     x1 = 2 * current_x - current_ctrl_x;
+                     y1 = 2 * current_y - current_ctrl_y;
+                     x2 = pts[2];
+                     y2 = pts[3];
+                     x3 = pts[0];
+                     y3 = pts[1];
+
+                     USE(obj, cairo_curve_to, EINA_FALSE);
+
+                     cairo_curve_to(pd->parent->cairo,
+                                    x1, y1, x2, y2, x3, y3);
+
+                     current_ctrl_x = x2;
+                     current_ctrl_y = y2;
+                     current_x = x3;
+                     current_y = y3;
+                     break;
+                  }
+                case EFL_GRAPHICS_PATH_COMMAND_TYPE_LAST:
+                case EFL_GRAPHICS_PATH_COMMAND_TYPE_END:
+                   break;
+               }
+          }
+
+        USE(obj, cairo_copy_path, EINA_FALSE);
+
+        pd->path = cairo_copy_path(pd->parent->cairo);
+     }
+
+   return EINA_TRUE;
 }
 
 static Eina_Bool
 _ector_renderer_cairo_shape_ector_renderer_generic_base_draw(Eo *obj, Ector_Renderer_Cairo_Shape_Data *pd, Ector_Rop op, Eina_Array *clips, int x, int y)
 {
+   if (pd->path == NULL) return EINA_FALSE;
+
+   // FIXME: find a way to offset the drawing and setting multiple clips
+
+   eo_do_super(obj, ECTOR_RENDERER_CAIRO_SHAPE_CLASS, ector_renderer_draw(op, clips, x, y));
+
+   USE(obj, cairo_new_path, EINA_FALSE);
+   USE(obj, cairo_append_path, EINA_FALSE);
+
+   cairo_new_path(pd->parent->cairo);
+   cairo_append_path(pd->parent->cairo, pd->path);
+
+   if (pd->shape->fill)
+     eo_do(pd->shape->fill, ector_renderer_cairo_base_fill());
+
+   if (pd->shape->stroke.color.a > 0)
+     {
+        USE(obj, cairo_fill_preserve, EINA_FALSE);
+        USE(obj, cairo_set_source_rgba, EINA_FALSE);
+        USE(obj, cairo_stroke, EINA_FALSE);
+
+        cairo_fill_preserve(pd->parent->cairo);
+
+        cairo_set_source_rgba(pd->parent->cairo,
+                              pd->shape->stroke.color.r / 255.0,
+                              pd->shape->stroke.color.g / 255.0,
+                              pd->shape->stroke.color.b / 255.0,
+                              pd->shape->stroke.color.a / 255.0);
+
+        if (pd->shape->stroke.fill)
+          eo_do(pd->shape->stroke.fill, ector_renderer_cairo_base_fill());
+        // Set dash, cap and join
+        cairo_stroke(pd->parent->cairo);
+     }
+   else
+     {
+        USE(obj, cairo_fill, EINA_FALSE);
+        cairo_fill(pd->parent->cairo);
+     }
+
+   return EINA_TRUE;
 }
 
 static Eina_Bool
-_ector_renderer_cairo_shape_ector_renderer_generic_base_done(Eo *obj, Ector_Renderer_Cairo_Shape_Data *pd)
+_ector_renderer_cairo_shape_ector_renderer_cairo_base_fill(Eo *obj, Ector_Renderer_Cairo_Shape_Data *pd)
 {
+   // FIXME: let's find out how to fill a shape with a shape later.
+   // I need to read SVG specification and see how to map that with cairo.
 }
 
 static Eina_Bool
-_ector_renderer_cairo_shape_ector_renderer_cairo_base_fill(Eo *obj, Ector_Renderer_Cairo_Shape_Data *pd)
+_ector_renderer_cairo_shape_efl_graphics_shape_path_set(Eo *obj, Ector_Renderer_Cairo_Shape_Data *pd,
+                                                        const Efl_Graphics_Path_Command *op, const double *points)
+{
+   Eina_Bool r;
+
+   USE(obj, cairo_path_destroy, EINA_FALSE);
+
+   if (pd->path) cairo_path_destroy(pd->path);
+   pd->path = NULL;
+
+   eo_do_super(obj, ECTOR_RENDERER_CAIRO_SHAPE_CLASS, r = efl_graphics_shape_path_set(op, points));
+
+   return r;
+}
+
+
+void
+_ector_renderer_cairo_shape_eo_base_constructor(Eo *obj, Ector_Renderer_Cairo_Shape_Data *pd)
 {
+   eo_do_super(obj, ECTOR_RENDERER_CAIRO_SHAPE_CLASS, eo_constructor());
+   pd->shape = eo_data_xref(obj, ECTOR_RENDERER_GENERIC_SHAPE_CLASS, obj);
+   pd->base = eo_data_xref(obj, ECTOR_RENDERER_GENERIC_BASE_CLASS, obj);
 }
 
+void
+_ector_renderer_cairo_shape_eo_base_destructor(Eo *obj, Ector_Renderer_Cairo_Shape_Data *pd)
+{
+   Eo *parent;
+
+   USE(obj, cairo_path_destroy, );
+   if (pd->path) cairo_path_destroy(pd->path);
+
+   eo_do(obj, parent = eo_parent_get());
+   eo_data_xunref(parent, pd->parent, obj);
+
+   eo_data_xunref(obj, pd->shape, obj);
+   eo_data_xunref(obj, pd->base, obj);
+   eo_do_super(obj, ECTOR_RENDERER_CAIRO_SHAPE_CLASS, eo_destructor());
+}
+
+
 #include "ector_renderer_cairo_shape.eo.c"
index b923f6f..ea3e1b1 100644 (file)
@@ -5,7 +5,9 @@ class Ector.Renderer.Cairo.Shape (Ector.Renderer.Cairo.Base, Ector.Renderer.Gene
    implements {
       Ector.Renderer.Generic.Base.prepare;
       Ector.Renderer.Generic.Base.draw;
-      Ector.Renderer.Generic.Base.done;
       Ector.Renderer.Cairo.Base.fill;
+      Efl.Graphics.Shape.path_set;
+      Eo.Base.constructor;
+      Eo.Base.destructor;
    }
 }