This repository has been archived on 2025-02-01. You can view files and clone it, but cannot push or open issues or pull requests.
PyBitmessage-2025-02-01/mockenv/lib/python3.6/site-packages/kivy/graphics/vertex_instructions_line.pxi
2022-07-22 16:13:59 +05:30

1562 lines
53 KiB
Cython

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
DEF LINE_MODE_POINTS = 0
DEF LINE_MODE_ELLIPSE = 1
DEF LINE_MODE_CIRCLE = 2
DEF LINE_MODE_RECTANGLE = 3
DEF LINE_MODE_ROUNDED_RECTANGLE = 4
DEF LINE_MODE_BEZIER = 5
from kivy.graphics.stencil_instructions cimport StencilUse, StencilUnUse, StencilPush, StencilPop
import itertools
cdef float PI = <float>3.1415926535
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)
The line has 3 internal drawing modes that you should be aware of
for optimal results:
#. If the :attr:`width` is 1.0, then the standard GL_LINE drawing from
OpenGL will be used. :attr:`dash_length`, :attr:`dash_offset`, and :attr:`dashes` will
work, while properties for cap and joint have no meaning here.
#. If the :attr:`width` is greater than 1.0, then a custom drawing method,
based on triangulation, will be used. :attr:`dash_length`,
:attr:`dash_offset`, and :attr:`dashes` do not work in this mode.
Additionally, if the current color has an alpha less than 1.0, a
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), defaults to 1.
`dash_offset`: int
Offset between the end of a segment and the beginning of the
next one, defaults to 0. Changing this makes it dashed.
`dashes`: list of ints
List of [ON length, offset, ON length, offset, ...]. E.g. ``[2,4,1,6,8,2]``
would create a line with the first dash length 2 then an offset of 4 then
a dash lenght of 1 then an offset of 6 and so on. Defaults to ``[]``.
Changing this makes it dashed and overrides `dash_length` and `dash_offset`.
`width`: float
Width of the line, defaults to 1.0.
`cap`: str, defaults to 'round'
See :attr:`cap` for more information.
`joint`: str, defaults to 'round'
See :attr:`joint` for more information.
`cap_precision`: int, defaults to 10
See :attr:`cap_precision` for more information
`joint_precision`: int, defaults to 10
See :attr:`joint_precision` for more information
See :attr:`cap_precision` for more information.
`joint_precision`: int, defaults to 10
See :attr:`joint_precision` for more information.
`close`: bool, defaults to False
If True, the line will be closed.
`circle`: list
If set, the :attr:`points` will be set to build a circle. See
:attr:`circle` for more information.
`ellipse`: list
If set, the :attr:`points` will be set to build an ellipse. See
:attr:`ellipse` for more information.
`rectangle`: list
If set, the :attr:`points` will be set to build a rectangle. See
:attr:`rectangle` for more information.
`bezier`: list
If set, the :attr:`points` will be set to build a bezier line. See
:attr:`bezier` for more information.
`bezier_precision`: int, defaults to 180
Precision of the Bezier drawing.
.. versionchanged:: 1.0.8
`dash_offset` and `dash_length` have been added.
.. versionchanged:: 1.4.1
`width`, `cap`, `joint`, `cap_precision`, `joint_precision`, `close`,
`ellipse`, `rectangle` have been added.
.. versionchanged:: 1.4.1
`bezier`, `bezier_precision` have been added.
.. versionchanged:: 1.11.0
`dashes` 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 list _dash_list
cdef float _width
cdef int _dash_offset, _dash_length
cdef int _use_stencil
cdef int _close
cdef int _mode
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
cdef tuple _mode_args
def __init__(self, **kwargs):
super(Line, self).__init__(**kwargs)
v = kwargs.get('points')
self.points = v if v is not None else []
self.dashes = kwargs.get('dashes', [])
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._bezier_precision = kwargs.get('bezier_precision') or 180
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
if 'ellipse' in kwargs:
self.ellipse = kwargs['ellipse']
if 'circle' in kwargs:
self.circle = kwargs['circle']
if 'rectangle' in kwargs:
self.rectangle = kwargs['rectangle']
if 'rounded_rectangle' in kwargs:
self.rounded_rectangle = kwargs['rounded_rectangle']
if 'bezier' in kwargs:
self.bezier = kwargs['bezier']
cdef void build(self):
if self._mode == LINE_MODE_ELLIPSE:
self.prebuild_ellipse()
elif self._mode == LINE_MODE_CIRCLE:
self.prebuild_circle()
elif self._mode == LINE_MODE_RECTANGLE:
self.prebuild_rectangle()
elif self._mode == LINE_MODE_ROUNDED_RECTANGLE:
self.prebuild_rounded_rectangle()
elif self._mode == LINE_MODE_BEZIER:
self.prebuild_bezier()
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='lequal')
self._stencil_unuse = StencilUnUse()
cdef int apply(self) except -1:
if self._width == 1.:
VertexInstruction.apply(self)
return 0
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)
return 0
cdef void build_legacy(self):
cdef int i
cdef long count = <int>int(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
cdef int length = 0
cdef int position = 0
cdef int val
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_list:
length = sum(self._dash_list)
if texture is None or texture._width != length \
or texture._height != 1:
self.texture = texture = Texture.create(size=(length, 1))
texture.wrap = 'repeat'
# create a buffer to fill our texture
buf = <char *>malloc(4 * length)
memset(buf, 0, 4 * length)
for idx, val in enumerate(self._dash_list):
if idx % 2 == 0:
memset(buf + position, 255, val * 4)
position += val * 4
p_str = buf[:position]
try:
self.texture.blit_buffer(p_str, colorfmt='rgba', bufferfmt='ubyte')
finally:
free(buf)
elif self._dash_offset != 0:
length = self._dash_length + self._dash_offset
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 = <char *>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 = buf[:(self._dash_length + self._dash_offset) * 4]
try:
self.texture.blit_buffer(p_str, colorfmt='rgba', bufferfmt='ubyte')
finally:
free(buf)
elif texture is not None:
self.texture = None
vertices = <vertex_t *>malloc(count * sizeof(vertex_t))
if vertices == NULL:
raise MemoryError('vertices')
indices = <unsigned short *>malloc(count * sizeof(unsigned short))
if indices == NULL:
free(vertices)
raise MemoryError('indices')
tex_x = 0
for i in range(count):
if (self._dash_offset != 0 or self._dash_list) and i > 0:
tex_x += <float>(sqrt(
pow(p[i * 2] - p[(i - 1) * 2], 2) +
pow(p[i * 2 + 1] - p[(i - 1) * 2 + 1], 2)) / length)
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, <int>count, indices, <int>count)
free(vertices)
free(indices)
cdef void build_extended(self):
cdef int i, j
cdef long count = <int>int(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
self.texture = None
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 long vertices_count = (count - 1) * 4
cdef unsigned long indices_count = (count - 1) * 6
cdef unsigned int iv = 0, ii = 0, siv = 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 = <vertex_t *>malloc(vertices_count * sizeof(vertex_t))
if vertices == NULL:
raise MemoryError('vertices')
indices = <unsigned short *>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 = 0, pangle2
cdef double w = self._width
cdef double ix, iy
cdef unsigned int piv, pii2, piv2, skip = 0
cdef double jangle
angle = 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 (ax, ay) == (bx, _by):
skip += 1
continue
if i - skip > 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 - skip == 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 = <float>x1
vertices[iv].y = <float>y1
vertices[iv].s0 = 0
vertices[iv].t0 = 0
iv += 1
vertices[iv].x = <float>x2
vertices[iv].y = <float>y2
vertices[iv].s0 = 1
vertices[iv].t0 = 0
iv += 1
vertices[iv].x = <float>x3
vertices[iv].y = <float>y3
vertices[iv].s0 = 1
vertices[iv].t0 = 1
iv += 1
vertices[iv].x = <float>x4
vertices[iv].y = <float>y4
vertices[iv].s0 = 0
vertices[iv].t0 = 1
iv += 1
# joint generation
if i - skip == 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:
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 = <float>ax
vertices[iv].y = <float>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 = <float>ax
vertices[iv].y = <float>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 = <float>ix
vertices[iv + 1].y = <float>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 = <float>ix
vertices[iv + 1].y = <float>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 = <float>ax
vertices[iv].y = <float>ay
vertices[iv].s0 = 0
vertices[iv].t0 = 0
iv += 1
for j in xrange(0, self._joint_precision - 1):
vertices[iv].x = <float>(ax - cos(a0 - step * j) * w)
vertices[iv].y = <float>(ay - sin(a0 - step * j) * w)
vertices[iv].s0 = 0
vertices[iv].t0 = 0
if j == 0:
indices[ii] = siv
indices[ii + 1] = <unsigned short>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] = <unsigned short>pivend
ii += 3
# caps
if cap == LINE_CAP_SQUARE:
vertices[iv].x = <float>(x2 + cos(angle) * w)
vertices[iv].y = <float>(y2 + sin(angle) * w)
vertices[iv].s0 = 0
vertices[iv].t0 = 0
vertices[iv + 1].x = <float>(x3 + cos(angle) * w)
vertices[iv + 1].y = <float>(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 = <float>(sx1 - cos(sangle) * w)
vertices[iv].y = <float>(sy1 - sin(sangle) * w)
vertices[iv].s0 = 0
vertices[iv].t0 = 0
vertices[iv + 1].x = <float>(sx4 - cos(sangle) * w)
vertices[iv + 1].y = <float>(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 = <float>cx
vertices[iv].y = <float>cy
vertices[iv].s0 = 0
vertices[iv].t0 = 0
iv += 1
for i in xrange(0, self._cap_precision - 1):
vertices[iv].x = <float>(cx + cos(a1 + step * i) * w)
vertices[iv].y = <float>(cy + sin(a1 + step * i) * w)
vertices[iv].s0 = 1
vertices[iv].t0 = 1
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 = <float>cx
vertices[iv].y = <float>cy
vertices[iv].s0 = 0
vertices[iv].t0 = 0
iv += 1
for i in xrange(0, self._cap_precision - 1):
vertices[iv].x = <float>(cx + cos(a1 + step * i) * w)
vertices[iv].y = <float>(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
while ii < indices_count:
# make all the remaining indices point to the last vertice
indices[ii] = siv
ii += 1
# compute bbox
cdef unsigned long iul
for iul in xrange(vertices_count):
if vertices[iul].x < self._bxmin:
self._bxmin = vertices[iul].x
if vertices[iul].x > self._bxmax:
self._bxmax = vertices[iul].x
if vertices[iul].y < self._bymin:
self._bymin = vertices[iul].y
if vertices[iul].y > self._bymax:
self._bymax = vertices[iul].y
self.batch.set_data(vertices, <int>vertices_count,
indices, <int>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):
if points and isinstance(points[0], (list, tuple)):
self._points = list(itertools.chain(*points))
else:
self._points = list(points)
self._mode = LINE_MODE_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 dashes:
'''Property for getting/setting ``dashes``.
List of [ON length, offset, ON length, offset, ...]. E.g. ``[2,4,1,6,8,2]``
would create a line with the first dash length 2 then an offset of 4 then
a dash lenght of 1 then an offset of 6 and so on.
.. versionadded:: 1.11.0
'''
def __get__(self):
return self._dash_list
def __set__(self, value):
self._dash_list = list(value)
self.flag_update()
property width:
'''Determine the width of the line, defaults 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, defaults 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, defaults 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, defaults 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, defaults 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 joint_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()
property ellipse:
'''Use this property to build an ellipse, without calculating the
:attr:`points`. You can only set this property, not get it.
The argument must be a tuple of (x, y, width, height, angle_start,
angle_end, segments):
* x and y represent the bottom left of the ellipse
* width and height represent the size of the ellipse
* (optional) angle_start and angle_end are in degree. The default
value is 0 and 360.
* (optional) segments is the precision of the ellipse. The default
value is calculated from the range between angle.
Note that it's up to you to :attr:`close` the ellipse or not.
For example, for building a simple ellipse, in python::
# simple ellipse
Line(ellipse=(0, 0, 150, 150))
# only from 90 to 180 degrees
Line(ellipse=(0, 0, 150, 150, 90, 180))
# only from 90 to 180 degrees, with few segments
Line(ellipse=(0, 0, 150, 150, 90, 180, 20))
.. versionadded:: 1.4.1
'''
def __set__(self, args):
if args == None:
raise GraphicException(
'Invalid ellipse value: {0!r}'.format(args))
if len(args) not in (4, 6, 7):
raise GraphicException('Invalid number of arguments: '
'{0} instead of 4, 6 or 7.'.format(len(args)))
self._mode_args = tuple(args)
self._mode = LINE_MODE_ELLIPSE
self.flag_update()
cdef void prebuild_ellipse(self):
cdef double x, y, w, h, angle_start = 0, angle_end = 360
cdef int angle_dir, segments = 0
cdef double angle_range
cdef tuple args = self._mode_args
if len(args) == 4:
x, y, w, h = args
elif len(args) == 6:
x, y, w, h, angle_start, angle_end = args
elif len(args) == 7:
x, y, w, h, angle_start, angle_end, segments = args
segments += 2
else:
x = y = w = h = 0
assert(0)
if angle_end > angle_start:
angle_dir = 1
else:
angle_dir = -1
if segments == 0:
segments = int(abs(angle_end - angle_start) / 2) + 3
if segments % 2 == 1:
segments += 1
# rad = deg * (pi / 180), where pi/180 = 0.0174...
angle_start = angle_start * 0.017453292519943295
angle_end = angle_end * 0.017453292519943295
angle_range = abs(angle_end - angle_start) / (segments - 2)
cdef list points = [0, ] * segments
cdef double angle
cdef double rx = w * 0.5
cdef double ry = h * 0.5
for i in xrange(0, segments, 2):
angle = angle_start + (angle_dir * (i - 1) * angle_range)
points[i] = (x + rx) + (rx * sin(angle))
points[i + 1] = (y + ry) + (ry * cos(angle))
self._points = points
property circle:
'''Use this property to build a circle, without calculating the
:attr:`points`. You can only set this property, not get it.
The argument must be a tuple of (center_x, center_y, radius, angle_start,
angle_end, segments):
* center_x and center_y represent the center of the circle
* radius represent the radius of the circle
* (optional) angle_start and angle_end are in degree. The default
value is 0 and 360.
* (optional) segments is the precision of the ellipse. The default
value is calculated from the range between angle.
Note that it's up to you to :attr:`close` the circle or not.
For example, for building a simple ellipse, in python::
# simple circle
Line(circle=(150, 150, 50))
# only from 90 to 180 degrees
Line(circle=(150, 150, 50, 90, 180))
# only from 90 to 180 degrees, with few segments
Line(circle=(150, 150, 50, 90, 180, 20))
.. versionadded:: 1.4.1
'''
def __set__(self, args):
if args == None:
raise GraphicException(
'Invalid circle value: {0!r}'.format(args))
if len(args) not in (3, 5, 6):
raise GraphicException('Invalid number of arguments: '
'{0} instead of 3, 5 or 6.'.format(len(args)))
self._mode_args = tuple(args)
self._mode = LINE_MODE_CIRCLE
self.flag_update()
cdef void prebuild_circle(self):
cdef double x, y, r, angle_start = 0, angle_end = 360
cdef int angle_dir, segments = 0
cdef double angle_range
cdef tuple args = self._mode_args
if len(args) == 3:
x, y, r = args
elif len(args) == 5:
x, y, r, angle_start, angle_end = args
elif len(args) == 6:
x, y, r, angle_start, angle_end, segments = args
segments += 1
else:
x = y = r = 0
assert(0)
if angle_end > angle_start:
angle_dir = 1
else:
angle_dir = -1
if segments == 0:
segments = int(abs(angle_end - angle_start) / 2) + 3
segmentpoints = segments * 2
# rad = deg * (pi / 180), where pi/180 = 0.0174...
angle_start = angle_start * 0.017453292519943295
angle_end = angle_end * 0.017453292519943295
angle_range = abs(angle_end - angle_start) / (segmentpoints - 2)
cdef list points = [0, ] * segmentpoints
cdef double angle
for i in xrange(0, segmentpoints, 2):
angle = angle_start + (angle_dir * i * angle_range)
points[i] = x + (r * sin(angle))
points[i + 1] = y + (r * cos(angle))
self._points = points
property rectangle:
'''Use this property to build a rectangle, without calculating the
:attr:`points`. You can only set this property, not get it.
The argument must be a tuple of (x, y, width, height):
* 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.1
'''
def __set__(self, args):
if args == None:
raise GraphicException(
'Invalid rectangle value: {0!r}'.format(args))
if len(args) != 4:
raise GraphicException('Invalid number of arguments: '
'{0} instead of 4.'.format(len(args)))
self._mode_args = tuple(args)
self._mode = LINE_MODE_RECTANGLE
self.flag_update()
cdef void prebuild_rectangle(self):
cdef double x, y, width, height
cdef int angle_dir, segments = 0
cdef double angle_range
cdef tuple args = self._mode_args
if args == None:
raise GraphicException(
'Invalid ellipse value: {0!r}'.format(args))
if len(args) == 4:
x, y, width, height = args
else:
x = y = width = height = 0
assert(0)
self._points = [x, y, x + width, y, x + width, y + height, x, y + height]
self._close = 1
property rounded_rectangle:
'''Use this property to build a rectangle, without calculating the
:attr:`points`. You can only set this property, not get it.
The argument must be a tuple of one of the following forms:
* (x, y, width, height, corner_radius)
* (x, y, width, height, corner_radius, resolution)
* (x, y, width, height, corner_radius1, corner_radius2, corner_radius3, corner_radius4)
* (x, y, width, height, corner_radius1, corner_radius2, corner_radius3, corner_radius4, resolution)
* x and y represent the bottom-left position of the rectangle
* width and height represent the size
* corner_radius is the number of pixels between two borders and the center of the circle arc joining them
* resolution is the number of line segment that will be used to draw the circle arc at each corner (defaults to 30)
The line is automatically closed.
Usage::
Line(rounded_rectangle=(0, 0, 200, 200, 10, 20, 30, 40, 100))
.. versionadded:: 1.9.0
'''
def __set__(self, args):
if args == None:
raise GraphicException(
'Invlid rounded rectangle value: {0!r}'.format(args))
if len(args) not in (5, 6, 8, 9):
raise GraphicException('invalid number of arguments:'
'{0} not in (5, 6, 8, 9)'.format(len(args)))
self._mode_args = tuple(args)
self._mode = LINE_MODE_ROUNDED_RECTANGLE
self.flag_update()
cdef void prebuild_rounded_rectangle(self):
cdef float a, px, py, x, y, w, h, c1, c2, c3, c4
cdef resolution = 30
cdef int l = <int>len(self._mode_args)
self._points = []
a = <float>-PI
x, y, w, h = self._mode_args [:4]
if l == 5:
c1 = c2 = c3 = c4 = self._mode_args[4]
elif l == 6:
c1 = c2 = c3 = c4 = self._mode_args[4]
resolution = self._mode_args[5]
elif l == 8:
c1, c2, c3, c4 = self._mode_args[4:]
else: # l == 9, but else make the compiler happy about uninitialization
c1, c2, c3, c4 = self._mode_args[4:8]
resolution = self._mode_args[8]
px = x + c1
py = y + c1
while a < - PI / 2.:
a += pi / resolution
self._points.extend([
px + cos(a) * c1,
py + sin(a) * c1])
px = x + w - c2
py = y + c2
while a < 0:
a += PI / resolution
self._points.extend([
px + cos(a) * c2,
py + sin(a) * c2])
px = x + w - c3
py = y + h - c3
while a < PI / 2.:
a += PI / resolution
self._points.extend([
px + cos(a) * c3,
py + sin(a) * c3])
px = x + c4
py = y + h - c4
while a < PI:
a += PI / resolution
self._points.extend([
px + cos(a) * c4,
py + sin(a) * c4])
self._close = 1
property bezier:
'''Use this property to build a bezier line, without calculating the
:attr:`points`. You can only set this property, not get it.
The argument must be a tuple of 2n elements, n being the number of points.
Usage::
Line(bezier=(x1, y1, x2, y2, x3, y3)
.. versionadded:: 1.4.2
.. note:: Bezier lines calculations are inexpensive for a low number of
points, but complexity is quadratic, so lines with a lot of points
can be very expensive to build, use with care!
'''
def __set__(self, args):
if args == None or len(args) % 2:
raise GraphicException(
'Invalid bezier 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,
defaults 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()
cdef class SmoothLine(Line):
'''Experimental line using over-draw methods to get better anti-aliasing
results. It has few drawbacks:
- drawing a line with alpha will probably not have the intended result if
the line crosses itself.
- :attr:`~Line.cap`, :attr:`~Line.joint` and :attr:`~Line.dash` properties
are not supported.
- it uses a custom texture with a premultiplied alpha.
- lines under 1px in width are not supported: they will look the same.
.. warning::
This is an unfinished work, experimental, and subject to crashes.
.. versionadded:: 1.9.0
'''
cdef float _owidth
def __init__(self, **kwargs):
Line.__init__(self, **kwargs)
self._owidth = kwargs.get("overdraw_width") or <float>1.2
self.batch.set_mode("triangles")
self.texture = self.premultiplied_texture()
def premultiplied_texture(self):
texture = Texture.create(size=(4, 1), colorfmt="rgba")
texture.add_reload_observer(self._smooth_reload_observer)
self._smooth_reload_observer(texture)
return texture
cpdef _smooth_reload_observer(self, texture):
cdef bytes GRADIENT_DATA = (
b"\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00")
texture.blit_buffer(GRADIENT_DATA, colorfmt="rgba")
cdef void build(self):
if self._mode == LINE_MODE_ELLIPSE:
self.prebuild_ellipse()
elif self._mode == LINE_MODE_CIRCLE:
self.prebuild_circle()
elif self._mode == LINE_MODE_RECTANGLE:
self.prebuild_rectangle()
elif self._mode == LINE_MODE_ROUNDED_RECTANGLE:
self.prebuild_rounded_rectangle()
elif self._mode == LINE_MODE_BEZIER:
self.prebuild_bezier()
self.build_smooth()
cdef int apply(self) except -1:
VertexInstruction.apply(self)
return 0
cdef void build_smooth(self):
cdef:
list p = self.points
double width = max(0, (self._width - 1.))
double owidth = width + self._owidth
vertex_t *vertices = NULL
unsigned short *indices = NULL
unsigned short *tindices = NULL
double ax, ay, bx = 0., by = 0., rx = 0., ry = 0., last_angle = 0., angle, av_angle
double cos1, sin1, cos2, sin2, ocos1, ocos2, osin1, osin2
long index, icount, iv, ii, max_vindex, count
unsigned short i0, i1, i2, i3, i4, i5, i6, i7, vindex, vcount
iv = vindex = 0
count = <long>int(len(p) / 2.)
if count < 2:
self.batch.clear_data()
return
vcount = <unsigned short>(count * 4)
icount = (count - 1) * 18
if self._close:
icount += 18
vertices = <vertex_t *>malloc(vcount * sizeof(vertex_t))
if vertices == NULL:
raise MemoryError("vertices")
indices = <unsigned short *>malloc(icount * sizeof(unsigned short))
if indices == NULL:
free(vertices)
raise MemoryError("indices")
if self._close:
ax = p[-2]
ay = p[-1]
bx = p[0]
by = p[1]
rx = bx - ax
ry = by - ay
last_angle = atan2(ry, rx)
max_index = len(p)
for index in range(0, max_index, 2):
ax = p[index]
ay = p[index + 1]
if index < max_index - 2:
bx = p[index + 2]
by = p[index + 3]
rx = bx - ax
ry = by - ay
angle = atan2(ry, rx)
else:
angle = last_angle
if index == 0 and not self._close:
av_angle = angle
ad_angle = pi
else:
av_angle = atan2(
sin(angle) + sin(last_angle),
cos(angle) + cos(last_angle))
ad_angle = abs(pi - abs(angle - last_angle))
a1 = av_angle - PI2
a2 = av_angle + PI2
'''
cos1 = cos(a1) * width
sin1 = sin(a1) * width
cos2 = cos(a2) * width
sin2 = sin(a2) * width
ocos1 = cos(a1) * owidth
osin1 = sin(a1) * owidth
ocos2 = cos(a2) * owidth
osin2 = sin(a2) * owidth
print 'angle diff', ad_angle
'''
#l = width
#ol = owidth
if index == 0 or index >= max_index - 2:
l = width
ol = owidth
else:
la1 = last_angle - PI2
la2 = angle - PI2
ra1 = last_angle + PI2
ra2 = angle + PI2
ox = p[index - 2]
oy = p[index - 1]
if line_intersection(
ox + cos(la1) * width,
oy + sin(la1) * width,
ax + cos(la1) * width,
ay + sin(la1) * width,
ax + cos(la2) * width,
ay + sin(la2) * width,
bx + cos(la2) * width,
by + sin(la2) * width,
&rx, &ry) == 0:
#print 'ERROR LINE INTERSECTION 1'
pass
l = <float>sqrt((ax - rx) ** 2 + (ay - ry) ** 2)
if line_intersection(
ox + cos(ra1) * owidth,
oy + sin(ra1) * owidth,
ax + cos(ra1) * owidth,
ay + sin(ra1) * owidth,
ax + cos(ra2) * owidth,
ay + sin(ra2) * owidth,
bx + cos(ra2) * owidth,
by + sin(ra2) * owidth,
&rx, &ry) == 0:
#print 'ERROR LINE INTERSECTION 2'
pass
ol = <float>sqrt((ax - rx) ** 2 + (ay - ry) ** 2)
last_angle = angle
#l = sqrt(width ** 2 * (1. / sin(av_angle)) ** 2)
#l = width / tan(av_angle / 2.)
#l = width * sqrt(1 + 1 / (av_angle / 2.))
#l = 2 * (width * width * sin(av_angle))
#l = 2 * (cos(av_angle / 2.) * width)
#l = width / abs(cos(PI2 - 1.5 * ad_angle))
cos1 = cos(a1) * l
sin1 = sin(a1) * l
cos2 = cos(a2) * l
sin2 = sin(a2) * l
#ol = sqrt(owidth ** 2 * (1. / sin(av_angle)) ** 2)
#ol = owidth / tan(av_angle / 2.)
#ol = owidth * sqrt(1 + 1 / (av_angle / 2.))
#ol = 2 * (owidth * owidth * sin(av_angle))
#ol = 2 * (cos(av_angle / 2.) * owidth)
#ol = owidth / abs(cos(PI2 - 1.5 * ad_angle))
ocos1 = cos(a1) * ol
osin1 = sin(a1) * ol
ocos2 = cos(a2) * ol
osin2 = sin(a2) * ol
x1 = ax + cos1
y1 = ay + sin1
x2 = ax + cos2
y2 = ay + sin2
ox1 = ax + ocos1
oy1 = ay + osin1
ox2 = ax + ocos2
oy2 = ay + osin2
vertices[iv].x = <float>x1
vertices[iv].y = <float>y1
vertices[iv].s0 = 0.5
vertices[iv].t0 = 0.25
iv += 1
vertices[iv].x = <float>x2
vertices[iv].y = <float>y2
vertices[iv].s0 = 0.5
vertices[iv].t0 = 0.75
iv += 1
vertices[iv].x = <float>ox1
vertices[iv].y = <float>oy1
vertices[iv].s0 = 1
vertices[iv].t0 = 0
iv += 1
vertices[iv].x = <float>ox2
vertices[iv].y = <float>oy2
vertices[iv].s0 = 1
vertices[iv].t0 = 1
iv += 1
tindices = indices
for vindex in range(0, vcount - 4, 4):
tindices[0] = vindex
tindices[1] = vindex + 2
tindices[2] = vindex + 6
tindices[3] = vindex
tindices[4] = vindex + 6
tindices[5] = vindex + 4
tindices[6] = vindex + 1
tindices[7] = vindex
tindices[8] = vindex + 4
tindices[9] = vindex + 1
tindices[10] = vindex + 4
tindices[11] = vindex + 5
tindices[12] = vindex + 3
tindices[13] = vindex + 1
tindices[14] = vindex + 5
tindices[15] = vindex + 3
tindices[16] = vindex + 5
tindices[17] = vindex + 7
tindices = tindices + 18
if self._close:
vindex = vcount - 4
i0 = vindex
i1 = vindex + 1
i2 = vindex + 2
i3 = vindex + 3
i4 = 0
i5 = 1
i6 = 2
i7 = 3
tindices[0] = i0
tindices[1] = i2
tindices[2] = i6
tindices[3] = i0
tindices[4] = i6
tindices[5] = i4
tindices[6] = i1
tindices[7] = i0
tindices[8] = i4
tindices[9] = i1
tindices[10] = i4
tindices[11] = i5
tindices[12] = i3
tindices[13] = i1
tindices[14] = i5
tindices[15] = i3
tindices[16] = i5
tindices[17] = i7
tindices = tindices + 18
#print 'tindices', <long>tindices, <long>indices, (<long>tindices - <long>indices) / sizeof(unsigned short)
self.batch.set_data(vertices, <int>vcount, indices, <int>icount)
free(vertices)
free(indices)
property overdraw_width:
'''Determine the overdraw width of the line, defaults to 1.2.
'''
def __get__(self):
return self._owidth
def __set__(self, value):
if value <= 0:
raise GraphicException('Invalid width value, must be > 0')
self._owidth = value
self.flag_update()