~ubuntu-branches/ubuntu/trusty/python-enable/trusty

« back to all changes in this revision

Viewing changes to enthought/kiva/backend_gl.py

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-04-05 21:54:28 UTC
  • mfrom: (1.1.5 upstream)
  • mto: (8.2.1 sid)
  • mto: This revision was merged to the branch mainline in revision 10.
  • Revision ID: james.westby@ubuntu.com-20110405215428-1x2wtubz3ok2kxaq
Tags: upstream-3.4.1
ImportĀ upstreamĀ versionĀ 3.4.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
 
2
 
# Major library imports
3
 
import ctypes
4
 
from math import floor
5
 
from numpy import array, ndarray
6
 
 
7
 
# Pyglet and pyglet-related imports
8
 
# Before we import anything else from pyglet, we need to set the shadow_window
9
 
# option to False, so that it does not conflict with WX, in case someone is
10
 
# trying to use the kiva GL GraphicsContext from within WX.
11
 
# This is necessary as of pyglet 1.1.
12
 
import pyglet
13
 
pyglet.options['shadow_window'] = False
14
 
 
15
 
#from pyglet.font import Text
16
 
from pyglet.text import Label
17
 
from pyglet.font import load as load_font
18
 
from pyglet.font.base import Font as PygletFont
19
 
from pyglet.graphics import Batch
20
 
from pyglet import gl
21
 
from pygarrayimage.arrayimage import ArrayInterfaceImage
22
 
 
23
 
# Local kiva imports
24
 
from affine import affine_from_values, transform_points
25
 
from agg import GraphicsContextGL as _GCL
26
 
from agg import GraphicsContextArray
27
 
from agg import AggFontType
28
 
from agg import Image
29
 
from agg import CompiledPath
30
 
from constants import BOLD, BOLD_ITALIC, ITALIC
31
 
from fonttools import Font
32
 
 
33
 
 
34
 
class ArrayImage(ArrayInterfaceImage):
35
 
    """ pyglet ImageData made from numpy arrays.
36
 
 
37
 
    Customized from pygarrayimage's ArrayInterfaceImage to override the texture
38
 
    creation.
39
 
    """
40
 
 
41
 
    def create_texture(self, cls):
42
 
        '''Create a texture containing this image.
43
 
 
44
 
        If the image's dimensions are not powers of 2, a TextureRegion of
45
 
        a larger Texture will be returned that matches the dimensions of this
46
 
        image.
47
 
 
48
 
        :Parameters:
49
 
            `cls` : class (subclass of Texture)
50
 
                Class to construct.
51
 
 
52
 
        :rtype: cls or cls.region_class
53
 
        '''
54
 
 
55
 
        texture = cls.create_for_size(
56
 
            gl.GL_TEXTURE_2D, self.width, self.height)
57
 
        subimage = False
58
 
        if texture.width != self.width or texture.height != self.height:
59
 
            texture = texture.get_region(0, 0, self.width, self.height)
60
 
            subimage = True
61
 
 
62
 
        internalformat = self._get_internalformat(self.format)
63
 
 
64
 
        gl.glBindTexture(texture.target, texture.id)
