From 74b2eed15898b22af4514a406600cdb05e437585 Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Fri, 3 Apr 2015 16:22:11 +0200 Subject: [PATCH] ector: first implementation of an Cairo shape renderer. 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 | 455 +++++++++++++++++++++- src/lib/ector/cairo/ector_renderer_cairo_shape.eo | 4 +- 2 files changed, 456 insertions(+), 3 deletions(-) diff --git a/src/lib/ector/cairo/ector_renderer_cairo_shape.c b/src/lib/ector/cairo/ector_renderer_cairo_shape.c index 070ba16..499a01b 100644 --- a/src/lib/ector/cairo/ector_renderer_cairo_shape.c +++ b/src/lib/ector/cairo/ector_renderer_cairo_shape.c @@ -2,6 +2,9 @@ # include "config.h" #endif +#include +#include + #include #include #include @@ -9,29 +12,477 @@ #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, + ¤t_x, ¤t_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" diff --git a/src/lib/ector/cairo/ector_renderer_cairo_shape.eo b/src/lib/ector/cairo/ector_renderer_cairo_shape.eo index b923f6f..ea3e1b1 100644 --- a/src/lib/ector/cairo/ector_renderer_cairo_shape.eo +++ b/src/lib/ector/cairo/ector_renderer_cairo_shape.eo @@ -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; } } -- 2.7.4