From 04c55356311593341a00d73334a22b4c77de6367 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 1 Oct 2012 17:00:19 +0200 Subject: [PATCH] line: add bezier/bezier_precision properties --- examples/canvas/lines_extended.py | 18 +++++ kivy/graphics/vertex_instructions_line.pxi | 83 +++++++++++++++++++++- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/examples/canvas/lines_extended.py b/examples/canvas/lines_extended.py index ab4777ce7..05970079c 100644 --- a/examples/canvas/lines_extended.py +++ b/examples/canvas/lines_extended.py @@ -83,6 +83,20 @@ Builder.load_string(''' Label: center: root.center text: 'Rectangle' + + +: + canvas: + Color: + rgba: .1, .1, 1, .9 + Line: + width: 2. + bezier: (self.x, self.y, self.center_x - 40, self.y + 100, self.center_x + 40, self.y - 100, self.right, self.y) + Label: + center: root.center + text: 'Bezier' + + ''') @@ -107,6 +121,9 @@ class LineCircle3(Widget): class LineRectangle(Widget): pass +class LineBezier(Widget): + pass + class LineExtendedApp(App): def build(self): root = GridLayout(cols=2, padding=50, spacing=50) @@ -117,6 +134,7 @@ class LineExtendedApp(App): root.add_widget(LineCircle2()) root.add_widget(LineCircle3()) root.add_widget(LineRectangle()) + root.add_widget(LineBezier()) return root if __name__ == '__main__': diff --git a/kivy/graphics/vertex_instructions_line.pxi b/kivy/graphics/vertex_instructions_line.pxi index dd15e8c85..865924503 100644 --- a/kivy/graphics/vertex_instructions_line.pxi +++ b/kivy/graphics/vertex_instructions_line.pxi @@ -11,6 +11,7 @@ DEF LINE_MODE_POINTS = 0 DEF LINE_MODE_ELLIPSE = 1 DEF LINE_MODE_CIRCLE = 2 DEF LINE_MODE_RECTANGLE = 3 +DEF LINE_MODE_BEZIER = 4 from kivy.graphics.stencil_instructions cimport StencilUse, StencilUnUse, StencilPush, StencilPop @@ -77,6 +78,11 @@ cdef class Line(VertexInstruction): `rectangle`: list If set, the :data:`points` will be set to build a rectangle. Check :data:`rectangle` for more information. + `bezier`: list + If set, the :data:`points` will be set to build a bezier line. Check + :data:`bezier` for more information. + `bezier_precision`: int, default to 180 + Precision of the Bezier drawing. .. versionadded:: 1.0.8 `dash_offset` and `dash_length` have been added @@ -84,10 +90,14 @@ cdef class Line(VertexInstruction): .. versionadded:: 1.4.1 `width`, `cap`, `joint`, `cap_precision`, `joint_precision`, `close`, `ellipse`, `rectangle` have been added. + + .. versionadded:: 1.4.1 + `bezier`, `bezier_precision` have been added. ''' cdef int _cap cdef int _cap_precision cdef int _joint_precision + cdef int _bezier_precision cdef int _joint cdef list _points cdef float _width @@ -115,6 +125,7 @@ cdef class Line(VertexInstruction): self.cap = kwargs.get('cap') or 'round' self._cap_precision = kwargs.get('cap_precision') or 10 self._joint_precision = kwargs.get('joint_precision') or 10 + self._bezier_precision = kwargs.get('bezier_precision') or 180 self._close = int(bool(kwargs.get('close', 0))) self._stencil_rect = None self._stencil_push = None @@ -130,6 +141,8 @@ cdef class Line(VertexInstruction): self.prebuild_circle() elif self._mode == LINE_MODE_RECTANGLE: self.prebuild_rectangle() + elif self._mode == LINE_MODE_BEZIER: + self.prebuild_bezier() if self._width == 1.0: self.build_legacy() else: @@ -767,7 +780,7 @@ cdef class Line(VertexInstruction): def __set__(self, value): if value < 1: - raise GraphicException('Invalid cap_precision value, must be >= 1') + raise GraphicException('Invalid joint_precision value, must be >= 1') self._joint_precision = int(value) self.flag_update() @@ -987,3 +1000,71 @@ cdef class Line(VertexInstruction): self._points = [x, y, x + width, y, x + width, y + height, x, y + height] self._close = 1 + + property bezier: + '''Use this property to build a rectangle, without calculate the + :data:`points`. You can only set this property, not get it. + + The argument must be a tuple of (x, y, width, height) + angle_end, segments): + + * x and y represent the bottom-left position of the rectangle + * width and height represent the size + + The line is automatically closed. + + Usage:: + + Line(rectangle=(0, 0, 200, 200)) + + .. versionadded:: 1.4.2 + ''' + + def __set__(self, args): + if args == None: + raise GraphicException( + 'Invalid rectangle value: {0!r}'.format(args)) + self._mode_args = tuple(args) + self._mode = LINE_MODE_BEZIER + self.flag_update() + + cdef void prebuild_bezier(self): + cdef double x, y, l + cdef int segments = self._bezier_precision + cdef list T = list(self._mode_args)[:] + + self._points = [] + for x in xrange(segments): + l = x / (1.0 * segments) + # http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm + # as the list is in the form of (x1, y1, x2, y2...) iteration is + # done on each item and the current item (xn or yn) in the list is + # replaced with a calculation of "xn + x(n+1) - xn" x(n+1) is + # placed at n+2. each iteration makes the list one item shorter + for i in range(1, len(T)): + for j in xrange(len(T) - 2*i): + T[j] = T[j] + (T[j+2] - T[j]) * l + + # we got the coordinates of the point in T[0] and T[1] + self._points.append(T[0]) + self._points.append(T[1]) + + # add one last point to join the curve to the end + self._points.append(T[-2]) + self._points.append(T[-1]) + + property bezier_precision: + '''Number of iteration for drawing the bezier between 2 segments, + default to 180. The bezier_precision must be at least 1. + + .. versionadded:: 1.4.2 + ''' + + def __get__(self): + return self._bezier_precision + + def __set__(self, value): + if value < 1: + raise GraphicException('Invalid bezier_precision value, must be >= 1') + self._bezier_precision = int(value) + self.flag_update()