65
 
        gl.glTexParameteri(texture.target, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
66
 
        gl.glTexParameteri(texture.target, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
67
 
        gl.glTexParameteri(texture.target, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
68
 
        gl.glTexParameteri(texture.target, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
69
 
 
70
 
        if subimage:
71
 
            width = texture.owner.width
72
 
            height = texture.owner.height
73
 
            blank = (ctypes.c_ubyte * (width * height * 4))()
74
 
            gl.glTexImage2D(texture.target, texture.level,
75
 
                         internalformat,
76
 
                         width, height,
77
 
                         1,
78
 
                         gl.GL_RGBA, gl.GL_UNSIGNED_BYTE,
79
 
                         blank) 
80
 
            internalformat = None
81
 
 
82
 
        self.blit_to_texture(texture.target, texture.level, 
83
 
            0, 0, 0, internalformat)
84
 
        
85
 
        return texture 
86
 
 
87
 
    def blit_to_texture(self, target, level, x, y, z, internalformat=None):
88
 
        '''Draw this image to to the currently bound texture at `target`.
89
 
 
90
 
        If `internalformat` is specified, glTexImage is used to initialise
91
 
        the texture; otherwise, glTexSubImage is used to update a region.
92
 
        '''
93
 
 
94
 
        data_format = self.format
95
 
        data_pitch = abs(self._current_pitch)
96
 
 
97
 
        # Determine pixel format from format string
98
 
        matrix = None
99
 
        format, type = self._get_gl_format_and_type(data_format)
100
 
        if format is None:
101
 
            if (len(data_format) in (3, 4) and 
102
 
                gl.gl_info.have_extension('GL_ARB_imaging')):
103
 
                # Construct a color matrix to convert to GL_RGBA
104
 
                def component_column(component):
105
 
                    try:
106
 
                        pos = 'RGBA'.index(component)
107
 
                        return [0] * pos + [1] + [0] * (3 - pos)
108
 
                    except ValueError:
109
 
                        return [0, 0, 0, 0]
110
 
                # pad to avoid index exceptions
111
 
                lookup_format = data_format + 'XXX'
112
 
                matrix = (component_column(lookup_format[0]) +
113
 
                          component_column(lookup_format[1]) +
114
 
                          component_column(lookup_format[2]) + 
115
 
                          component_column(lookup_format[3]))
116
 
                format = {
117
 
                    3: gl.GL_RGB,
118
 
                    4: gl.GL_RGBA}.get(len(data_format))
119
 
                type = gl.GL_UNSIGNED_BYTE
120
 
 
121
 
                gl.glMatrixMode(gl.GL_COLOR)
122
 
                gl.glPushMatrix()
123
 
                gl.glLoadMatrixf((gl.GLfloat * 16)(*matrix))
124
 
            else:
125
 
                # Need to convert data to a standard form
126
 
                data_format = {
127
 
                    1: 'L',
128
 
                    2: 'LA',
129
 
                    3: 'RGB',
130
 
                    4: 'RGBA'}.get(len(data_format))
131
 
                format, type = self._get_gl_format_and_type(data_format)
132
 
 
133
 
        # Workaround: don't use GL_UNPACK_ROW_LENGTH
134
 
        if gl._current_context._workaround_unpack_row_length:
135
 
            data_pitch = self.width * len(data_format)
136
 
 
137
 
        # Get data in required format (hopefully will be the same format it's
138
 
        # already in, unless that's an obscure format, upside-down or the
139
 
        # driver is old).
140
 
        data = self._convert(data_format, data_pitch)
141
 
 
142
 
        if data_pitch & 0x1:
143
 
            alignment = 1
144
 
        elif data_pitch & 0x2:
145
 
            alignment = 2
146
 
        else:
147
 
            alignment = 4
148
 
        row_length = data_pitch / len(data_format)
149
 
        gl.glPushClientAttrib(gl.GL_CLIENT_PIXEL_STORE_BIT)
150
 
        gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, alignment)
151
 
        gl.glPixelStorei(gl.GL_UNPACK_ROW_LENGTH, row_length)
152
 
        self._apply_region_unpack()
153
 
        gl.glTexParameteri(target, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
154
 
        gl.glTexParameteri(target, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
155
 
        gl.glTexParameteri(target, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
156
 
        gl.glTexParameteri(target, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
157
 
        
158
 
 
159
 
        if target == gl.GL_TEXTURE_3D:
160
 
            assert not internalformat
161
 
            gl.glTexSubImage3D(target, level,
162
 
                            x, y, z,
163
 
                            self.width, self.height, 1,
164
 
                            format, type,
165
 
                            data)
166
 
        elif internalformat:
167
 
            gl.glTexImage2D(target, level,
168
 
                         internalformat,
169
 
                         self.width, self.height,
170
 
                         0,
171
 
                         format, type,
172
 
                         data)
173
 
        else:
174
 
            gl.glTexSubImage2D(target, level,
175
 
                            x, y,
176
 
                            self.width, self.height,
177
 
                            format, type,
178
 
                            data)
179
 
        gl.glPopClientAttrib()
180
 
 
181
 
        if matrix:
182
 
            gl.glPopMatrix()
183
 
            gl.glMatrixMode(gl.GL_MODELVIEW)
184
 
    
185
 
 
186
 
def image_as_array(img):
187
 
    """ Adapt an image object into a numpy array.
188
 
 
189
 
    Typically, this is used to adapt an agg GraphicsContextArray which has been
190
 
    used for image storage in Kiva applications.
191
 
    """
192
 
    if hasattr(img, 'bmp_array'):
193
 
        # Yup, a GraphicsContextArray.
194
 
        return img.bmp_array
195
 
    elif isinstance(img, ndarray):
196
 
        return img
197
 
    else:
198
 
        raise NotImplementedError("can't convert %r into a numpy array" % (img,))
199
 
 
200
 
def get_dpi():
201
 
    """ Returns the appropriate DPI setting for the system"""
202
 
    pass
203
 
 
204
 
class MRU(dict):
205
 
    def __init__(self, *args, **kw):
206
 
        # An ordering of keys in self; the last item was the most recently used
207
 
        self.__order__ = []
208
 
        self.__maxlength__ = 30
209
 
        dict.__init__(self, *args, **kw)
210
 
 
211
 
    def __getitem__(self, key):
212
 
        val = dict.__getitem__(self, key)
213
 
        # If we get here, then key was found in our dict
214
 
        self.__touch__(key)
215
 
        return val
216
 
 
217
 
    def __setitem__(self, key, value):
218
 
        dict.__setitem__(self, key, value)
219
 
        self.__touch__(key)
220
 
 
221
 
    def __delitem__(self, key):
222
 
        dict.__delitem__(self, key)
223
 
        if key in self.__order__:
224
 
            self.__order__.remove(key)
225
 
 
226
 
    def __touch__(self, key):
227
 
        """ Puts **key** as the most recently used item """
228
 
        if len(self.__order__) == 0:
229
 
            self.__order__.append(key)
230
 
        if (len(self.__order__) == self.__maxlength__) and key not in self.__order__:
231
 
            # The MRU is full, so pop the oldest element
232
 
            del self[self.__order__[0]]
233
 
        if key != self.__order__[-1]:
234
 
            try:
235
 
                ndx = self.__order__.index(key)
236
 
                self.__order__[ndx:-1] = self.__order__[ndx+1:]
237
 
                self.__order__[-1] = key
238
 
            except ValueError:
239
 
                # new key that's not already in the cache
240
 
                if len(self.__order__) == self.__maxlength__:
241
 
                    self.__order__ = self.__order__[1:] + [key]
242
 
                else:
243
 
                    self.__order__.append(key)
244
 
        return
245
 
 
246
 
# Use a singleton for the font cache
247
 
GlobalFontCache = MRU()
248
 
def GetFont(font):
249
 
    """ Returns a Pylget Font object for the given Agg or Kiva font """
250
 
    if isinstance(font, PygletFont):
251
 
        pyglet_font = font
252
 
    else:
253
 
        # AggFontType
254
 
        key = (font.name, font.size, font.family, font.style)
255
 
        if key not in GlobalFontCache:
256
 
            if isinstance(font, AggFontType):
257
 
                agg_font = font
258
 
                font = Font(face_name = agg_font.name,
259
 
                            size = agg_font.size,
260
 
                            family = agg_font.family,
261
 
                            style = agg_font.style)
262
 
            bold = False
263
 
            italic = False
264
 
            if font.style == BOLD or font.style == BOLD_ITALIC or font.weight == BOLD:
265
 
                bold = True
266
 
            if font.style == ITALIC or font.style == BOLD_ITALIC:
267
 
                italic = True
268
 
            pyglet_font = load_font(font.findfontname(), font.size, bold, italic)
269
 
            GlobalFontCache[key] = pyglet_font
270
 
        else:
271
 
            pyglet_font = GlobalFontCache[key]
272
 
    return pyglet_font
273
 
 
274
 
 
275
 
# Because Pyglet 1.1 uses persistent Label objects to efficiently lay
276
 
# out and render text, we cache these globally to minimize the creation
277
 
# time.  An MRU is not really the right structure to use here, though.
278
 
# (We typically expect that the same numbers of labels will be rendered.)
279
 
GlobalTextCache = MRU()
280
 
GlobalTextCache.__maxlength__ = 100
281
 
def GetLabel(text, pyglet_font):
282
 
    """ Returns a Pyglet Label object for the given text and font """
283
 
    key = (text, pyglet_font)
284
 
    if key not in GlobalTextCache:
285
 
        # Use anchor_y="bottom" because by default, pyglet sets the baseline to
286
 
        # the y coordinate given.  Unfortunately, it doesn't expose a per-Text
287
 
        # descent (only a per-Font descent), so it's impossible to know how to
288
 
        # offset the y value properly for a given string.
289
 
        label = Label(text, font_name=pyglet_font.name, font_size=pyglet_font.size,
290
 
                      anchor_y="bottom")
291
 
        GlobalTextCache[key] = label
292
 
    else:
293
 
        label = GlobalTextCache[key]
294
 
    return label
295
 
 
296
 
 
297
 
class GraphicsContext(_GCL):
298
 
    def __init__(self, size, *args, **kw):
299
 
        # Ignore the pix_format argument for now
300
 
        if "pix_format" in kw:
301
 
            kw.pop("pix_format")
302
 
        _GCL.__init__(self, size[0], size[1], *args, **kw)
303
 
        self.corner_pixel_origin = True
304
 
 
305
 
        self._font_stack = []
306
 
        self._current_font = None
307
 
 
308
 
    def save_state(self):
309
 
        super(GraphicsContext, self).save_state()
310
 
        self._font_stack.append(self._current_font)
311
 
 
312
 
    def restore_state(self):
313
 
        super(GraphicsContext, self).restore_state()
314
 
        self._current_font = self._font_stack.pop()
315
 
 
316
 
    def set_font(self, font):
317
 
        super(GraphicsContext, self).set_font(font)
318
 
        self._current_font = font
319
 
 
320
 
    def get_text_extent(self, text):
321
 
        if self._current_font is None:
322
 
            return (0, 0, 0, 0)
323
 
 
324
 
        pyglet_font = GetFont(self._current_font)
325
 
        label = GetLabel(text, pyglet_font)
326
 
        return (0, 0, label.content_width, label.content_height)
327
 
 
328
 
    def show_text(self, text, point = None):
329
 
        if point is None:
330
 
            point = (0,0)
331
 
        return self.show_text_at_point(text, *point)
332
 
 
333
 
    def show_text_at_point(self, text, x, y):
334
 
        if self._current_font is None:
335
 
            return
336
 
 
337
 
        pyglet_font = GetFont(self._current_font)
338
 
        label = GetLabel(text, pyglet_font)
339
 
 
340
 
        xform = self.get_ctm()
341
 
        x0 = xform[4]
342
 
        y0 = xform[5]
343
 
 
344
 
        # The GL backend places the center of a pixel at (0.5, 0.5); however, 
345
 
        # for show_text_at_point, we don't actually want to render the text
346
 
        # offset by half a pixel.  There is probably a better, more uniform way
347
 
        # to handle this across all of Kiva, because this is probably a common
348
 
        # issue that will arise, but for now, we just round the position down.
349
 
        x = floor(x + x0)
350
 
        y = floor(y + y0)
351
 
        
352
 
        label.x = x
353
 
        label.y = y
354
 
        c = self.get_fill_color()
355
 
        label.color = (int(c[0]*255), int(c[1]*255), int(c[2]*255), int(c[3]*255))
356
 
        label.draw()
357
 
        return True
358
 
 
359
 
    def draw_image(self, img, rect=None, force_copy=False):
360
 
        """ Renders a GraphicsContextArray into this GC """
361
 
        xform = self.get_ctm()
362
 
        x0 = xform[4]
363
 
        y0 = xform[5]
364
 
 
365
 
        image = image_as_array(img)
366
 
        shape = image.shape
367
 
        if shape[2] == 4:
368
 
            fmt = "RGBA"
369
 
        else:
370
 
            fmt = "RGB"
371
 
        aii = ArrayImage(image, format=fmt)
372
 
        texture = aii.texture
373
 
 
374
 
        # The texture coords consists of (u,v,r) for each corner of the
375
 
        # texture rectangle.  The coordinates are stored in the order
376
 
        # bottom left, bottom right, top right, top left.
377
 
        x, y, w, h = rect
378
 
        texture.width = w
379
 
        texture.height = h
380
 
        t = texture.tex_coords
381
 
        points = array([
382
 
            [x,   y+h],
383
 
            [x+w, y+h],
384
 
            [x+w, y],
385
 
            [x,   y],
386
 
        ])
387
 
        p = transform_points(affine_from_values(*xform), points)
388
 
        a = (gl.GLfloat*32)(
389
 
            t[0],   t[1],   t[2],  1.,
390
 
            p[0,0], p[0,1], 0,     1.,
391
 
            t[3],   t[4],   t[5],  1.,
392
 
            p[1,0], p[1,1], 0,     1.,
393
 
            t[6],   t[7],   t[8],  1.,
394
 
            p[2,0], p[2,1], 0,     1.,
395
 
            t[9],   t[10],  t[11], 1.,
396
 
            p[3,0], p[3,1], 0,     1.,
397
 
        )
398
 
        gl.glPushAttrib(gl.GL_ENABLE_BIT)
399
 
        gl.glEnable(texture.target)
400
 
        gl.glBindTexture(texture.target, texture.id)
401
 
        gl.glPushClientAttrib(gl.GL_CLIENT_VERTEX_ARRAY_BIT)
402
 
        gl.glInterleavedArrays(gl.GL_T4F_V4F, 0, a)
403
 
        gl.glDrawArrays(gl.GL_QUADS, 0, 4)
404
 
        gl.glPopClientAttrib()
405
 
        gl.glPopAttrib()
406
 
 
407
 
def font_metrics_provider():
408
 
    return GraphicsContext((1,1))
409
 
 
410
 
Canvas = None
411
 
CanvasWindow = None