Files
KPK/v3.12/Lib/site-packages/kivy/graphics/vertex_instructions_line.pxi
2026-06-23 15:20:56 +02:00

1785 lines
62 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.cache import Cache
from kivy.graphics.stencil_instructions cimport StencilUse, StencilUnUse, StencilPush, StencilPop
import itertools
# register graphics texture cache
Cache.register('kv.graphics.texture')
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 and :attr:`force_custom_drawing_method` is False, 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 or :attr:`force_custom_drawing_method`
is True, 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 length 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.
`force_custom_drawing_method`: bool, defaults to False
Should the custom drawing method be used, instead of it depending on :attr:`width`
being equal to 1.o or not.
.. 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
.. versionchanged:: 2.3.0
`force_custom_drawing_method` has 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 str _close_mode
cdef int _force_custom_drawing_method
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
cdef tuple _rounded_rectangle, _rectangle, _ellipse, _circle
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._close_mode = kwargs.get('close_mode', 'straight-line')
self._force_custom_drawing_method = int(bool(kwargs.get('force_custom_drawing_method', 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 and self._force_custom_drawing_method == 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. and self._force_custom_drawing_method == 0:
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 and self._close_mode == 'straight-line':
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 self._close_mode == 'straight-line' 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_data_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_data_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_data_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 length 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_data_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_data_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_data_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_data_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_data_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_data_update()
property close:
'''If True, the line will be closed by joining the two ends, according to :attr:`close_mode`.
.. versionadded:: 1.4.1
'''
def __get__(self):
return self._close
def __set__(self, value):
self._close = int(bool(value))
self.flag_data_update()
@property
def close_mode(self):
'''Defines how the ends of the line will be connected.
Defaults to ``"straight-line"``.
.. note::
Support for the different closing modes depends on drawing shapes.
Available modes:
- ``"straight-line"`` (all drawing shapes): the ends will be closed by a straight line.
- ``"center-connected"`` (:attr:`ellipse` specific): the ends will be closed by a line passing through the center of the ellipse.
.. versionadded:: 2.2.0
'''
return self._close_mode
@close_mode.setter
def close_mode(self, value):
if value not in ("straight-line", "center-connected"):
raise GraphicException(f'{self.__class__.__name__} - Invalid close_mode, must be one of "straight-line" or "center-connected".')
self._close_mode = value
self.flag_data_update()
property force_custom_drawing_method:
'''If True, the line will be drawn using the custom drawing method, no matter what the width is.
.. versionadded:: 2.3.0
'''
def __get__(self):
return self._force_custom_drawing_method
def __set__(self, value):
self._force_custom_drawing_method = int(bool(value))
self.flag_data_update()
property ellipse:
'''Use this property to build an ellipse, without calculating the
:attr:`points`.
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. You can use this
property to create polygons with 3 or more sides. Values smaller than
3 will not be represented and the number of segments will be
automatically calculated.
Note that it's up to you to :attr:`close` or not.
If you choose to close, use :attr:`close_mode` to define how the figure
will be closed. Whether it will be by closed by a ``"straight-line"``
or by ``"center-connected"``.
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
.. versionchanged:: 2.2.0
Now you can get the ellipse generated through the property.
The minimum number of segments allowed is 3. Smaller values will be
ignored and the number of segments will be automatically calculated.
'''
def __get__(self):
return self._ellipse
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_data_update()
cdef void prebuild_ellipse(self):
cdef double x, y, w, h, angle_start = 0, angle_end = 360
cdef int angle_dir, segments = 0, extra_segments = 0
cdef double angle_range
cdef tuple args = self._mode_args
cdef bint center_connected = self._close and self._close_mode == "center-connected"
extra_segments = 3 if center_connected else 1
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
else:
x = y = w = h = 0
assert 0
if 0 in (w, h):
return
if segments < 3:
if segments != 0:
Logger.warning(f'{self.__class__.__name__} - ellipse: A minimum of 3 segments is required. The default value will be used instead.')
segments = int(abs(angle_end - angle_start) / 2) + extra_segments
segments += extra_segments
segments *= 2
if angle_end > angle_start:
angle_dir = 1
else:
angle_dir = -1
# Resulting ellipse
self._ellipse = (x, y, w, h, angle_start, angle_end, segments)
# Reset other properties
self._rounded_rectangle = self._rectangle = self._circle = None
# 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 - extra_segments * 2)
cdef list points = [0, ] * segments
cdef double angle
cdef double rx = w * 0.5
cdef double ry = h * 0.5
cdef int inc_x = 0, inc_y = 1
if center_connected and angle_start != angle_end:
points[0] = points[segments - 2] = x + rx
points[1] = points[segments - 1] = y + ry
inc_x = 2
inc_y = 3
segments -= 4
for i in xrange(0, segments, 2):
angle = angle_start + (angle_dir * i * angle_range)
points[i + inc_x] = (x + rx) + (rx * sin(angle))
points[i + inc_y] = (y + ry) + (ry * cos(angle))
self._points = points
property circle:
'''Use this property to build a circle, without calculating the
:attr:`points`.
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
.. versionchanged:: 2.2.0
Now you can get the circle generated through the property.
'''
def __get__(self):
return self._circle
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_data_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
# Resulting circle
self._circle = (x, y, r, angle_start, angle_end, segments)
# Reset other properties
self._rounded_rectangle = self._rectangle = self._ellipse = None
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`.
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
.. versionchanged:: 2.2.0
Now you can get the rectangle generated through the property.
'''
def __get__(self):
return self._rectangle
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_data_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
# Resulting rectangle
self._rectangle = (x, y, width, height)
# Reset other properties
self._rounded_rectangle = self._circle = self._ellipse = None
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`.
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` specifies the radius used for the rounded corners clockwise: top-left, top-right, bottom-right, bottom-left.
* `resolution` is the number of line segment that will be used to draw the circle arc at each corner (defaults to 45).
The line is automatically closed.
Usage::
Line(rounded_rectangle=(0, 0, 200, 200, 10, 20, 30, 40, 100))
.. versionadded:: 1.9.0
.. versionchanged:: 2.2.0
Default value of `resolution` changed from 30 to 45.
Now you can get the rounded rectangle generated through the property.
The order of `corner_radius` has been changed to match the RoundedRectangle radius property (clockwise).
It was bottom-left, bottom-right, top-right, top-left in previous versions.
Now both are clockwise: top-left, top-right, bottom-right, bottom-left.
To keep the corner radius order without changing the order manually, you can use python's built-in method `reversed` or `[::-1]`,
to reverse the order of the corner radius.
'''
def __get__(self):
return self._rounded_rectangle
def __set__(self, args):
if args == None:
raise GraphicException(
'Invalid 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_data_update()
cdef void prebuild_rounded_rectangle(self):
cdef float a, max_a, px, py, x, y, w, h, c1, c2, c3, c4, step, min_dimension, half_min_dimension
cdef resolution = 45
cdef int l = <int>len(self._mode_args)
self._points = []
x, y, w, h = self._mode_args [:4]
# zero size of the figure + avoid rendering issue with SmoothLine
if w <= 0 or h <= 0 or isinstance(self, SmoothLine) and (w < 2 or h < 2):
return
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]
if resolution <= 4:
resolution = 4
# The minimum radius needs to be limited to 1px.
# This avoid some known rendering issues with Line/SmoothLine.
c1 = max(c1, 1.0)
c2 = max(c2, 1.0)
c3 = max(c3, 1.0)
c4 = max(c4, 1.0)
min_dimension = min(w, h)
half_min_dimension = min_dimension / 2.0
# If larger values are passed for each corner, we will have to make some adjustments.
if c1 > half_min_dimension:
c2 = min(c2, half_min_dimension)
c4 = min(c4, half_min_dimension)
c1 = min(c1, min_dimension - c2, min_dimension - c4)
if c2 > half_min_dimension:
c1 = min(c1, half_min_dimension)
c3 = min(c3, half_min_dimension)
c2 = min(c2, min_dimension - c1, min_dimension - c3)
if c3 > half_min_dimension:
c2 = min(c2, half_min_dimension)
c4 = min(c4, half_min_dimension)
c3 = min(c3, min_dimension - c2, min_dimension - c4)
if c4 > half_min_dimension:
c3 = min(c3, half_min_dimension)
c1 = min(c1, half_min_dimension)
c4 = min(c4, min_dimension - c3, min_dimension - c1)
# Resulting rounded_rectangle
self._rounded_rectangle = (x, y, w, h, c1, c2, c3, c4, resolution)
# Reset other properties
self._rectangle = self._ellipse = self._circle = None
step = PI / resolution
max_a = PI / 2.0 - step
# top-left
a = 0.0
px = x + c1
py = y + h - c1
while a < max_a:
a += step
self._points.extend([
px - cos(a) * c1,
py + sin(a) * c1
])
# top-right
a = 0.0
px = x + w - c2
py = y + h - c2
while a < max_a:
a += step
self._points.extend([
px + sin(a) * c2,
py + cos(a) * c2
])
# bottom-right
a = 0.0
px = x + w - c3
py = y + c3
while a < max_a:
a += step
self._points.extend([
px + cos(a) * c3,
py - sin(a) * c3
])
# bottom-left
a = 0.0
px = x + c4
py = y + c4
while a < max_a:
a += step
self._points.extend([
px - sin(a) * c4,
py - cos(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_data_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_data_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 = Cache.get('kv.graphics.texture', 'smoothline')
if not texture:
texture = Texture.create(size=(4, 1), colorfmt="rgba")
texture.add_reload_observer(self._smooth_reload_observer)
self._smooth_reload_observer(texture)
Cache.append('kv.graphics.texture', 'smoothline', 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
# FIXME: Some artifacts can be observed, depending on the line width,
# overdraw_width and radius. This occurs due to the way the vertices are
# built. It is not noticeable when the alpha value of the color of the
# active context is equal to 1. One solution would be to avoid overlapping
# vertices.
cdef void build_smooth(self):
cdef:
list p = self.points
int must_close_line = self._close
double width = max(0, (self._width - 1.))
double owidth = width + self._owidth
vertex_t *vertices = NULL
unsigned short *indices = NULL
unsigned short *tindices = NULL
float min_distance_threshold = 0.1
double min_angle_threshold = 0.017453292519943295 # 1 degree in radians, determined empirically.
double ax, ay, bx = 0., by = 0., rx = 0., ry = 0., last_angle = 0., angle, av_angle, ad_angle, angle_diff
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
# Points that are very close (with a distance less than 0.1) will
# be discarded. This increases the reliability of line rendering.
p = self._remove_too_nearby_points(p, min_distance_threshold)
# If it is just a line segment, there will be no support to close the line.
if len(p) <= 4:
must_close_line = False
# A new point needs to meet a minimum distance threshold before being added.
if must_close_line and not (abs(p[-2] - p[0]) < min_distance_threshold and abs(p[-1] - p[1]) < min_distance_threshold):
p = p + p[:2]
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
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 must_close_line and self._close_mode == 'straight-line':
ax = p[-4]
ay = p[-3]
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)
elif must_close_line and index == max_index - 2:
ax = p[0]
ay = p[1]
bx = p[2]
by = p[3]
rx = bx - ax
ry = by - ay
angle = atan2(ry, rx)
else:
angle = last_angle
if index == 0 and (not must_close_line or self._close_mode != 'straight-line'):
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
if (index == 0 or index >= max_index - 2) and (not must_close_line or self._close_mode != 'straight-line'):
l = width
ol = owidth
else:
if index == 0:
ox = p[- 4]
oy = p[- 3]
else:
ox = p[index - 2]
oy = p[index - 1]
la1 = last_angle - PI2
la2 = angle - PI2
ra1 = last_angle + PI2
ra2 = angle + PI2
angle_diff = self._get_angle_diff(
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,
)
# If the angle difference is too small it is not safe to use
# the calculated values of l or l. Otherwise, l and ol will
# have extremely high values, causing the line to become
# excessively thick at some intersections (in specific cases).
if angle_diff < min_angle_threshold or ad_angle < min_angle_threshold:
l = width
ol = owidth
else:
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
cos1 = cos(a1) * l
sin1 = sin(a1) * l
cos2 = cos(a2) * l
sin2 = sin(a2) * l
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
# 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)
cdef list _remove_too_nearby_points(self, list p, float min_distance_threshold):
cdef int index = 0
cdef double x1, y1, x2, y2
while index < len(p) - 2:
x1, y1 = p[index], p[index + 1]
x2, y2 = p[index + 2], p[index + 3]
if abs(x2 - x1) < min_distance_threshold and abs(y2 - y1) < min_distance_threshold:
del p[index + 2: index + 4]
else:
index += 2
return p
cdef double _get_angle_diff(self, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4):
cdef list vector_1, vector_2
vector_1 = [x2 - x1, y2 - y1]
vector_2 = [x4 - x3, y4 - y3]
angle_1 = atan2(vector_1[1], vector_1[0])
angle_2 = atan2(vector_2[1], vector_2[0])
angle_diff = abs(angle_1 - angle_2)
if angle_diff > pi:
angle_diff = 2 * pi - angle_diff
return angle_diff
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_data_update()