diff --git a/doc/sources/images/line-instruction.png b/doc/sources/images/line-instruction.png new file mode 100644 index 000000000..d6973d0ee Binary files /dev/null and b/doc/sources/images/line-instruction.png differ diff --git a/examples/canvas/lines.py b/examples/canvas/lines.py new file mode 100644 index 000000000..09c8879e8 --- /dev/null +++ b/examples/canvas/lines.py @@ -0,0 +1,185 @@ +from kivy.app import App +from kivy.properties import OptionProperty, NumericProperty, ListProperty, \ + BooleanProperty +from kivy.uix.floatlayout import FloatLayout +from kivy.lang import Builder +from kivy.clock import Clock +from math import cos, sin + +Builder.load_string(''' +: + canvas: + Color: + rgba: .4, .4, 1, root.alpha + Line: + points: self.points + joint: self.joint + cap: self.cap + width: self.linewidth + close: self.close + Color: + rgba: .8, .8, .8, root.alpha_controlline + Line: + points: self.points + close: self.close + Color: + rgba: 1, .4, .4, root.alpha + Line: + points: self.points2 + joint: self.joint + cap: self.cap + width: self.linewidth + close: self.close + + GridLayout: + cols: 2 + size_hint: 1, None + height: 44 * 5 + + GridLayout: + cols: 2 + + Label: + text: 'Alpha' + Slider: + value: root.alpha + on_value: root.alpha = float(args[1]) + min: 0. + max: 1. + Label: + text: 'Alpha Control Line' + Slider: + value: root.alpha_controlline + on_value: root.alpha_controlline = float(args[1]) + min: 0. + max: 1. + Label: + text: 'Width' + Slider: + value: root.linewidth + on_value: root.linewidth = args[1] + min: 1 + max: 40 + Label: + text: 'Cap' + GridLayout: + rows: 1 + ToggleButton: + group: 'cap' + text: 'none' + on_press: root.cap = self.text + ToggleButton: + group: 'cap' + text: 'round' + on_press: root.cap = self.text + ToggleButton: + group: 'cap' + text: 'square' + on_press: root.cap = self.text + Label: + text: 'Joint' + GridLayout: + rows: 1 + ToggleButton: + group: 'joint' + text: 'none' + on_press: root.joint = self.text + ToggleButton: + group: 'joint' + text: 'round' + on_press: root.joint = self.text + ToggleButton: + group: 'joint' + text: 'miter' + on_press: root.joint = self.text + ToggleButton: + group: 'joint' + text: 'bevel' + on_press: root.joint = self.text + + Label: + text: 'Close' + ToggleButton: + text: 'Close line' + on_press: root.close = self.state == 'down' + + AnchorLayout: + GridLayout: + cols: 1 + size_hint: None, None + size: self.minimum_size + ToggleButton: + size_hint: None, None + size: 100, 44 + text: 'Animate' + on_state: root.animate(self.state == 'down') + Button: + size_hint: None, None + size: 100, 44 + text: 'Clear' + on_press: root.points = root.points2 = [] + +''') + + +class LinePlayground(FloatLayout): + alpha_controlline = NumericProperty(1.0) + alpha = NumericProperty(0.5) + close = BooleanProperty(False) + points = ListProperty([500, 500, 300, 300, 500, 300, 500, 400, 600, 400]) + points2 = ListProperty([]) + joint = OptionProperty('none', options=('round', 'miter', 'bevel', 'none')) + cap = OptionProperty('none', options=('round', 'square', 'none')) + linewidth = NumericProperty(10.0) + dt = NumericProperty(0) + + def on_touch_down(self, touch): + if super(LinePlayground, self).on_touch_down(touch): + return True + touch.grab(self) + self.points = self.points + list(touch.pos) + return True + + def on_touch_move(self, touch): + if touch.grab_current is self: + self.points[-2:] = list(touch.pos) + return True + return super(LinePlayground, self).on_touch_move(touch) + + def on_touch_up(self, touch): + if touch.grab_current is self: + touch.ungrab(self) + return True + return super(LinePlayground, self).on_touch_up(touch) + + def animate(self, do_animation): + if do_animation: + Clock.schedule_interval(self.update_points_animation, 0) + else: + Clock.unschedule(self.update_points_animation) + + def update_points_animation(self, dt): + cy = self.height * 0.6 + cx = self.width * 0.1 + w = self.width * 0.8 + step = 20 + points = [] + points2 = [] + self.dt += dt + for i in xrange(int(w / step)): + x = i * step + points.append(cx + x) + points.append(cy + cos(x / w * 8. + self.dt) * self.height * 0.2) + points2.append(cx + x) + points2.append(cy + sin(x / w * 8. + self.dt) * self.height * 0.2) + self.points = points + self.points2 = points2 + + +class TestLineApp(App): + def build(self): + return LinePlayground() + + +if __name__ == '__main__': + TestLineApp().run() diff --git a/kivy/graphics/common.pxi b/kivy/graphics/common.pxi index 8f45fb089..c644af435 100644 --- a/kivy/graphics/common.pxi +++ b/kivy/graphics/common.pxi @@ -2,15 +2,19 @@ # Common definition # +DEF PI2 = 1.5707963267948966 +DEF PI = 3.1415926535897931 + cdef extern from *: ctypedef char* const_char_ptr "const char*" -cdef double pi = 3.1415926535897931 +cdef double pi = PI cdef extern from "math.h": double cos(double) nogil double sin(double) nogil double sqrt(double) nogil double pow(double x, double y) nogil + double atan2(double y, double x) nogil cdef extern from "stdlib.h": ctypedef unsigned long size_t diff --git a/kivy/graphics/instructions.pxd b/kivy/graphics/instructions.pxd index fe90ae929..196d931ef 100644 --- a/kivy/graphics/instructions.pxd +++ b/kivy/graphics/instructions.pxd @@ -114,3 +114,4 @@ cdef class RenderContext(Canvas): cpdef draw(self) cdef void reload(self) +cdef RenderContext getActiveContext() diff --git a/kivy/graphics/stencil_instructions.pxd b/kivy/graphics/stencil_instructions.pxd new file mode 100644 index 000000000..1cf556e4c --- /dev/null +++ b/kivy/graphics/stencil_instructions.pxd @@ -0,0 +1,11 @@ +from kivy.graphics.instructions cimport Instruction + +cdef class StencilPush(Instruction): + cdef void apply(self) +cdef class StencilPop(Instruction): + cdef void apply(self) +cdef class StencilUse(Instruction): + cdef unsigned int _op + cdef void apply(self) +cdef class StencilUnUse(Instruction): + cdef void apply(self) diff --git a/kivy/graphics/stencil_instructions.pyx b/kivy/graphics/stencil_instructions.pyx index 092fe3146..6ea16d7e2 100644 --- a/kivy/graphics/stencil_instructions.pyx +++ b/kivy/graphics/stencil_instructions.pyx @@ -147,11 +147,14 @@ cdef class StencilUse(Instruction): '''Use current stencil buffer as a mask. Check module documentation for more information. ''' + def __init__(self, **kwargs): + super(StencilUse, self).__init__(**kwargs) + self._op = kwargs.get('op') or GL_EQUAL cdef void apply(self): global _stencil_in_push _stencil_in_push = 0 glColorMask(1, 1, 1, 1) - glStencilFunc(GL_EQUAL, _stencil_level, 0xff) + glStencilFunc(self._op, _stencil_level, 0xff) glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) cdef class StencilUnUse(Instruction): diff --git a/kivy/graphics/vertex_instructions.pyx b/kivy/graphics/vertex_instructions.pyx index 56eb818f1..2c92b3490 100644 --- a/kivy/graphics/vertex_instructions.pyx +++ b/kivy/graphics/vertex_instructions.pyx @@ -19,143 +19,14 @@ from kivy.graphics.c_opengl cimport * IF USE_OPENGL_DEBUG == 1: from kivy.graphics.c_opengl_debug cimport * from kivy.logger import Logger -from kivy.graphics.texture import Texture +from kivy.graphics.texture cimport Texture class GraphicException(Exception): '''Exception fired when a graphic error is fired. ''' -cdef class Line(VertexInstruction): - '''A 2d line. - - .. versionadded:: 1.0.8 - `dash_offset` and `dash_length` have been added - - :Parameters: - `points`: list - List of points in the format (x1, y1, x2, y2...) - `dash_length`: int - length of a segment (if dashed), default 1 - `dash_offset`: int - offset between the end of a segments and the begining of the - next one, default 0, changing this makes it dashed. - ''' - cdef list _points - cdef int _dash_offset, _dash_length - - def __init__(self, **kwargs): - VertexInstruction.__init__(self, **kwargs) - v = kwargs.get('points') - self.points = v if v is not None else [] - self.batch.set_mode('line_strip') - self._dash_length = kwargs.get('dash_length') or 1 - self._dash_offset = kwargs.get('dash_offset') or 0 - - cdef void build(self): - cdef int i, count = len(self.points) / 2 - cdef list p = self.points - cdef vertex_t *vertices = NULL - cdef unsigned short *indices = NULL - cdef float tex_x - cdef char *buf = NULL - cdef Texture texture = self.texture - - if count < 2: - self.batch.clear_data() - return - - if self._dash_offset != 0: - if texture is None or texture._width != \ - (self._dash_length + self._dash_offset) or \ - texture._height != 1: - - self.texture = texture = Texture.create( - size=(self._dash_length + self._dash_offset, 1)) - texture.wrap = 'repeat' - - # create a buffer to fill our texture - buf = malloc(4 * (self._dash_length + self._dash_offset)) - memset(buf, 255, self._dash_length * 4) - memset(buf + self._dash_length * 4, 0, self._dash_offset * 4) - p_str = PyString_FromStringAndSize(buf, (self._dash_length + self._dash_offset) * 4) - - self.texture.blit_buffer(p_str, colorfmt='rgba', bufferfmt='ubyte') - free(buf) - - elif texture is not None: - self.texture = None - - vertices = malloc(count * sizeof(vertex_t)) - if vertices == NULL: - raise MemoryError('vertices') - - indices = malloc(count * sizeof(unsigned short)) - if indices == NULL: - free(vertices) - raise MemoryError('indices') - - tex_x = 0 - for i in xrange(count): - if self._dash_offset != 0 and i > 0: - tex_x += sqrt( - pow(p[i * 2] - p[(i - 1) * 2], 2) + - pow(p[i * 2 + 1] - p[(i - 1) * 2 + 1], 2)) / ( - self._dash_length + self._dash_offset) - - vertices[i].s0 = tex_x - vertices[i].t0 = 0 - - vertices[i].x = p[i * 2] - vertices[i].y = p[i * 2 + 1] - indices[i] = i - - self.batch.set_data(vertices, count, indices, count) - - free(vertices) - free(indices) - - property points: - '''Property for getting/settings points of the line - - .. warning:: - - This will always reconstruct the whole graphics from the new points - list. It can be very CPU expensive. - ''' - def __get__(self): - return self._points - def __set__(self, points): - self._points = list(points) - self.flag_update() - - property dash_length: - '''Property for getting/setting the length of the dashes in the curve - - .. versionadded:: 1.0.8 - ''' - def __get__(self): - return self._dash_length - - def __set__(self, value): - if value < 0: - raise GraphicException('Invalid dash_length value, must be >= 0') - self._dash_length = value - self.flag_update() - - property dash_offset: - '''Property for getting/setting the offset between the dashes in the curve - - .. versionadded:: 1.0.8 - ''' - def __get__(self): - return self._dash_offset - - def __set__(self, value): - if value < 0: - raise GraphicException('Invalid dash_offset value, must be >= 0') - self._dash_offset = value - self.flag_update() +include "vertex_instructions_line.pxi" cdef class Bezier(VertexInstruction): @@ -903,7 +774,7 @@ cdef class BorderImage(Rectangle): hs[0], vs[1], ths[0], tvs[1], #v11 hs[1], vs[1], ths[1], tvs[1], #v12 hs[2], vs[1], ths[2], tvs[1], #v13 - hs[2], vs[2], ths[2], tvs[2], #v14 + hs[2], vs[2], ths[2], tvs[2], #v14 hs[1], vs[2], ths[1], tvs[2]] #v15 cdef unsigned short *indices = [ diff --git a/kivy/graphics/vertex_instructions_line.pxi b/kivy/graphics/vertex_instructions_line.pxi new file mode 100644 index 000000000..965c71680 --- /dev/null +++ b/kivy/graphics/vertex_instructions_line.pxi @@ -0,0 +1,765 @@ +DEF LINE_CAP_NONE = 0 +DEF LINE_CAP_SQUARE = 1 +DEF LINE_CAP_ROUND = 2 + +DEF LINE_JOINT_NONE = 0 +DEF LINE_JOINT_MITER = 1 +DEF LINE_JOINT_BEVEL = 2 +DEF LINE_JOINT_ROUND = 3 + +from kivy.graphics.stencil_instructions cimport StencilUse, StencilUnUse, StencilPush, StencilPop + +cdef inline int line_intersection(double x1, double y1, double x2, double y2, + double x3, double y3, double x4, double y4, double *px, double *py): + cdef double u = (x1 * y2 - y1 * x2) + cdef double v = (x3 * y4 - y3 * x4) + cdef double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) + if denom == 0: + return 0 + px[0] = (u * (x3 - x4) - (x1 - x2) * v) / denom + py[0] = (u * (y3 - y4) - (y1 - y2) * v) / denom + return 1 + +cdef class Line(VertexInstruction): + '''A 2d line. + + Drawing a line can be done easily:: + + with self.canvas: + Line(points=[100, 100, 200, 100, 100, 200], width=10) + + Actually, the line have 3 internal drawing mode that you should know about + if you want to get the best performance of it: + + #. If the :data:`width` is 1.0, then we will use standard GL_LINE drawing + from OpenGL. :data:`dash_length` and :data:`dash_offset` works, while + properties for cap and joint have no sense for this. + #. If the :data:`width` is > 1.0, then we will use a custom drawing method, + based on triangles. :data:`dash_length` and :data:`dash_offset` is not + working on that mode. + Additionally, if the current color have an alpha < 1.0, stencil will be + used internally to draw the line. + + .. image:: images/line-instruction.png + :align: center + + :Parameters: + `points`: list + List of points in the format (x1, y1, x2, y2...) + `dash_length`: int + Length of a segment (if dashed), default 1 + `dash_offset`: int + Offset between the end of a segments and the begining of the + next one, default 0, changing this makes it dashed. + `width`: float + Width of the line, default 1.0 + `cap`: str, default to 'round' + See :data:`cap` for more information. + `joint`: str, default to 'round' + See :data:`joint` for more information. + `cap_precision`: int, default to 10 + See :data:`cap_precision` for more information + `joint_precision`: int, default to 10 + See :data:`joint_precision` for more information + `close`: bool, default to False + If True, the line will be closed. + + .. versionadded:: 1.0.8 + `dash_offset` and `dash_length` have been added + + .. versionadded:: 1.4.1 + `width`, `cap`, `joint`, `cap_precision`, `joint_precision`, `close` + have been added. + ''' + cdef int _cap + cdef int _cap_precision + cdef int _joint_precision + cdef int _joint + cdef list _points + cdef float _width + cdef int _dash_offset, _dash_length + cdef int _use_stencil + cdef int _close + cdef Instruction _stencil_rect + cdef Instruction _stencil_push + cdef Instruction _stencil_use + cdef Instruction _stencil_unuse + cdef Instruction _stencil_pop + cdef double _bxmin, _bxmax, _bymin, _bymax + + def __init__(self, **kwargs): + VertexInstruction.__init__(self, **kwargs) + v = kwargs.get('points') + self.points = v if v is not None else [] + self.batch.set_mode('line_strip') + self._dash_length = kwargs.get('dash_length') or 1 + self._dash_offset = kwargs.get('dash_offset') or 0 + self._width = kwargs.get('width') or 1.0 + self.joint = kwargs.get('joint') or 'round' + 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._close = int(bool(kwargs.get('close', 0))) + self._stencil_rect = None + self._stencil_push = None + self._stencil_use = None + self._stencil_unuse = None + self._stencil_pop = None + self._use_stencil = 0 + + cdef void build(self): + if self._width == 1.0: + self.build_legacy() + else: + self.build_extended() + + cdef void ensure_stencil(self): + if self._stencil_rect == None: + self._stencil_rect = Rectangle() + self._stencil_push = StencilPush() + self._stencil_pop = StencilPop() + self._stencil_use = StencilUse(op=GL_LEQUAL) + self._stencil_unuse = StencilUnUse() + + cdef void apply(self): + if self._width == 1.: + VertexInstruction.apply(self) + return + + cdef double alpha = getActiveContext()['color'][-1] + self._use_stencil = alpha < 1 + if self._use_stencil: + self.ensure_stencil() + + self._stencil_push.apply() + VertexInstruction.apply(self) + self._stencil_use.apply() + self._stencil_rect.pos = self._bxmin, self._bymin + self._stencil_rect.size = self._bxmax - self._bxmin, self._bymax - self._bymin + self._stencil_rect.apply() + self._stencil_unuse.apply() + VertexInstruction.apply(self) + self._stencil_pop.apply() + else: + VertexInstruction.apply(self) + + + + cdef void build_legacy(self): + cdef int i, count = len(self.points) / 2 + cdef list p = self.points + cdef vertex_t *vertices = NULL + cdef unsigned short *indices = NULL + cdef float tex_x + cdef char *buf = NULL + cdef Texture texture = self.texture + + if count < 2: + self.batch.clear_data() + return + + if self._close: + p = p + [p[0], p[1]] + count += 1 + + self.batch.set_mode('line_strip') + if self._dash_offset != 0: + if texture is None or texture._width != \ + (self._dash_length + self._dash_offset) or \ + texture._height != 1: + + self.texture = texture = Texture.create( + size=(self._dash_length + self._dash_offset, 1)) + texture.wrap = 'repeat' + + # create a buffer to fill our texture + buf = malloc(4 * (self._dash_length + self._dash_offset)) + memset(buf, 255, self._dash_length * 4) + memset(buf + self._dash_length * 4, 0, self._dash_offset * 4) + p_str = PyString_FromStringAndSize(buf, (self._dash_length + self._dash_offset) * 4) + + self.texture.blit_buffer(p_str, colorfmt='rgba', bufferfmt='ubyte') + free(buf) + + elif texture is not None: + self.texture = None + + vertices = malloc(count * sizeof(vertex_t)) + if vertices == NULL: + raise MemoryError('vertices') + + indices = malloc(count * sizeof(unsigned short)) + if indices == NULL: + free(vertices) + raise MemoryError('indices') + + tex_x = 0 + for i in xrange(count): + if self._dash_offset != 0 and i > 0: + tex_x += sqrt( + pow(p[i * 2] - p[(i - 1) * 2], 2) + + pow(p[i * 2 + 1] - p[(i - 1) * 2 + 1], 2)) / ( + self._dash_length + self._dash_offset) + + vertices[i].s0 = tex_x + vertices[i].t0 = 0 + + vertices[i].x = p[i * 2] + vertices[i].y = p[i * 2 + 1] + indices[i] = i + + self.batch.set_data(vertices, count, indices, count) + + free(vertices) + free(indices) + + cdef void build_extended(self): + cdef int i, j, count = len(self.points) / 2 + cdef list p = self.points + cdef vertex_t *vertices = NULL + cdef unsigned short *indices = NULL + cdef float tex_x + cdef int cap + cdef char *buf = NULL + cdef Texture texture = self.texture + + self._bxmin = 999999999 + self._bymin = 999999999 + self._bxmax = -999999999 + self._bymax = -999999999 + + if count < 2: + self.batch.clear_data() + return + + cap = self._cap + if self._close and count > 2: + p = p + p[0:4] + count += 2 + cap = LINE_CAP_NONE + + self.batch.set_mode('triangles') + cdef unsigned int vertices_count = (count - 1) * 4 + cdef unsigned int indices_count = (count - 1) * 6 + cdef unsigned int iv = 0, ii = 0 + + if self._joint == LINE_JOINT_BEVEL: + indices_count += (count - 2) * 3 + vertices_count += (count - 2) + elif self._joint == LINE_JOINT_ROUND: + indices_count += (self._joint_precision * 3) * (count - 2) + vertices_count += (self._joint_precision) * (count - 2) + elif self._joint == LINE_JOINT_MITER: + indices_count += (count - 2) * 6 + vertices_count += (count - 2) * 2 + + if cap == LINE_CAP_SQUARE: + indices_count += 12 + vertices_count += 4 + elif cap == LINE_CAP_ROUND: + indices_count += (self._cap_precision * 3) * 2 + vertices_count += (self._cap_precision) * 2 + + vertices = malloc(vertices_count * sizeof(vertex_t)) + if vertices == NULL: + raise MemoryError('vertices') + + indices = malloc(indices_count * sizeof(unsigned short)) + if indices == NULL: + free(vertices) + raise MemoryError('indices') + + cdef double ax, ay, bx, by, cx, cy, angle, a1, a2 + cdef double x1, y1, x2, y2, x3, y3, x4, y4 + cdef double sx1, sy1, sx4, sy4, sangle + cdef double pcx, pcy, px1, py1, px2, py2, px3, py3, px4, py4, pangle, pangle2 + cdef double w = self._width + cdef double ix, iy + cdef unsigned int piv, pii2, piv2 + cdef double jangle + sangle = 0 + piv = pcx = pcy = cx = cy = ii = iv = ix = iy = 0 + px1 = px2 = px3 = px4 = py1 = py2 = py3 = py4 = 0 + sx1 = sy1 = sx4 = sy4 = 0 + x1 = x2 = x3 = x4 = y1 = y2 = y3 = y4 = 0 + cdef double cos1 = 0, cos2 = 0, sin1 = 0, sin2 = 0 + for i in range(0, count - 1): + ax = p[i * 2] + ay = p[i * 2 + 1] + bx = p[i * 2 + 2] + by = p[i * 2 + 3] + + if i > 0 and self._joint != LINE_JOINT_NONE: + pcx = cx + pcy = cy + px1 = x1 + px2 = x2 + px3 = x3 + px4 = x4 + py1 = y1 + py2 = y2 + py3 = y3 + py4 = y4 + + piv2 = piv + piv = iv + pangle2 = pangle + pangle = angle + + # calculate the orientation of the segment, between pi and -pi + cx = bx - ax + cy = by - ay + angle = atan2(cy, cx) + a1 = angle - PI2 + a2 = angle + PI2 + + # calculate the position of the segment + cos1 = cos(a1) * w + sin1 = sin(a1) * w + cos2 = cos(a2) * w + sin2 = sin(a2) * w + x1 = ax + cos1 + y1 = ay + sin1 + x4 = ax + cos2 + y4 = ay + sin2 + x2 = bx + cos1 + y2 = by + sin1 + x3 = bx + cos2 + y3 = by + sin2 + + if i == 0: + sx1 = x1 + sy1 = y1 + sx4 = x4 + sy4 = y4 + sangle = angle + + indices[ii ] = iv + indices[ii + 1] = iv + 1 + indices[ii + 2] = iv + 2 + indices[ii + 3] = iv + indices[ii + 4] = iv + 2 + indices[ii + 5] = iv + 3 + ii += 6 + + vertices[iv].x = x1 + vertices[iv].y = y1 + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + iv += 1 + vertices[iv].x = x2 + vertices[iv].y = y2 + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + iv += 1 + vertices[iv].x = x3 + vertices[iv].y = y3 + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + iv += 1 + vertices[iv].x = x4 + vertices[iv].y = y4 + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + iv += 1 + + # joint generation + if i == 0 or self._joint == LINE_JOINT_NONE: + continue + + # calculate the angle of the previous and current segment + jangle = atan2( + cx * pcy - cy * pcx, + cx * pcx + cy * pcy) + + # in case of the angle is NULL, avoid the generation + if jangle == 0 or jangle == PI or jangle == -PI: + if self._joint == LINE_JOINT_ROUND: + vertices_count -= self._joint_precision + indices_count -= self._joint_precision * 3 + elif self._joint == LINE_JOINT_BEVEL: + vertices_count -= 1 + indices_count -= 3 + elif self._joint == LINE_JOINT_MITER: + vertices_count -= 2 + indices_count -= 6 + continue + + if self._joint == LINE_JOINT_BEVEL: + vertices[iv].x = ax + vertices[iv].y = ay + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + if jangle < 0: + indices[ii] = piv2 + 1 + indices[ii + 1] = piv + indices[ii + 2] = iv + else: + indices[ii] = piv2 + 2 + indices[ii + 1] = piv + 3 + indices[ii + 2] = iv + ii += 3 + iv += 1 + + elif self._joint == LINE_JOINT_MITER: + vertices[iv].x = ax + vertices[iv].y = ay + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + if jangle < 0: + if line_intersection(px1, py1, px2, py2, x1, y1, x2, y2, &ix, &iy) == 0: + vertices_count -= 2 + indices_count -= 6 + continue + vertices[iv + 1].x = ix + vertices[iv + 1].y = iy + vertices[iv + 1].s0 = 0 + vertices[iv + 1].t0 = 0 + indices[ii] = iv + indices[ii + 1] = iv + 1 + indices[ii + 2] = piv2 + 1 + indices[ii + 3] = iv + indices[ii + 4] = piv + indices[ii + 5] = iv + 1 + ii += 6 + iv += 2 + else: + if line_intersection(px3, py3, px4, py4, x3, y3, x4, y4, &ix, &iy) == 0: + vertices_count -= 2 + indices_count -= 6 + continue + vertices[iv + 1].x = ix + vertices[iv + 1].y = iy + vertices[iv + 1].s0 = 0 + vertices[iv + 1].t0 = 0 + indices[ii] = iv + indices[ii + 1] = iv + 1 + indices[ii + 2] = piv2 + 2 + indices[ii + 3] = iv + indices[ii + 4] = piv + 3 + indices[ii + 5] = iv + 1 + ii += 6 + iv += 2 + + + + elif self._joint == LINE_JOINT_ROUND: + + # cap end + if jangle < 0: + a1 = pangle2 - PI2 + a2 = angle + PI2 + a0 = a2 + step = (abs(jangle)) / float(self._joint_precision) + pivstart = piv + 3 + pivend = piv2 + 1 + else: + a1 = angle - PI2 + a2 = pangle2 + PI2 + a0 = a1 + step = -(abs(jangle)) / float(self._joint_precision) + pivstart = piv + pivend = piv2 + 2 + siv = iv + vertices[iv].x = ax + vertices[iv].y = ay + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + iv += 1 + for j in xrange(0, self._joint_precision - 1): + vertices[iv].x = ax - cos(a0 - step * j) * w + vertices[iv].y = ay - sin(a0 - step * j) * w + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + if j == 0: + indices[ii] = siv + indices[ii + 1] = pivstart + indices[ii + 2] = iv + else: + indices[ii] = siv + indices[ii + 1] = iv - 1 + indices[ii + 2] = iv + iv += 1 + ii += 3 + indices[ii] = siv + indices[ii + 1] = iv - 1 + indices[ii + 2] = pivend + ii += 3 + + # caps + if cap == LINE_CAP_SQUARE: + vertices[iv].x = x2 + cos(angle) * w + vertices[iv].y = y2 + sin(angle) * w + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + vertices[iv + 1].x = x3 + cos(angle) * w + vertices[iv + 1].y = y3 + sin(angle) * w + vertices[iv + 1].s0 = 0 + vertices[iv + 1].t0 = 0 + indices[ii] = piv + 1 + indices[ii + 1] = piv + 2 + indices[ii + 2] = iv + 1 + indices[ii + 3] = piv + 1 + indices[ii + 4] = iv + indices[ii + 5] = iv + 1 + ii += 6 + iv += 2 + vertices[iv].x = sx1 - cos(sangle) * w + vertices[iv].y = sy1 - sin(sangle) * w + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + vertices[iv + 1].x = sx4 - cos(sangle) * w + vertices[iv + 1].y = sy4 - sin(sangle) * w + vertices[iv + 1].s0 = 0 + vertices[iv + 1].t0 = 0 + indices[ii] = 0 + indices[ii + 1] = 3 + indices[ii + 2] = iv + 1 + indices[ii + 3] = 0 + indices[ii + 4] = iv + indices[ii + 5] = iv + 1 + ii += 6 + iv += 2 + + elif cap == LINE_CAP_ROUND: + + # cap start + a1 = sangle - PI2 + a2 = sangle + PI2 + step = (a1 - a2) / float(self._cap_precision) + siv = iv + cx = p[0] + cy = p[1] + vertices[iv].x = cx + vertices[iv].y = cy + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + iv += 1 + for i in xrange(0, self._cap_precision - 1): + vertices[iv].x = cx + cos(a1 + step * i) * w + vertices[iv].y = cy + sin(a1 + step * i) * w + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + if i == 0: + indices[ii] = siv + indices[ii + 1] = 0 + indices[ii + 2] = iv + else: + indices[ii] = siv + indices[ii + 1] = iv - 1 + indices[ii + 2] = iv + iv += 1 + ii += 3 + indices[ii] = siv + indices[ii + 1] = iv - 1 + indices[ii + 2] = 3 + ii += 3 + + # cap end + a1 = angle - PI2 + a2 = angle + PI2 + step = (a2 - a1) / float(self._cap_precision) + siv = iv + cx = p[-2] + cy = p[-1] + vertices[iv].x = cx + vertices[iv].y = cy + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + iv += 1 + for i in xrange(0, self._cap_precision - 1): + vertices[iv].x = cx + cos(a1 + step * i) * w + vertices[iv].y = cy + sin(a1 + step * i) * w + vertices[iv].s0 = 0 + vertices[iv].t0 = 0 + if i == 0: + indices[ii] = siv + indices[ii + 1] = piv + 1 + indices[ii + 2] = iv + else: + indices[ii] = siv + indices[ii + 1] = iv - 1 + indices[ii + 2] = iv + iv += 1 + ii += 3 + indices[ii] = siv + indices[ii + 1] = iv - 1 + indices[ii + 2] = piv + 2 + ii += 3 + + #print 'ii=', ii, 'indices_count=', indices_count + #print 'iv=', iv, 'vertices_count', vertices_count + + # compute bbox + for i in xrange(vertices_count): + if vertices[i].x < self._bxmin: + self._bxmin = vertices[i].x + if vertices[i].x > self._bxmax: + self._bxmax = vertices[i].x + if vertices[i].y < self._bymin: + self._bymin = vertices[i].y + if vertices[i].y > self._bymax: + self._bymax = vertices[i].y + + self.batch.set_data(vertices, vertices_count, indices, indices_count) + + free(vertices) + free(indices) + + + + property points: + '''Property for getting/settings points of the line + + .. warning:: + + This will always reconstruct the whole graphics from the new points + list. It can be very CPU expensive. + ''' + def __get__(self): + return self._points + def __set__(self, points): + self._points = list(points) + self.flag_update() + + property dash_length: + '''Property for getting/setting the length of the dashes in the curve + + .. versionadded:: 1.0.8 + ''' + def __get__(self): + return self._dash_length + + def __set__(self, value): + if value < 0: + raise GraphicException('Invalid dash_length value, must be >= 0') + self._dash_length = value + self.flag_update() + + property dash_offset: + '''Property for getting/setting the offset between the dashes in the curve + + .. versionadded:: 1.0.8 + ''' + def __get__(self): + return self._dash_offset + + def __set__(self, value): + if value < 0: + raise GraphicException('Invalid dash_offset value, must be >= 0') + self._dash_offset = value + self.flag_update() + + property width: + '''Determine the width of the line, default to 1.0. + + .. versionadded:: 1.4.1 + ''' + def __get__(self): + return self._width + + def __set__(self, value): + if value <= 0: + raise GraphicException('Invalid width value, must be > 0') + self._width = value + self.flag_update() + + property cap: + '''Determine the cap of the line, default to 'round'. Can be one of + 'none', 'square' or 'round' + + .. versionadded:: 1.4.1 + ''' + def __get__(self): + if self._cap == LINE_CAP_SQUARE: + return 'square' + elif self._cap == LINE_CAP_ROUND: + return 'round' + return 'none' + + def __set__(self, value): + if value not in ('none', 'square', 'round'): + raise GraphicException('Invalid cap, must be one of ' + '"none", "square", "round"') + if value == 'square': + self._cap = LINE_CAP_SQUARE + elif value == 'round': + self._cap = LINE_CAP_ROUND + else: + self._cap = LINE_CAP_NONE + self.flag_update() + + property joint: + '''Determine the join of the line, default to 'round'. Can be one of + 'none', 'round', 'bevel', 'miter'. + + .. versionadded:: 1.4.1 + ''' + + def __get__(self): + if self._joint == LINE_JOINT_ROUND: + return 'round' + elif self._joint == LINE_JOINT_BEVEL: + return 'bevel' + elif self._joint == LINE_JOINT_MITER: + return 'miter' + return 'none' + + def __set__(self, value): + if value not in ('none', 'miter', 'bevel', 'round'): + raise GraphicException('Invalid joint, must be one of ' + '"none", "miter", "bevel", "round"') + if value == 'round': + self._joint = LINE_JOINT_ROUND + elif value == 'bevel': + self._joint = LINE_JOINT_BEVEL + elif value == 'miter': + self._joint = LINE_JOINT_MITER + else: + self._joint = LINE_JOINT_NONE + self.flag_update() + + property cap_precision: + '''Number of iteration for drawing the "round" cap, default to 10. + The cap_precision must be at least 1. + + .. versionadded:: 1.4.1 + ''' + + def __get__(self): + return self._cap_precision + + def __set__(self, value): + if value < 1: + raise GraphicException('Invalid cap_precision value, must be >= 1') + self._cap_precision = int(value) + self.flag_update() + + property joint_precision: + '''Number of iteration for drawing the "round" joint, default to 10. + The joint_precision must be at least 1. + + .. versionadded:: 1.4.1 + ''' + + def __get__(self): + return self._joint_precision + + def __set__(self, value): + if value < 1: + raise GraphicException('Invalid cap_precision value, must be >= 1') + self._joint_precision = int(value) + self.flag_update() + + property close: + '''If True, the line will be closed. + + .. versionadded:: 1.4.1 + ''' + + def __get__(self): + return self._close + + def __set__(self, value): + self._close = int(bool(value)) + self.flag_update()