File: agg_path_collection.py

package info (click to toggle)
python-vispy 0.15.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 8,868 kB
  • sloc: python: 59,799; javascript: 6,800; makefile: 69; sh: 6
file content (197 lines) | stat: -rw-r--r-- 6,702 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) 2014, Nicolas P. Rougier
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
# -----------------------------------------------------------------------------
"""
Antigrain Geometry Path Collection

This collection provides antialiased and accurate paths with caps and joins. It
is memory hungry (x8) and slow (x.25) so it is to be used sparingly, mainly for
thick paths where quality is critical.
"""
import numpy as np
from ... import glsl
from ... import gloo
from . collection import Collection
from ..transforms import NullTransform


class AggPathCollection(Collection):
    """
    Antigrain Geometry Path Collection

    This collection provides antialiased and accurate paths with caps and
    joins. It is memory hungry (x8) and slow (x.25) so it is to be used
    sparingly, mainly for thick paths where quality is critical.
    """

    def __init__(self, user_dtype=None, transform=None,
                 vertex=None, fragment=None, **kwargs):
        """
        Initialize the collection.

        Parameters
        ----------
        user_dtype: list
            The base dtype can be completed (appended) by the used_dtype. It
            only make sense if user also provide vertex and/or fragment shaders

        transform : Transform instance
            Used to define the transform(vec4) function

        vertex: string
            Vertex shader code

        fragment: string
            Fragment  shader code

        caps : string
            'local', 'shared' or 'global'

        join : string
            'local', 'shared' or 'global'

        color : string
            'local', 'shared' or 'global'

        miter_limit : string
            'local', 'shared' or 'global'

        linewidth : string
            'local', 'shared' or 'global'

        antialias : string
            'local', 'shared' or 'global'
        """
        base_dtype = [('p0', (np.float32, 3), '!local', (0, 0, 0)),
                      ('p1', (np.float32, 3), '!local', (0, 0, 0)),
                      ('p2', (np.float32, 3), '!local', (0, 0, 0)),
                      ('p3', (np.float32, 3), '!local', (0, 0, 0)),
                      ('uv', (np.float32, 2), '!local', (0, 0)),

                      ('caps', (np.float32, 2), 'global', (0, 0)),
                      ('join', (np.float32, 1), 'global', 0),
                      ('color', (np.float32, 4), 'global', (0, 0, 0, 1)),
                      ('miter_limit', (np.float32, 1), 'global', 4),
                      ('linewidth', (np.float32, 1), 'global', 1),
                      ('antialias', (np.float32, 1), 'global', 1),
                      ('viewport', (np.float32, 4), 'global', (0, 0, 512, 512))]  # noqa

        dtype = base_dtype
        if user_dtype:
            dtype.extend(user_dtype)

        if vertex is None:
            vertex = glsl.get('collections/agg-path.vert')
        if transform is None:
            transform = NullTransform()
        self.transform = transform
        if fragment is None:
            fragment = glsl.get('collections/agg-path.frag')

        Collection.__init__(self, dtype=dtype, itype=np.uint32,  # 16 for WebGL
                            mode="triangles",
                            vertex=vertex, fragment=fragment, **kwargs)
        self._programs[0].vert['transform'] = self.transform

    def append(self, P, closed=False, itemsize=None, **kwargs):
        """
        Append a new set of vertices to the collection.

        For kwargs argument, n is the number of vertices (local) or the number
        of item (shared)

        Parameters
        ----------
        P : np.array
            Vertices positions of the path(s) to be added

        closed: bool
            Whether path(s) is/are closed

        itemsize: int or None
            Size of an individual path

        caps : list, array or 2-tuple
           Path start /end cap

        join : list, array or float
           path segment join

        color : list, array or 4-tuple
           Path color

        miter_limit : list, array or float
           Miter limit for join

        linewidth : list, array or float
           Path linewidth

        antialias : list, array or float
           Path antialias area
        """
        itemsize = int(itemsize or len(P))
        itemcount = len(P) // itemsize

        # Computes the adjacency information
        n, p = len(P), P.shape[-1]
        Z = np.tile(P, 2).reshape(2 * len(P), p)
        V = np.empty(n, dtype=self.vtype)

        V['p0'][1:-1] = Z[0::2][:-2]
        V['p1'][:-1] = Z[1::2][:-1]
        V['p2'][:-1] = Z[1::2][+1:]
        V['p3'][:-2] = Z[0::2][+2:]

        # Apply default values on vertices
        for name in self.vtype.names:
            if name not in ['collection_index', 'p0', 'p1', 'p2', 'p3']:
                V[name] = kwargs.get(name, self._defaults[name])

        # Extract relevant segments only
        V = (V.reshape(n // itemsize, itemsize)[:, :-1])
        if closed:
            V['p0'][:, 0] = V['p2'][:, -1]
            V['p3'][:, -1] = V['p1'][:, 0]
        else:
            V['p0'][:, 0] = V['p1'][:, 0]
            V['p3'][:, -1] = V['p2'][:, -1]
        V = V.ravel()

        # Quadruple each point (we're using 2 triangles / segment)
        # No shared vertices between segment because of joins
        V = np.repeat(V, 4, axis=0).reshape((len(V), 4))
        V['uv'] = (-1, -1), (-1, +1), (+1, -1), (+1, +1)
        V = V.ravel()

        n = itemsize
        if closed:
            # uint16 for WebGL
            idxs = np.resize(
                np.array([0, 1, 2, 1, 2, 3], dtype=np.uint32), n * 2 * 3)
            idxs += np.repeat(4 * np.arange(n, dtype=np.uint32), 6)
            idxs[-6:] = 4 * n - 6, 4 * n - 5, 0, 4 * n - 5, 0, 1
        else:
            idxs = np.resize(
                np.array([0, 1, 2, 1, 2, 3], dtype=np.uint32), (n - 1) * 2 * 3)
            idxs += np.repeat(4 * np.arange(n - 1, dtype=np.uint32), 6)
        idxs = idxs.ravel()

        # Uniforms
        if self.utype:
            U = np.zeros(itemcount, dtype=self.utype)
            for name in self.utype.names:
                if name not in ["__unused__"]:
                    U[name] = kwargs.get(name, self._defaults[name])
        else:
            U = None

        Collection.append(self, vertices=V, uniforms=U,
                          indices=idxs, itemsize=itemsize * 4 - 4)

    def draw(self, mode="triangles"):
        """Draw collection"""
        gloo.set_depth_mask(0)
        Collection.draw(self, mode)
        gloo.set_depth_mask(1)