1# ----------------------------------------------------------------------------
2# pyglet
3# Copyright (c) 2006-2008 Alex Holkner
4# Copyright (c) 2008-2021 pyglet contributors
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10#
11#  * Redistributions of source code must retain the above copyright
12#    notice, this list of conditions and the following disclaimer.
13#  * Redistributions in binary form must reproduce the above copyright
14#    notice, this list of conditions and the following disclaimer in
15#    the documentation and/or other materials provided with the
16#    distribution.
17#  * Neither the name of pyglet nor the names of its
18#    contributors may be used to endorse or promote products
19#    derived from this software without specific prior written
20#    permission.
21#
22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33# POSSIBILITY OF SUCH DAMAGE.
34# ----------------------------------------------------------------------------
35
36"""Image load, capture and high-level texture functions.
37
38Only basic functionality is described here; for full reference see the
39accompanying documentation.
40
41To load an image::
42
43    from pyglet import image
44    pic = image.load('picture.png')
45
46The supported image file types include PNG, BMP, GIF, JPG, and many more,
47somewhat depending on the operating system.  To load an image from a file-like
48object instead of a filename::
49
50    pic = image.load('hint.jpg', file=fileobj)
51
52The hint helps the module locate an appropriate decoder to use based on the
53file extension.  It is optional.
54
55Once loaded, images can be used directly by most other modules of pyglet.  All
56images have a width and height you can access::
57
58    width, height = pic.width, pic.height
59
60You can extract a region of an image (this keeps the original image intact;
61the memory is shared efficiently)::
62
63    subimage = pic.get_region(x, y, width, height)
64
65Remember that y-coordinates are always increasing upwards.
66
67Drawing images
68--------------
69
70To draw an image at some point on the screen::
71
72    pic.blit(x, y, z)
73
74This assumes an appropriate view transform and projection have been applied.
75
76Some images have an intrinsic "anchor point": this is the point which will be
77aligned to the ``x`` and ``y`` coordinates when the image is drawn.  By
78default the anchor point is the lower-left corner of the image.  You can use
79the anchor point to center an image at a given point, for example::
80
81    pic.anchor_x = pic.width // 2
82    pic.anchor_y = pic.height // 2
83    pic.blit(x, y, z)
84
85Texture access
86--------------
87
88If you are using OpenGL directly, you can access the image as a texture::
89
90    texture = pic.get_texture()
91
92(This is the most efficient way to obtain a texture; some images are
93immediately loaded as textures, whereas others go through an intermediate
94form).  To use a texture with pyglet.gl::
95
96    from pyglet.gl import *
97    glEnable(texture.target)        # typically target is GL_TEXTURE_2D
98    glBindTexture(texture.target, texture.id)
99    # ... draw with the texture
100
101Pixel access
102------------
103
104To access raw pixel data of an image::
105
106    rawimage = pic.get_image_data()
107
108(If the image has just been loaded this will be a very quick operation;
109however if the image is a texture a relatively expensive readback operation
110will occur).  The pixels can be accessed as a string::
111
112    format = 'RGBA'
113    pitch = rawimage.width * len(format)
114    pixels = rawimage.get_data(format, pitch)
115
116"format" strings consist of characters that give the byte order of each color
117component.  For example, if rawimage.format is 'RGBA', there are four color
118components: red, green, blue and alpha, in that order.  Other common format
119strings are 'RGB', 'LA' (luminance, alpha) and 'I' (intensity).
120
121The "pitch" of an image is the number of bytes in a row (this may validly be
122more than the number required to make up the width of the image, it is common
123to see this for word alignment).  If "pitch" is negative the rows of the image
124are ordered from top to bottom, otherwise they are ordered from bottom to top.
125
126Retrieving data with the format and pitch given in `ImageData.format` and
127`ImageData.pitch` avoids the need for data conversion (assuming you can make
128use of the data in this arbitrary format).
129
130"""
131import re
132import weakref
133
134from ctypes import *
135from io import open, BytesIO
136from functools import lru_cache
137
138import pyglet
139
140from pyglet.gl import *
141from pyglet.gl import gl_info
142from pyglet.util import asbytes
143
144from .codecs import ImageEncodeException, ImageDecodeException
145from .codecs import add_default_image_codecs, add_decoders, add_encoders
146from .codecs import get_animation_decoders, get_decoders, get_encoders
147from .animation import Animation, AnimationFrame
148from . import atlas
149
150
151class ImageException(Exception):
152    pass
153
154
155def load(filename, file=None, decoder=None):
156    """Load an image from a file.
157
158    :note: You can make no assumptions about the return type; usually it will
159        be ImageData or CompressedImageData, but decoders are free to return
160        any subclass of AbstractImage.
161
162    :Parameters:
163        `filename` : str
164            Used to guess the image format, and to load the file if `file` is
165            unspecified.
166        `file` : file-like object or None
167            Source of image data in any supported format.
168        `decoder` : ImageDecoder or None
169            If unspecified, all decoders that are registered for the filename
170            extension are tried.  If none succeed, the exception from the
171            first decoder is raised.
172
173    :rtype: AbstractImage
174    """
175
176    if not file:
177        file = open(filename, 'rb')
178        opened_file = file
179    else:
180        opened_file = None
181
182    if not hasattr(file, 'seek'):
183        file = BytesIO(file.read())
184
185    try:
186        if decoder:
187            return decoder.decode(file, filename)
188        else:
189            first_exception = None
190            for decoder in get_decoders(filename):
191                try:
192                    image = decoder.decode(file, filename)
193                    return image
194                except ImageDecodeException as e:
195                    if (not first_exception or
196                            first_exception.exception_priority < e.exception_priority):
197                        first_exception = e
198                    file.seek(0)
199
200            if not first_exception:
201                raise ImageDecodeException('No image decoders are available')
202            raise first_exception
203    finally:
204        if opened_file:
205            opened_file.close()
206
207
208def load_animation(filename, file=None, decoder=None):
209    """Load an animation from a file.
210
211    Currently, the only supported format is GIF.
212
213    :Parameters:
214        `filename` : str
215            Used to guess the animation format, and to load the file if `file`
216            is unspecified.
217        `file` : file-like object or None
218            File object containing the animation stream.
219        `decoder` : ImageDecoder or None
220            If unspecified, all decoders that are registered for the filename
221            extension are tried.  If none succeed, the exception from the
222            first decoder is raised.
223
224    :rtype: Animation
225    """
226    if not file:
227        file = open(filename, 'rb')
228    if not hasattr(file, 'seek'):
229        file = BytesIO(file.read())
230
231    if decoder:
232        return decoder.decode_animation(file, filename)
233    else:
234        first_exception = None
235        for decoder in get_animation_decoders(filename):
236            try:
237                image = decoder.decode_animation(file, filename)
238                return image
239            except ImageDecodeException as e:
240                first_exception = first_exception or e
241                file.seek(0)
242
243        if not first_exception:
244            raise ImageDecodeException('No image decoders are available')
245        raise first_exception
246
247
248def create(width, height, pattern=None):
249    """Create an image optionally filled with the given pattern.
250
251    :note: You can make no assumptions about the return type; usually it will
252        be ImageData or CompressedImageData, but patterns are free to return
253        any subclass of AbstractImage.
254
255    :Parameters:
256        `width` : int
257            Width of image to create
258        `height` : int
259            Height of image to create
260        `pattern` : ImagePattern or None
261            Pattern to fill image with.  If unspecified, the image will
262            initially be transparent.
263
264    :rtype: AbstractImage
265    """
266    if not pattern:
267        pattern = SolidColorImagePattern()
268    return pattern.create_image(width, height)
269
270
271@lru_cache()
272def get_max_texture_size():
273    """Query the maximum texture size available"""
274    size = c_int()
275    glGetIntegerv(GL_MAX_TEXTURE_SIZE, size)
276    return size.value
277
278
279@lru_cache()
280def _color_as_bytes(color):
281    if len(color) != 4:
282        raise TypeError("color is expected to have 4 components")
283    return bytes(color)
284
285
286@lru_cache()
287def _nearest_pow2(v):
288    # From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
289    # Credit: Sean Anderson
290    v -= 1
291    v |= v >> 1
292    v |= v >> 2
293    v |= v >> 4
294    v |= v >> 8
295    v |= v >> 16
296    return v + 1
297
298
299@lru_cache()
300def _is_pow2(v):
301    # http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
302    return (v & (v - 1)) == 0
303
304
305class ImagePattern:
306    """Abstract image creation class."""
307
308    def create_image(self, width, height):
309        """Create an image of the given size.
310
311        :Parameters:
312            `width` : int
313                Width of image to create
314            `height` : int
315                Height of image to create
316
317        :rtype: AbstractImage
318        """
319        raise NotImplementedError('abstract')
320
321
322class SolidColorImagePattern(ImagePattern):
323    """Creates an image filled with a solid color."""
324
325    def __init__(self, color=(0, 0, 0, 0)):
326        """Create a solid image pattern with the given color.
327
328        :Parameters:
329            `color` : (int, int, int, int)
330                4-tuple of ints in range [0,255] giving RGBA components of
331                color to fill with.
332
333        """
334        self.color = _color_as_bytes(color)
335
336    def create_image(self, width, height):
337        data = self.color * width * height
338        return ImageData(width, height, 'RGBA', data)
339
340
341class CheckerImagePattern(ImagePattern):
342    """Create an image with a tileable checker image.
343    """
344
345    def __init__(self, color1=(150, 150, 150, 255), color2=(200, 200, 200, 255)):
346        """Initialise with the given colors.
347
348        :Parameters:
349            `color1` : (int, int, int, int)
350                4-tuple of ints in range [0,255] giving RGBA components of
351                color to fill with.  This color appears in the top-left and
352                bottom-right corners of the image.
353            `color2` : (int, int, int, int)
354                4-tuple of ints in range [0,255] giving RGBA components of
355                color to fill with.  This color appears in the top-right and
356                bottom-left corners of the image.
357
358        """
359        self.color1 = _color_as_bytes(color1)
360        self.color2 = _color_as_bytes(color2)
361
362    def create_image(self, width, height):
363        hw = width // 2
364        hh = height // 2
365        row1 = self.color1 * hw + self.color2 * hw
366        row2 = self.color2 * hw + self.color1 * hw
367        data = row1 * hh + row2 * hh
368        return ImageData(width, height, 'RGBA', data)
369
370
371class AbstractImage:
372    """Abstract class representing an image.
373
374    :Parameters:
375        `width` : int
376            Width of image
377        `height` : int
378            Height of image
379        `anchor_x` : int
380            X coordinate of anchor, relative to left edge of image data
381        `anchor_y` : int
382            Y coordinate of anchor, relative to bottom edge of image data
383    """
384    anchor_x = 0
385    anchor_y = 0
386
387    _is_rectangle = False
388
389    def __init__(self, width, height):
390        self.width = width
391        self.height = height
392
393    def __repr__(self):
394        return '<%s %dx%d>' % (self.__class__.__name__, self.width, self.height)
395
396    def get_image_data(self):
397        """Get an ImageData view of this image.
398
399        Changes to the returned instance may or may not be reflected in this
400        image.
401
402        :rtype: :py:class:`~pyglet.image.ImageData`
403
404        .. versionadded:: 1.1
405        """
406        raise ImageException('Cannot retrieve image data for %r' % self)
407
408    def get_texture(self, rectangle=False, force_rectangle=False):
409        """A :py:class:`~pyglet.image.Texture` view of this image.
410
411        By default, textures are created with dimensions that are powers of
412        two.  Smaller images will return a :py:class:`~pyglet.image.TextureRegion` that covers just
413        the image portion of the larger texture.  This restriction is required
414        on older video cards, and for compressed textures, or where texture
415        repeat modes will be used, or where mipmapping is desired.
416
417        If the `rectangle` parameter is ``True``, this restriction is ignored
418        and a texture the size of the image may be created if the driver
419        supports the ``GL_ARB_texture_rectangle`` or
420        ``GL_NV_texture_rectangle`` extensions.  If the extensions are not
421        present, the image already is a texture, or the image has power 2
422        dimensions, the `rectangle` parameter is ignored.
423
424        Examine `Texture.target` to determine if the returned texture is a
425        rectangle (``GL_TEXTURE_RECTANGLE_ARB`` or
426        ``GL_TEXTURE_RECTANGLE_NV``) or not (``GL_TEXTURE_2D``).
427
428        If the `force_rectangle` parameter is ``True``, one of these
429        extensions must be present, and the returned texture always
430        has target ``GL_TEXTURE_RECTANGLE_ARB`` or ``GL_TEXTURE_RECTANGLE_NV``.
431
432        Changes to the returned instance may or may not be reflected in this
433        image.
434
435        :Parameters:
436            `rectangle` : bool
437                True if the texture can be created as a rectangle.
438            `force_rectangle` : bool
439                True if the texture must be created as a rectangle.
440
441                .. versionadded:: 1.1.4.
442        :rtype: :py:class:`~pyglet.image.Texture`
443
444        .. versionadded:: 1.1
445        """
446        raise ImageException('Cannot retrieve texture for %r' % self)
447
448    def get_mipmapped_texture(self):
449        """Retrieve a :py:class:`~pyglet.image.Texture` instance with all mipmap levels filled in.
450
451        Requires that image dimensions be powers of 2.
452
453        :rtype: :py:class:`~pyglet.image.Texture`
454
455        .. versionadded:: 1.1
456        """
457        raise ImageException('Cannot retrieve mipmapped texture for %r' % self)
458
459    def get_region(self, x, y, width, height):
460        """Retrieve a rectangular region of this image.
461
462        :Parameters:
463            `x` : int
464                Left edge of region.
465            `y` : int
466                Bottom edge of region.
467            `width` : int
468                Width of region.
469            `height` : int
470                Height of region.
471
472        :rtype: AbstractImage
473        """
474        raise ImageException('Cannot get region for %r' % self)
475
476    def save(self, filename=None, file=None, encoder=None):
477        """Save this image to a file.
478
479        :Parameters:
480            `filename` : str
481                Used to set the image file format, and to open the output file
482                if `file` is unspecified.
483            `file` : file-like object or None
484                File to write image data to.
485            `encoder` : ImageEncoder or None
486                If unspecified, all encoders matching the filename extension
487                are tried.  If all fail, the exception from the first one
488                attempted is raised.
489
490        """
491        if not file:
492            file = open(filename, 'wb')
493
494        if encoder:
495            encoder.encode(self, file, filename)
496        else:
497            first_exception = None
498            for encoder in get_encoders(filename):
499                try:
500                    encoder.encode(self, file, filename)
501                    return
502                except ImageEncodeException as e:
503                    first_exception = first_exception or e
504                    file.seek(0)
505
506            if not first_exception:
507                raise ImageEncodeException(
508                    'No image encoders are available')
509            raise first_exception
510
511    def blit(self, x, y, z=0):
512        """Draw this image to the active framebuffers.
513
514        The image will be drawn with the lower-left corner at
515        (``x -`` `anchor_x`, ``y -`` `anchor_y`, ``z``).
516        """
517        raise ImageException('Cannot blit %r.' % self)
518
519    def blit_into(self, source, x, y, z):
520        """Draw `source` on this image.
521
522        `source` will be copied into this image such that its anchor point
523        is aligned with the `x` and `y` parameters.  If this image is a 3D
524        texture, the `z` coordinate gives the image slice to copy into.
525
526        Note that if `source` is larger than this image (or the positioning
527        would cause the copy to go out of bounds) then you must pass a
528        region of `source` to this method, typically using get_region().
529        """
530        raise ImageException('Cannot blit images onto %r.' % self)
531
532    def blit_to_texture(self, target, level, x, y, z=0):
533        """Draw this image on the currently bound texture at `target`.
534
535        This image is copied into the texture such that this image's anchor
536        point is aligned with the given `x` and `y` coordinates of the
537        destination texture.  If the currently bound texture is a 3D texture,
538        the `z` coordinate gives the image slice to blit into.
539        """
540        raise ImageException('Cannot blit %r to a texture.' % self)
541
542
543class AbstractImageSequence:
544    """Abstract sequence of images.
545
546    The sequence is useful for storing image animations or slices of a volume.
547    For efficient access, use the `texture_sequence` member.  The class
548    also implements the sequence interface (`__len__`, `__getitem__`,
549    `__setitem__`).
550    """
551
552    def get_texture_sequence(self):
553        """Get a TextureSequence.
554
555        :rtype: `TextureSequence`
556
557        .. versionadded:: 1.1
558        """
559        raise NotImplementedError('abstract')
560
561    def get_animation(self, period, loop=True):
562        """Create an animation over this image sequence for the given constant
563        framerate.
564
565        :Parameters
566            `period` : float
567                Number of seconds to display each frame.
568            `loop` : bool
569                If True, the animation will loop continuously.
570
571        :rtype: :py:class:`~pyglet.image.Animation`
572
573        .. versionadded:: 1.1
574        """
575        return Animation.from_image_sequence(self, period, loop)
576
577    def __getitem__(self, slice):
578        """Retrieve a (list of) image.
579
580        :rtype: AbstractImage
581        """
582        raise NotImplementedError('abstract')
583
584    def __setitem__(self, slice, image):
585        """Replace one or more images in the sequence.
586
587        :Parameters:
588            `image` : `~pyglet.image.AbstractImage`
589                The replacement image.  The actual instance may not be used,
590                depending on this implementation.
591
592        """
593        raise NotImplementedError('abstract')
594
595    def __len__(self):
596        raise NotImplementedError('abstract')
597
598    def __iter__(self):
599        """Iterate over the images in sequence.
600
601        :rtype: Iterator
602
603        .. versionadded:: 1.1
604        """
605        raise NotImplementedError('abstract')
606
607
608class TextureSequence(AbstractImageSequence):
609    """Interface for a sequence of textures.
610
611    Typical implementations store multiple :py:class:`~pyglet.image.TextureRegion` s within one
612    :py:class:`~pyglet.image.Texture` so as to minimise state changes.
613    """
614
615    def get_texture_sequence(self):
616        return self
617
618
619class UniformTextureSequence(TextureSequence):
620    """Interface for a sequence of textures, each with the same dimensions.
621
622    :Parameters:
623        `item_width` : int
624            Width of each texture in the sequence.
625        `item_height` : int
626            Height of each texture in the sequence.
627
628    """
629
630    def _get_item_width(self):
631        raise NotImplementedError('abstract')
632
633    def _get_item_height(self):
634        raise NotImplementedError('abstract')
635
636    @property
637    def item_width(self):
638        return self._get_item_width()
639
640    @property
641    def item_height(self):
642        return self._get_item_height()
643
644
645class ImageData(AbstractImage):
646    """An image represented as a string of unsigned bytes.
647
648    :Parameters:
649        `data` : str
650            Pixel data, encoded according to `format` and `pitch`.
651        `format` : str
652            The format string to use when reading or writing `data`.
653        `pitch` : int
654            Number of bytes per row.  Negative values indicate a top-to-bottom
655            arrangement.
656
657    """
658    _swap1_pattern = re.compile(asbytes('(.)'), re.DOTALL)
659    _swap2_pattern = re.compile(asbytes('(.)(.)'), re.DOTALL)
660    _swap3_pattern = re.compile(asbytes('(.)(.)(.)'), re.DOTALL)
661    _swap4_pattern = re.compile(asbytes('(.)(.)(.)(.)'), re.DOTALL)
662
663    _current_texture = None
664    _current_mipmap_texture = None
665
666    def __init__(self, width, height, format, data, pitch=None):
667        """Initialise image data.
668
669        :Parameters:
670            `width` : int
671                Width of image data
672            `height` : int
673                Height of image data
674            `format` : str
675                A valid format string, such as 'RGB', 'RGBA', 'ARGB', etc.
676            `data` : sequence
677                String or array/list of bytes giving the decoded data.
678            `pitch` : int or None
679                If specified, the number of bytes per row.  Negative values
680                indicate a top-to-bottom arrangement.  Defaults to
681                ``width * len(format)``.
682
683        """
684        super(ImageData, self).__init__(width, height)
685
686        self._current_format = self._desired_format = format.upper()
687        self._current_data = data
688        if not pitch:
689            pitch = width * len(format)
690        self._current_pitch = self.pitch = pitch
691        self.mipmap_images = []
692
693    def __getstate__(self):
694        return {
695            'width': self.width,
696            'height': self.height,
697            '_current_data': self.get_data(self._current_format, self._current_pitch),
698            '_current_format': self._current_format,
699            '_desired_format': self._desired_format,
700            '_current_pitch': self._current_pitch,
701            'pitch': self.pitch,
702            'mipmap_images': self.mipmap_images
703        }
704
705    def get_image_data(self):
706        return self
707
708    @property
709    def format(self):
710        """Format string of the data.  Read-write.
711
712        :type: str
713        """
714        return self._desired_format
715
716    @format.setter
717    def format(self, fmt):
718        self._desired_format = fmt.upper()
719        self._current_texture = None
720
721    def get_data(self, fmt=None, pitch=None):
722        """Get the byte data of the image.
723
724        :Parameters:
725            `fmt` : str
726                Format string of the return data.
727            `pitch` : int
728                Number of bytes per row.  Negative values indicate a
729                top-to-bottom arrangement.
730
731        .. versionadded:: 1.1
732
733        :rtype: sequence of bytes, or str
734        """
735        fmt = fmt or self._desired_format
736        pitch = pitch or self._current_pitch
737
738        if fmt == self._current_format and pitch == self._current_pitch:
739            return self._current_data
740        return self._convert(fmt, pitch)
741
742    def set_data(self, fmt, pitch, data):
743        """Set the byte data of the image.
744
745        :Parameters:
746            `fmt` : str
747                Format string of the return data.
748            `pitch` : int
749                Number of bytes per row.  Negative values indicate a
750                top-to-bottom arrangement.
751            `data` : str or sequence of bytes
752                Image data.
753
754        .. versionadded:: 1.1
755        """
756        self._current_format = fmt
757        self._current_pitch = pitch
758        self._current_data = data
759        self._current_texture = None
760        self._current_mipmap_texture = None
761
762    def set_mipmap_image(self, level, image):
763        """Set a mipmap image for a particular level.
764
765        The mipmap image will be applied to textures obtained via
766        `get_mipmapped_texture`.
767
768        :Parameters:
769            `level` : int
770                Mipmap level to set image at, must be >= 1.
771            `image` : AbstractImage
772                Image to set.  Must have correct dimensions for that mipmap
773                level (i.e., width >> level, height >> level)
774        """
775
776        if level == 0:
777            raise ImageException(
778                'Cannot set mipmap image at level 0 (it is this image)')
779
780        if not _is_pow2(self.width) or not _is_pow2(self.height):
781            raise ImageException(
782                'Image dimensions must be powers of 2 to use mipmaps.')
783
784        # Check dimensions of mipmap
785        width, height = self.width, self.height
786        for i in range(level):
787            width >>= 1
788            height >>= 1
789        if width != image.width or height != image.height:
790            raise ImageException(
791                'Mipmap image has wrong dimensions for level %d' % level)
792
793        # Extend mipmap_images list to required level
794        self.mipmap_images += [None] * (level - len(self.mipmap_images))
795        self.mipmap_images[level - 1] = image
796
797    def create_texture(self, cls, rectangle=False, force_rectangle=False):
798        """Create a texture containing this image.
799
800        If the image's dimensions are not powers of 2, a TextureRegion of
801        a larger Texture will be returned that matches the dimensions of this
802        image.
803
804        :Parameters:
805            `cls` : class (subclass of Texture)
806                Class to construct.
807            `rectangle` : bool
808                ``True`` if a rectangle can be created; see
809                `AbstractImage.get_texture`.
810
811                .. versionadded:: 1.1
812            `force_rectangle` : bool
813                ``True`` if a rectangle must be created; see
814                `AbstractImage.get_texture`.
815
816                .. versionadded:: 1.1.4
817
818        :rtype: cls or cls.region_class
819        """
820        internalformat = self._get_internalformat(self.format)
821        texture = cls.create(self.width, self.height, internalformat,
822                             rectangle, force_rectangle)
823        if self.anchor_x or self.anchor_y:
824            texture.anchor_x = self.anchor_x
825            texture.anchor_y = self.anchor_y
826
827        self.blit_to_texture(texture.target, texture.level,
828                             self.anchor_x, self.anchor_y, 0, None)
829
830        return texture
831
832    def get_texture(self, rectangle=False, force_rectangle=False):
833        if (not self._current_texture or
834                (not self._current_texture._is_rectangle and force_rectangle)):
835            self._current_texture = self.create_texture(Texture, rectangle, force_rectangle)
836        return self._current_texture
837
838    def get_mipmapped_texture(self):
839        """Return a Texture with mipmaps.
840
841        If :py:class:`~pyglet.image.set_mipmap_Image` has been called with at least one image, the set
842        of images defined will be used.  Otherwise, mipmaps will be
843        automatically generated.
844
845        The texture dimensions must be powers of 2 to use mipmaps.
846
847        :rtype: :py:class:`~pyglet.image.Texture`
848
849        .. versionadded:: 1.1
850        """
851        if self._current_mipmap_texture:
852            return self._current_mipmap_texture
853
854        if not _is_pow2(self.width) or not _is_pow2(self.height):
855            raise ImageException(
856                'Image dimensions must be powers of 2 to use mipmaps.')
857
858        texture = Texture.create_for_size(GL_TEXTURE_2D, self.width, self.height)
859        if self.anchor_x or self.anchor_y:
860            texture.anchor_x = self.anchor_x
861            texture.anchor_y = self.anchor_y
862
863        internalformat = self._get_internalformat(self.format)
864
865        glBindTexture(texture.target, texture.id)
866        glTexParameteri(texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
867
868        if self.mipmap_images:
869            self.blit_to_texture(texture.target, texture.level,
870                                 self.anchor_x, self.anchor_y, 0, internalformat)
871            level = 0
872            for image in self.mipmap_images:
873                level += 1
874                if image:
875                    image.blit_to_texture(texture.target, level,
876                                          self.anchor_x, self.anchor_y, 0, internalformat)
877                    # TODO: should set base and max mipmap level if some mipmaps are missing.
878        else:
879            glTexParameteri(texture.target, GL_GENERATE_MIPMAP, GL_TRUE)
880            self.blit_to_texture(texture.target, texture.level,
881                                 self.anchor_x, self.anchor_y, 0, internalformat)
882
883        self._current_mipmap_texture = texture
884        return texture
885
886    def get_region(self, x, y, width, height):
887        """Retrieve a rectangular region of this image data.
888
889        :Parameters:
890            `x` : int
891                Left edge of region.
892            `y` : int
893                Bottom edge of region.
894            `width` : int
895                Width of region.
896            `height` : int
897                Height of region.
898
899        :rtype: ImageDataRegion
900        """
901        return ImageDataRegion(x, y, width, height, self)
902
903    def blit(self, x, y, z=0, width=None, height=None):
904        self.get_texture().blit(x, y, z, width, height)
905
906    def blit_to_texture(self, target, level, x, y, z, internalformat=None):
907        """Draw this image to to the currently bound texture at `target`.
908
909        This image's anchor point will be aligned to the given `x` and `y`
910        coordinates.  If the currently bound texture is a 3D texture, the `z`
911        parameter gives the image slice to blit into.
912
913        If `internalformat` is specified, glTexImage is used to initialise
914        the texture; otherwise, glTexSubImage is used to update a region.
915        """
916        x -= self.anchor_x
917        y -= self.anchor_y
918
919        data_format = self.format
920        data_pitch = abs(self._current_pitch)
921
922        # Determine pixel format from format string
923        matrix = None
924        format, type = self._get_gl_format_and_type(data_format)
925        if format is None:
926            if (len(data_format) in (3, 4) and
927                    gl_info.have_extension('GL_ARB_imaging')):
928                # Construct a color matrix to convert to GL_RGBA
929                def component_column(component):
930                    try:
931                        pos = 'RGBA'.index(component)
932                        return [0] * pos + [1] + [0] * (3 - pos)
933                    except ValueError:
934                        return [0, 0, 0, 0]
935
936                # pad to avoid index exceptions
937                lookup_format = data_format + 'XXX'
938                matrix = (component_column(lookup_format[0]) +
939                          component_column(lookup_format[1]) +
940                          component_column(lookup_format[2]) +
941                          component_column(lookup_format[3]))
942                format = {
943                    3: GL_RGB,
944                    4: GL_RGBA}.get(len(data_format))
945                type = GL_UNSIGNED_BYTE
946
947                glMatrixMode(GL_COLOR)
948                glPushMatrix()
949                glLoadMatrixf((GLfloat * 16)(*matrix))
950            else:
951                # Need to convert data to a standard form
952                data_format = {
953                    1: 'L',
954                    2: 'LA',
955                    3: 'RGB',
956                    4: 'RGBA'}.get(len(data_format))
957                format, type = self._get_gl_format_and_type(data_format)
958
959        # Workaround: don't use GL_UNPACK_ROW_LENGTH
960        if pyglet.gl.current_context._workaround_unpack_row_length:
961            data_pitch = self.width * len(data_format)
962
963        # Get data in required format (hopefully will be the same format it's
964        # already in, unless that's an obscure format, upside-down or the
965        # driver is old).
966        data = self._convert(data_format, data_pitch)
967
968        if data_pitch & 0x1:
969            alignment = 1
970        elif data_pitch & 0x2:
971            alignment = 2
972        else:
973            alignment = 4
974        row_length = data_pitch // len(data_format)
975        glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT)
976        glPixelStorei(GL_UNPACK_ALIGNMENT, alignment)
977        glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length)
978        self._apply_region_unpack()
979
980        if target == GL_TEXTURE_3D:
981            assert not internalformat
982            glTexSubImage3D(target, level,
983                            x, y, z,
984                            self.width, self.height, 1,
985                            format, type,
986                            data)
987        elif internalformat:
988            glTexImage2D(target, level,
989                         internalformat,
990                         self.width, self.height,
991                         0,
992                         format, type,
993                         data)
994        else:
995            glTexSubImage2D(target, level,
996                            x, y,
997                            self.width, self.height,
998                            format, type,
999                            data)
1000        glPopClientAttrib()
1001
1002        if matrix:
1003            glPopMatrix()
1004            glMatrixMode(GL_MODELVIEW)
1005
1006        # Flush image upload before data get GC'd.
1007        glFlush()
1008
1009    def _apply_region_unpack(self):
1010        pass
1011
1012    def _convert(self, format, pitch):
1013        """Return data in the desired format; does not alter this instance's
1014        current format or pitch.
1015        """
1016        if format == self._current_format and pitch == self._current_pitch:
1017            if type(self._current_data) is str:
1018                return asbytes(self._current_data)
1019            return self._current_data
1020
1021        self._ensure_string_data()
1022        data = self._current_data
1023        current_pitch = self._current_pitch
1024        current_format = self._current_format
1025        sign_pitch = current_pitch // abs(current_pitch)
1026        if format != self._current_format:
1027            # Create replacement string, e.g. r'\4\1\2\3' to convert RGBA to
1028            # ARGB
1029            repl = asbytes('')
1030            for c in format:
1031                try:
1032                    idx = current_format.index(c) + 1
1033                except ValueError:
1034                    idx = 1
1035                repl += asbytes(r'\%d' % idx)
1036
1037            if len(current_format) == 1:
1038                swap_pattern = self._swap1_pattern
1039            elif len(current_format) == 2:
1040                swap_pattern = self._swap2_pattern
1041            elif len(current_format) == 3:
1042                swap_pattern = self._swap3_pattern
1043            elif len(current_format) == 4:
1044                swap_pattern = self._swap4_pattern
1045            else:
1046                raise ImageException(
1047                    'Current image format is wider than 32 bits.')
1048
1049            packed_pitch = self.width * len(current_format)
1050            if abs(self._current_pitch) != packed_pitch:
1051                # Pitch is wider than pixel data, need to go row-by-row.
1052                new_pitch = abs(self._current_pitch)
1053                rows = [data[i:i+new_pitch] for i in range(0, len(data), new_pitch)]
1054                rows = [swap_pattern.sub(repl, r[:packed_pitch]) for r in rows]
1055                data = asbytes('').join(rows)
1056            else:
1057                # Rows are tightly packed, apply regex over whole image.
1058                data = swap_pattern.sub(repl, data)
1059
1060            # After conversion, rows will always be tightly packed
1061            current_pitch = sign_pitch * (len(format) * self.width)
1062
1063        if pitch != current_pitch:
1064            diff = abs(current_pitch) - abs(pitch)
1065            if diff > 0:
1066                # New pitch is shorter than old pitch, chop bytes off each row
1067                new_pitch = abs(pitch)
1068                rows = [data[i:i+new_pitch-diff] for i in range(0, len(data), new_pitch)]
1069            elif diff < 0:
1070                # New pitch is longer than old pitch, add '0' bytes to each row
1071                new_pitch = abs(current_pitch)
1072                padding = asbytes(1) * -diff
1073                rows = [data[i:i+new_pitch] + padding for i in range(0, len(data), new_pitch)]
1074
1075            if current_pitch * pitch < 0:
1076                # Pitch differs in sign, swap row order
1077                new_pitch = abs(pitch)
1078                rows = [data[i:i+new_pitch] for i in range(0, len(data), new_pitch)]
1079                rows.reverse()
1080
1081            data = asbytes('').join(rows)
1082
1083        return asbytes(data)
1084
1085    def _ensure_string_data(self):
1086        if type(self._current_data) is not bytes:
1087            buf = create_string_buffer(len(self._current_data))
1088            memmove(buf, self._current_data, len(self._current_data))
1089            self._current_data = buf.raw
1090
1091    def _get_gl_format_and_type(self, format):
1092        if format == 'I':
1093            return GL_LUMINANCE, GL_UNSIGNED_BYTE
1094        elif format == 'L':
1095            return GL_LUMINANCE, GL_UNSIGNED_BYTE
1096        elif format == 'LA':
1097            return GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE
1098        elif format == 'R':
1099            return GL_RED, GL_UNSIGNED_BYTE
1100        elif format == 'G':
1101            return GL_GREEN, GL_UNSIGNED_BYTE
1102        elif format == 'B':
1103            return GL_BLUE, GL_UNSIGNED_BYTE
1104        elif format == 'A':
1105            return GL_ALPHA, GL_UNSIGNED_BYTE
1106        elif format == 'RGB':
1107            return GL_RGB, GL_UNSIGNED_BYTE
1108        elif format == 'RGBA':
1109            return GL_RGBA, GL_UNSIGNED_BYTE
1110        elif (format == 'ARGB' and
1111                  gl_info.have_extension('GL_EXT_bgra') and
1112                  gl_info.have_extension('GL_APPLE_packed_pixels')):
1113            return GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV
1114        elif (format == 'ABGR' and
1115                  gl_info.have_extension('GL_EXT_abgr')):
1116            return GL_ABGR_EXT, GL_UNSIGNED_BYTE
1117        elif (format == 'BGR' and
1118                  gl_info.have_extension('GL_EXT_bgra')):
1119            return GL_BGR, GL_UNSIGNED_BYTE
1120        elif (format == 'BGRA' and
1121                  gl_info.have_extension('GL_EXT_bgra')):
1122            return GL_BGRA, GL_UNSIGNED_BYTE
1123
1124        return None, None
1125
1126    def _get_internalformat(self, format):
1127        if len(format) == 4:
1128            return GL_RGBA
1129        elif len(format) == 3:
1130            return GL_RGB
1131        elif len(format) == 2:
1132            return GL_LUMINANCE_ALPHA
1133        elif format == 'A':
1134            return GL_ALPHA
1135        elif format == 'L':
1136            return GL_LUMINANCE
1137        elif format == 'I':
1138            return GL_INTENSITY
1139        return GL_RGBA
1140
1141
1142class ImageDataRegion(ImageData):
1143    def __init__(self, x, y, width, height, image_data):
1144        super(ImageDataRegion, self).__init__(width, height,
1145                                              image_data._current_format, image_data._current_data,
1146                                              image_data._current_pitch)
1147        self.x = x
1148        self.y = y
1149
1150    def __getstate__(self):
1151        return {
1152            'width': self.width,
1153            'height': self.height,
1154            '_current_data':
1155                self.get_data(self._current_format, self._current_pitch),
1156            '_current_format': self._current_format,
1157            '_desired_format': self._desired_format,
1158            '_current_pitch': self._current_pitch,
1159            'pitch': self.pitch,
1160            'mipmap_images': self.mipmap_images,
1161            'x': self.x,
1162            'y': self.y
1163        }
1164
1165    def get_data(self, fmt=None, pitch=None):
1166        x1 = len(self._current_format) * self.x
1167        x2 = len(self._current_format) * (self.x + self.width)
1168
1169        self._ensure_string_data()
1170        data = self._convert(self._current_format, abs(self._current_pitch))
1171        new_pitch = abs(self._current_pitch)
1172        rows = [data[i:i+new_pitch] for i in range(0, len(data), new_pitch)]
1173        rows = [row[x1:x2] for row in rows[self.y:self.y + self.height]]
1174        self._current_data = b''.join(rows)
1175        self._current_pitch = self.width * len(self._current_format)
1176        self._current_texture = None
1177        self.x = 0
1178        self.y = 0
1179
1180        fmt = fmt or self._desired_format
1181        pitch = pitch or self._current_pitch
1182        return super(ImageDataRegion, self).get_data(fmt, pitch)
1183
1184    def set_data(self, fmt, pitch, data):
1185        self.x = 0
1186        self.y = 0
1187        super(ImageDataRegion, self).set_data(fmt, pitch, data)
1188
1189    def _apply_region_unpack(self):
1190        glPixelStorei(GL_UNPACK_SKIP_PIXELS, self.x)
1191        glPixelStorei(GL_UNPACK_SKIP_ROWS, self.y)
1192
1193    def get_region(self, x, y, width, height):
1194        x += self.x
1195        y += self.y
1196        return super(ImageDataRegion, self).get_region(x, y, width, height)
1197
1198
1199class CompressedImageData(AbstractImage):
1200    """Image representing some compressed data suitable for direct uploading
1201    to driver.
1202    """
1203
1204    _current_texture = None
1205    _current_mipmap_texture = None
1206
1207    def __init__(self, width, height, gl_format, data, extension=None, decoder=None):
1208        """Construct a CompressedImageData with the given compressed data.
1209
1210        :Parameters:
1211            `width` : int
1212                Width of image
1213            `height` : int
1214                Height of image
1215            `gl_format` : int
1216                GL constant giving format of compressed data; for example,
1217                ``GL_COMPRESSED_RGBA_S3TC_DXT5_EXT``.
1218            `data` : sequence
1219                String or array/list of bytes giving compressed image data.
1220            `extension` : str or None
1221                If specified, gives the name of a GL extension to check for
1222                before creating a texture.
1223            `decoder` : function(data, width, height) -> AbstractImage
1224                A function to decode the compressed data, to be used if the
1225                required extension is not present.
1226
1227        """
1228        super(CompressedImageData, self).__init__(width, height)
1229        self.data = data
1230        self.gl_format = gl_format
1231        self.extension = extension
1232        self.decoder = decoder
1233        self.mipmap_data = []
1234
1235    def set_mipmap_data(self, level, data):
1236        """Set data for a mipmap level.
1237
1238        Supplied data gives a compressed image for the given mipmap level.
1239        The image must be of the correct dimensions for the level
1240        (i.e., width >> level, height >> level); but this is not checked.  If
1241        any mipmap levels are specified, they are used; otherwise, mipmaps for
1242        `mipmapped_texture` are generated automatically.
1243
1244        :Parameters:
1245            `level` : int
1246                Level of mipmap image to set.
1247            `data` : sequence
1248                String or array/list of bytes giving compressed image data.
1249                Data must be in same format as specified in constructor.
1250
1251        """
1252        # Extend mipmap_data list to required level
1253        self.mipmap_data += [None] * (level - len(self.mipmap_data))
1254        self.mipmap_data[level - 1] = data
1255
1256    def _have_extension(self):
1257        return self.extension is None or gl_info.have_extension(self.extension)
1258
1259    def _verify_driver_supported(self):
1260        """Assert that the extension required for this image data is
1261        supported.
1262
1263        Raises `ImageException` if not.
1264        """
1265
1266        if not self._have_extension():
1267            raise ImageException('%s is required to decode %r' % (self.extension, self))
1268
1269    def get_texture(self, rectangle=False, force_rectangle=False):
1270        if force_rectangle:
1271            raise ImageException('Compressed texture rectangles not supported')
1272
1273        if self._current_texture:
1274            return self._current_texture
1275
1276        tex_id = GLuint()
1277        glGenTextures(1, byref(tex_id))
1278        texture = Texture(self.width, self.height, GL_TEXTURE_2D, tex_id.value)
1279        glBindTexture(GL_TEXTURE_2D, tex_id)
1280        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, Texture.default_min_filter)
1281        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, Texture.default_mag_filter)
1282
1283        if self.anchor_x or self.anchor_y:
1284            texture.anchor_x = self.anchor_x
1285            texture.anchor_y = self.anchor_y
1286
1287        if self._have_extension():
1288            glCompressedTexImage2D(texture.target, texture.level,
1289                                   self.gl_format,
1290                                   self.width, self.height, 0,
1291                                   len(self.data), self.data)
1292        else:
1293            image = self.decoder(self.data, self.width, self.height)
1294            texture = image.get_texture()
1295            assert texture.width == self.width
1296            assert texture.height == self.height
1297
1298        glFlush()
1299        self._current_texture = texture
1300        return texture
1301
1302    def get_mipmapped_texture(self):
1303        if self._current_mipmap_texture:
1304            return self._current_mipmap_texture
1305
1306        if not self._have_extension():
1307            # TODO mip-mapped software decoded compressed textures.  For now,
1308            # just return a non-mipmapped texture.
1309            return self.get_texture()
1310
1311        texture = Texture.create_for_size(GL_TEXTURE_2D, self.width, self.height)
1312        if self.anchor_x or self.anchor_y:
1313            texture.anchor_x = self.anchor_x
1314            texture.anchor_y = self.anchor_y
1315
1316        glBindTexture(texture.target, texture.id)
1317
1318        glTexParameteri(texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
1319
1320        if not self.mipmap_data:
1321            glTexParameteri(texture.target, GL_GENERATE_MIPMAP, GL_TRUE)
1322
1323        glCompressedTexImage2DARB(texture.target, texture.level,
1324                                  self.gl_format,
1325                                  self.width, self.height, 0,
1326                                  len(self.data), self.data)
1327
1328        width, height = self.width, self.height
1329        level = 0
1330        for data in self.mipmap_data:
1331            width >>= 1
1332            height >>= 1
1333            level += 1
1334            glCompressedTexImage2DARB(texture.target, level,
1335                                      self.gl_format,
1336                                      width, height, 0,
1337                                      len(data), data)
1338
1339        glFlush()
1340
1341        self._current_mipmap_texture = texture
1342        return texture
1343
1344    def blit_to_texture(self, target, level, x, y, z):
1345        self._verify_driver_supported()
1346
1347        if target == GL_TEXTURE_3D:
1348            glCompressedTexSubImage3DARB(target, level,
1349                                         x - self.anchor_x, y - self.anchor_y, z,
1350                                         self.width, self.height, 1,
1351                                         self.gl_format,
1352                                         len(self.data), self.data)
1353        else:
1354            glCompressedTexSubImage2DARB(target, level,
1355                                         x - self.anchor_x, y - self.anchor_y,
1356                                         self.width, self.height,
1357                                         self.gl_format,
1358                                         len(self.data), self.data)
1359
1360
1361class Texture(AbstractImage):
1362    """An image loaded into video memory that can be efficiently drawn
1363    to the framebuffer.
1364
1365    Typically you will get an instance of Texture by accessing the `texture`
1366    member of any other AbstractImage.
1367
1368    :Parameters:
1369        `region_class` : class (subclass of TextureRegion)
1370            Class to use when constructing regions of this texture.
1371        `tex_coords` : tuple
1372            12-tuple of float, named (u1, v1, r1, u2, v2, r2, ...).  u, v, r
1373            give the 3D texture coordinates for vertices 1-4.  The vertices
1374            are specified in the order bottom-left, bottom-right, top-right
1375            and top-left.
1376        `target` : int
1377            The GL texture target (e.g., ``GL_TEXTURE_2D``).
1378        `level` : int
1379            The mipmap level of this texture.
1380
1381    """
1382
1383    region_class = None  # Set to TextureRegion after it's defined
1384    tex_coords = (0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 0.)
1385    tex_coords_order = (0, 1, 2, 3)
1386    level = 0
1387    images = 1
1388    x = y = z = 0
1389    default_min_filter = GL_LINEAR
1390    default_mag_filter = GL_LINEAR
1391
1392    def __init__(self, width, height, target, id):
1393        super(Texture, self).__init__(width, height)
1394        self.target = target
1395        self.id = id
1396        self._context = pyglet.gl.current_context
1397
1398    def __del__(self):
1399        try:
1400            self._context.delete_texture(self.id)
1401        except:
1402            pass
1403
1404    @classmethod
1405    def create(cls, width, height, internalformat=GL_RGBA,
1406               rectangle=False, force_rectangle=False, min_filter=None, mag_filter=None):
1407        """Create an empty Texture.
1408
1409        If `rectangle` is ``False`` or the appropriate driver extensions are
1410        not available, a larger texture than requested will be created, and
1411        a :py:class:`~pyglet.image.TextureRegion` corresponding to the requested size will be
1412        returned.
1413
1414        :Parameters:
1415            `width` : int
1416                Width of the texture.
1417            `height` : int
1418                Height of the texture.
1419            `internalformat` : int
1420                GL constant giving the internal format of the texture; for
1421                example, ``GL_RGBA``.
1422            `rectangle` : bool
1423                ``True`` if a rectangular texture is permitted.  See
1424                `AbstractImage.get_texture`.
1425            `force_rectangle` : bool
1426                ``True`` if a rectangular texture is required.  See
1427                `AbstractImage.get_texture`.
1428
1429                .. versionadded:: 1.1.4.
1430            `min_filter` : int
1431                The minifaction filter used for this texture, commonly ``GL_LINEAR`` or ``GL_NEAREST``
1432            `mag_filter` : int
1433                The magnification filter used for this texture, commonly ``GL_LINEAR`` or ``GL_NEAREST``
1434
1435        :rtype: :py:class:`~pyglet.image.Texture`
1436
1437        .. versionadded:: 1.1
1438        """
1439        min_filter = min_filter or cls.default_min_filter
1440        mag_filter = mag_filter or cls.default_mag_filter
1441        target = GL_TEXTURE_2D
1442        if rectangle or force_rectangle:
1443            if not force_rectangle and _is_pow2(width) and _is_pow2(height):
1444                rectangle = False
1445            elif gl_info.have_extension('GL_ARB_texture_rectangle'):
1446                target = GL_TEXTURE_RECTANGLE_ARB
1447                rectangle = True
1448            elif gl_info.have_extension('GL_NV_texture_rectangle'):
1449                target = GL_TEXTURE_RECTANGLE_NV
1450                rectangle = True
1451            else:
1452                rectangle = False
1453
1454        if force_rectangle and not rectangle:
1455            raise ImageException('Texture rectangle extensions not available')
1456
1457        if rectangle:
1458            texture_width = width
1459            texture_height = height
1460        else:
1461            texture_width = _nearest_pow2(width)
1462            texture_height = _nearest_pow2(height)
1463
1464        id = GLuint()
1465        glGenTextures(1, byref(id))
1466        glBindTexture(target, id.value)
1467        glTexParameteri(target, GL_TEXTURE_MIN_FILTER, min_filter)
1468        glTexParameteri(target, GL_TEXTURE_MAG_FILTER, mag_filter)
1469
1470        blank = (GLubyte * (texture_width * texture_height * 4))()
1471        glTexImage2D(target, 0,
1472                     internalformat,
1473                     texture_width, texture_height,
1474                     0,
1475                     GL_RGBA, GL_UNSIGNED_BYTE,
1476                     blank)
1477
1478        texture = cls(texture_width, texture_height, target, id.value)
1479        texture.min_filter = min_filter
1480        texture.mag_filter = mag_filter
1481        if rectangle:
1482            texture._is_rectangle = True
1483            texture.tex_coords = (0., 0., 0.,
1484                                  width, 0., 0.,
1485                                  width, height, 0.,
1486                                  0., height, 0.)
1487
1488        glFlush()
1489
1490        if texture_width == width and texture_height == height:
1491            return texture
1492
1493        return texture.get_region(0, 0, width, height)
1494
1495    @classmethod
1496    def create_for_size(cls, target, min_width, min_height,
1497                        internalformat=None, min_filter=None, mag_filter=None):
1498        """Create a Texture with dimensions at least min_width, min_height.
1499        On return, the texture will be bound.
1500
1501        :Parameters:
1502            `target` : int
1503                GL constant giving texture target to use, typically
1504                ``GL_TEXTURE_2D``.
1505            `min_width` : int
1506                Minimum width of texture (may be increased to create a power
1507                of 2).
1508            `min_height` : int
1509                Minimum height of texture (may be increased to create a power
1510                of 2).
1511            `internalformat` : int
1512                GL constant giving internal format of texture; for example,
1513                ``GL_RGBA``.  If unspecified, the texture will not be
1514                initialised (only the texture name will be created on the
1515                instance).   If specified, the image will be initialised
1516                to this format with zero'd data.
1517            `min_filter` : int
1518                The minifaction filter used for this texture, commonly ``GL_LINEAR`` or ``GL_NEAREST``
1519            `mag_filter` : int
1520                The magnification filter used for this texture, commonly ``GL_LINEAR`` or ``GL_NEAREST``
1521
1522        :rtype: :py:class:`~pyglet.image.Texture`
1523        """
1524        if target not in (GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_RECTANGLE_ARB):
1525            width = _nearest_pow2(min_width)
1526            height = _nearest_pow2(min_height)
1527            tex_coords = cls.tex_coords
1528        else:
1529            width = min_width
1530            height = min_height
1531            tex_coords = (0., 0., 0.,
1532                          width, 0., 0.,
1533                          width, height, 0.,
1534                          0., height, 0.)
1535        min_filter = min_filter or cls.default_min_filter
1536        mag_filter = mag_filter or cls.default_mag_filter
1537        id = GLuint()
1538        glGenTextures(1, byref(id))
1539        glBindTexture(target, id.value)
1540        glTexParameteri(target, GL_TEXTURE_MIN_FILTER, min_filter)
1541        glTexParameteri(target, GL_TEXTURE_MAG_FILTER, mag_filter)
1542
1543        if internalformat is not None:
1544            blank = (GLubyte * (width * height * 4))()
1545            glTexImage2D(target, 0,
1546                         internalformat,
1547                         width, height,
1548                         0,
1549                         GL_RGBA, GL_UNSIGNED_BYTE,
1550                         blank)
1551            glFlush()
1552
1553        texture = cls(width, height, target, id.value)
1554        texture.min_filter = min_filter
1555        texture.mag_filter = mag_filter
1556        texture.tex_coords = tex_coords
1557        return texture
1558
1559    def get_image_data(self, z=0):
1560        """Get the image data of this texture.
1561
1562        Changes to the returned instance will not be reflected in this
1563        texture.
1564
1565        :Parameters:
1566            `z` : int
1567                For 3D textures, the image slice to retrieve.
1568
1569        :rtype: :py:class:`~pyglet.image.ImageData`
1570        """
1571        glBindTexture(self.target, self.id)
1572
1573        # Always extract complete RGBA data.  Could check internalformat
1574        # to only extract used channels. XXX
1575        format = 'RGBA'
1576        gl_format = GL_RGBA
1577
1578        glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT)
1579        glPixelStorei(GL_PACK_ALIGNMENT, 1)
1580        buffer = (GLubyte * (self.width * self.height * self.images * len(format)))()
1581        glGetTexImage(self.target, self.level,
1582                      gl_format, GL_UNSIGNED_BYTE, buffer)
1583        glPopClientAttrib()
1584
1585        data = ImageData(self.width, self.height, format, buffer)
1586        if self.images > 1:
1587            data = data.get_region(0, z * self.height, self.width, self.height)
1588        return data
1589
1590    def get_texture(self, rectangle=False, force_rectangle=False):
1591        if force_rectangle and not self._is_rectangle:
1592            raise ImageException('Texture is not a rectangle.')
1593        return self
1594
1595    # no implementation of blit_to_texture yet (could use aux buffer)
1596
1597    def blit(self, x, y, z=0, width=None, height=None):
1598        t = self.tex_coords
1599        x1 = x - self.anchor_x
1600        y1 = y - self.anchor_y
1601        x2 = x1 + (width is None and self.width or width)
1602        y2 = y1 + (height is None and self.height or height)
1603        array = (GLfloat * 32)(
1604            t[0], t[1], t[2], 1.,
1605            x1, y1, z, 1.,
1606            t[3], t[4], t[5], 1.,
1607            x2, y1, z, 1.,
1608            t[6], t[7], t[8], 1.,
1609            x2, y2, z, 1.,
1610            t[9], t[10], t[11], 1.,
1611            x1, y2, z, 1.)
1612
1613        glPushAttrib(GL_ENABLE_BIT)
1614        glEnable(self.target)
1615        glBindTexture(self.target, self.id)
1616        glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT)
1617        glInterleavedArrays(GL_T4F_V4F, 0, array)
1618        glDrawArrays(GL_QUADS, 0, 4)
1619        glPopClientAttrib()
1620        glPopAttrib()
1621
1622    def blit_into(self, source, x, y, z):
1623        glBindTexture(self.target, self.id)
1624        source.blit_to_texture(self.target, self.level, x, y, z)
1625
1626    def get_region(self, x, y, width, height):
1627        return self.region_class(x, y, 0, width, height, self)
1628
1629    def get_transform(self, flip_x=False, flip_y=False, rotate=0):
1630        """Create a copy of this image applying a simple transformation.
1631
1632        The transformation is applied to the texture coordinates only;
1633        :py:meth:`~pyglet.image.ImageData.get_image_data` will return the untransformed data.  The
1634        transformation is applied around the anchor point.
1635
1636        :Parameters:
1637            `flip_x` : bool
1638                If True, the returned image will be flipped horizontally.
1639            `flip_y` : bool
1640                If True, the returned image will be flipped vertically.
1641            `rotate` : int
1642                Degrees of clockwise rotation of the returned image.  Only
1643                90-degree increments are supported.
1644
1645        :rtype: :py:class:`~pyglet.image.TextureRegion`
1646        """
1647        transform = self.get_region(0, 0, self.width, self.height)
1648        bl, br, tr, tl = 0, 1, 2, 3
1649        transform.anchor_x = self.anchor_x
1650        transform.anchor_y = self.anchor_y
1651        if flip_x:
1652            bl, br, tl, tr = br, bl, tr, tl
1653            transform.anchor_x = self.width - self.anchor_x
1654        if flip_y:
1655            bl, br, tl, tr = tl, tr, bl, br
1656            transform.anchor_y = self.height - self.anchor_y
1657        rotate %= 360
1658        if rotate < 0:
1659            rotate += 360
1660        if rotate == 0:
1661            pass
1662        elif rotate == 90:
1663            bl, br, tr, tl = br, tr, tl, bl
1664            transform.anchor_x, transform.anchor_y = \
1665                transform.anchor_y, \
1666                transform.width - transform.anchor_x
1667        elif rotate == 180:
1668            bl, br, tr, tl = tr, tl, bl, br
1669            transform.anchor_x = transform.width - transform.anchor_x
1670            transform.anchor_y = transform.height - transform.anchor_y
1671        elif rotate == 270:
1672            bl, br, tr, tl = tl, bl, br, tr
1673            transform.anchor_x, transform.anchor_y = \
1674                transform.height - transform.anchor_y, \
1675                transform.anchor_x
1676        else:
1677            assert False, 'Only 90 degree rotations are supported.'
1678        if rotate in (90, 270):
1679            transform.width, transform.height = transform.height, transform.width
1680        transform._set_tex_coords_order(bl, br, tr, tl)
1681        return transform
1682
1683    def _set_tex_coords_order(self, bl, br, tr, tl):
1684        tex_coords = (self.tex_coords[:3],
1685                      self.tex_coords[3:6],
1686                      self.tex_coords[6:9],
1687                      self.tex_coords[9:])
1688        self.tex_coords = tex_coords[bl] + tex_coords[br] + tex_coords[tr] + tex_coords[tl]
1689
1690        order = self.tex_coords_order
1691        self.tex_coords_order = (order[bl], order[br], order[tr], order[tl])
1692
1693
1694class TextureRegion(Texture):
1695    """A rectangular region of a texture, presented as if it were
1696    a separate texture.
1697    """
1698
1699    def __init__(self, x, y, z, width, height, owner):
1700        super(TextureRegion, self).__init__(width, height, owner.target, owner.id)
1701
1702        self.x = x
1703        self.y = y
1704        self.z = z
1705        self.owner = owner
1706        owner_u1 = owner.tex_coords[0]
1707        owner_v1 = owner.tex_coords[1]
1708        owner_u2 = owner.tex_coords[3]
1709        owner_v2 = owner.tex_coords[7]
1710        scale_u = owner_u2 - owner_u1
1711        scale_v = owner_v2 - owner_v1
1712        u1 = x / owner.width * scale_u + owner_u1
1713        v1 = y / owner.height * scale_v + owner_v1
1714        u2 = (x + width) / owner.width * scale_u + owner_u1
1715        v2 = (y + height) / owner.height * scale_v + owner_v1
1716        r = z / owner.images + owner.tex_coords[2]
1717        self.tex_coords = (u1, v1, r, u2, v1, r, u2, v2, r, u1, v2, r)
1718
1719    def get_image_data(self):
1720        image_data = self.owner.get_image_data(self.z)
1721        return image_data.get_region(self.x, self.y, self.width, self.height)
1722
1723    def get_region(self, x, y, width, height):
1724        x += self.x
1725        y += self.y
1726        region = self.region_class(x, y, self.z, width, height, self.owner)
1727        region._set_tex_coords_order(*self.tex_coords_order)
1728        return region
1729
1730    def blit_into(self, source, x, y, z):
1731        self.owner.blit_into(source, x + self.x, y + self.y, z + self.z)
1732
1733    def __del__(self):
1734        # only the owner Texture should handle deletion
1735        pass
1736
1737
1738Texture.region_class = TextureRegion
1739
1740
1741class Texture3D(Texture, UniformTextureSequence):
1742    """A texture with more than one image slice.
1743
1744    Use `create_for_images` or `create_for_image_grid` classmethod to
1745    construct.
1746    """
1747    item_width = 0
1748    item_height = 0
1749    items = ()
1750
1751    @classmethod
1752    def create_for_images(cls, images, internalformat=GL_RGBA):
1753        item_width = images[0].width
1754        item_height = images[0].height
1755        for image in images:
1756            if image.width != item_width or image.height != item_height:
1757                raise ImageException('Images do not have same dimensions.')
1758
1759        depth = len(images)
1760        if not gl_info.have_version(2, 0):
1761            depth = _nearest_pow2(depth)
1762
1763        texture = cls.create_for_size(GL_TEXTURE_3D, item_width, item_height)
1764        if images[0].anchor_x or images[0].anchor_y:
1765            texture.anchor_x = images[0].anchor_x
1766            texture.anchor_y = images[0].anchor_y
1767
1768        texture.images = depth
1769
1770        blank = (GLubyte * (texture.width * texture.height * texture.images))()
1771        glBindTexture(texture.target, texture.id)
1772        glTexImage3D(texture.target, texture.level,
1773                     internalformat,
1774                     texture.width, texture.height, texture.images, 0,
1775                     GL_ALPHA, GL_UNSIGNED_BYTE,
1776                     blank)
1777
1778        items = []
1779        for i, image in enumerate(images):
1780            item = cls.region_class(0, 0, i, item_width, item_height, texture)
1781            items.append(item)
1782            image.blit_to_texture(texture.target, texture.level,
1783                                  image.anchor_x, image.anchor_y, i)
1784
1785        glFlush()
1786
1787        texture.items = items
1788        texture.item_width = item_width
1789        texture.item_height = item_height
1790        return texture
1791
1792    @classmethod
1793    def create_for_image_grid(cls, grid, internalformat=GL_RGBA):
1794        return cls.create_for_images(grid[:], internalformat)
1795
1796    def __len__(self):
1797        return len(self.items)
1798
1799    def __getitem__(self, index):
1800        return self.items[index]
1801
1802    def __setitem__(self, index, value):
1803        if type(index) is slice:
1804            for item, image in zip(self[index], value):
1805                image.blit_to_texture(self.target, self.level,
1806                                      image.anchor_x, image.anchor_y, item.z)
1807        else:
1808            value.blit_to_texture(self.target, self.level,
1809                                  value.anchor_x, value.anchor_y, self[index].z)
1810
1811    def __iter__(self):
1812        return iter(self.items)
1813
1814
1815class TileableTexture(Texture):
1816    """A texture that can be tiled efficiently.
1817
1818    Use :py:class:`~pyglet.image.create_for_Image` classmethod to construct.
1819    """
1820
1821    def __init__(self, width, height, target, id):
1822        if not _is_pow2(width) or not _is_pow2(height):
1823            raise ImageException(
1824                'TileableTexture requires dimensions that are powers of 2')
1825        super(TileableTexture, self).__init__(width, height, target, id)
1826
1827    def get_region(self, x, y, width, height):
1828        raise ImageException('Cannot get region of %r' % self)
1829
1830    def blit_tiled(self, x, y, z, width, height):
1831        """Blit this texture tiled over the given area.
1832
1833        The image will be tiled with the bottom-left corner of the destination
1834        rectangle aligned with the anchor point of this texture.
1835        """
1836        u1 = self.anchor_x / self.width
1837        v1 = self.anchor_y / self.height
1838        u2 = u1 + width / self.width
1839        v2 = v1 + height / self.height
1840        w, h = width, height
1841        t = self.tex_coords
1842        array = (GLfloat * 32)(
1843            u1, v1, t[2], 1.,
1844            x, y, z, 1.,
1845            u2, v1, t[5], 1.,
1846            x + w, y, z, 1.,
1847            u2, v2, t[8], 1.,
1848            x + w, y + h, z, 1.,
1849            u1, v2, t[11], 1.,
1850            x, y + h, z, 1.)
1851
1852        glPushAttrib(GL_ENABLE_BIT)
1853        glEnable(self.target)
1854        glBindTexture(self.target, self.id)
1855        glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT)
1856        glInterleavedArrays(GL_T4F_V4F, 0, array)
1857        glDrawArrays(GL_QUADS, 0, 4)
1858        glPopClientAttrib()
1859        glPopAttrib()
1860
1861    @classmethod
1862    def create_for_image(cls, image):
1863        if not _is_pow2(image.width) or not _is_pow2(image.height):
1864            # Potentially unnecessary conversion if a GL format exists.
1865            image = image.get_image_data()
1866            texture_width = _nearest_pow2(image.width)
1867            texture_height = _nearest_pow2(image.height)
1868            newdata = c_buffer(texture_width * texture_height * 4)
1869            gluScaleImage(GL_RGBA,
1870                          image.width, image.height,
1871                          GL_UNSIGNED_BYTE,
1872                          image.get_data('RGBA', image.width * 4),
1873                          texture_width,
1874                          texture_height,
1875                          GL_UNSIGNED_BYTE,
1876                          newdata)
1877            image = ImageData(texture_width, texture_height, 'RGBA',
1878                              newdata)
1879
1880        image = image.get_image_data()
1881        return image.create_texture(cls)
1882
1883
1884class DepthTexture(Texture):
1885    """A texture with depth samples (typically 24-bit)."""
1886
1887    def blit_into(self, source, x, y, z):
1888        glBindTexture(self.target, self.id)
1889        source.blit_to_texture(self.level, x, y, z)
1890
1891
1892class BufferManager:
1893    """Manages the set of framebuffers for a context.
1894
1895    Use :py:func:`~pyglet.image.get_buffer_manager` to obtain the instance of this class for the
1896    current context.
1897    """
1898
1899    def __init__(self):
1900        self.color_buffer = None
1901        self.depth_buffer = None
1902
1903        aux_buffers = GLint()
1904        glGetIntegerv(GL_AUX_BUFFERS, byref(aux_buffers))
1905        self.free_aux_buffers = [GL_AUX0,
1906                                 GL_AUX1,
1907                                 GL_AUX2,
1908                                 GL_AUX3][:aux_buffers.value]
1909
1910        stencil_bits = GLint()
1911        glGetIntegerv(GL_STENCIL_BITS, byref(stencil_bits))
1912        self.free_stencil_bits = list(range(stencil_bits.value))
1913
1914        self.refs = []
1915
1916    def get_viewport(self):
1917        """Get the current OpenGL viewport dimensions.
1918
1919        :rtype: 4-tuple of float.
1920        :return: Left, top, right and bottom dimensions.
1921        """
1922        viewport = (GLint * 4)()
1923        glGetIntegerv(GL_VIEWPORT, viewport)
1924        return viewport
1925
1926    def get_color_buffer(self):
1927        """Get the color buffer.
1928
1929        :rtype: :py:class:`~pyglet.image.ColorBufferImage`
1930        """
1931        viewport = self.get_viewport()
1932        viewport_width = viewport[2]
1933        viewport_height = viewport[3]
1934        if (not self.color_buffer or
1935                    viewport_width != self.color_buffer.width or
1936                    viewport_height != self.color_buffer.height):
1937            self.color_buffer = ColorBufferImage(*viewport)
1938        return self.color_buffer
1939
1940    def get_aux_buffer(self):
1941        """Get a free auxiliary buffer.
1942
1943        If not aux buffers are available, `ImageException` is raised.  Buffers
1944        are released when they are garbage collected.
1945
1946        :rtype: :py:class:`~pyglet.image.ColorBufferImage`
1947        """
1948        if not self.free_aux_buffers:
1949            raise ImageException('No free aux buffer is available.')
1950
1951        gl_buffer = self.free_aux_buffers.pop(0)
1952        viewport = self.get_viewport()
1953        buffer = ColorBufferImage(*viewport)
1954        buffer.gl_buffer = gl_buffer
1955
1956        def release_buffer(ref, self=self):
1957            self.free_aux_buffers.insert(0, gl_buffer)
1958
1959        self.refs.append(weakref.ref(buffer, release_buffer))
1960
1961        return buffer
1962
1963    def get_depth_buffer(self):
1964        """Get the depth buffer.
1965
1966        :rtype: :py:class:`~pyglet.image.DepthBufferImage`
1967        """
1968        viewport = self.get_viewport()
1969        viewport_width = viewport[2]
1970        viewport_height = viewport[3]
1971        if (not self.depth_buffer or
1972                    viewport_width != self.depth_buffer.width or
1973                    viewport_height != self.depth_buffer.height):
1974            self.depth_buffer = DepthBufferImage(*viewport)
1975        return self.depth_buffer
1976
1977    def get_buffer_mask(self):
1978        """Get a free bitmask buffer.
1979
1980        A bitmask buffer is a buffer referencing a single bit in the stencil
1981        buffer.  If no bits are free, `ImageException` is raised.  Bits are
1982        released when the bitmask buffer is garbage collected.
1983
1984        :rtype: :py:class:`~pyglet.image.BufferImageMask`
1985        """
1986        if not self.free_stencil_bits:
1987            raise ImageException('No free stencil bits are available.')
1988
1989        stencil_bit = self.free_stencil_bits.pop(0)
1990        x, y, width, height = self.get_viewport()
1991        buffer = BufferImageMask(x, y, width, height)
1992        buffer.stencil_bit = stencil_bit
1993
1994        def release_buffer(ref, self=self):
1995            self.free_stencil_bits.insert(0, stencil_bit)
1996
1997        self.refs.append(weakref.ref(buffer, release_buffer))
1998
1999        return buffer
2000
2001
2002def get_buffer_manager():
2003    """Get the buffer manager for the current OpenGL context.
2004
2005    :rtype: :py:class:`~pyglet.image.BufferManager`
2006    """
2007    context = pyglet.gl.current_context
2008    if not hasattr(context, 'image_buffer_manager'):
2009        context.image_buffer_manager = BufferManager()
2010    return context.image_buffer_manager
2011
2012
2013# XXX BufferImage could be generalised to support EXT_framebuffer_object's
2014# renderbuffer.
2015class BufferImage(AbstractImage):
2016    """An abstract framebuffer.
2017    """
2018    #: The OpenGL read and write target for this buffer.
2019    gl_buffer = GL_BACK
2020
2021    #: The OpenGL format constant for image data.
2022    gl_format = 0
2023
2024    #: The format string used for image data.
2025    format = ''
2026
2027    owner = None
2028
2029    # TODO: enable methods
2030
2031    def __init__(self, x, y, width, height):
2032        self.x = x
2033        self.y = y
2034        self.width = width
2035        self.height = height
2036
2037    def get_image_data(self):
2038        buffer = (GLubyte * (len(self.format) * self.width * self.height))()
2039
2040        x = self.x
2041        y = self.y
2042        if self.owner:
2043            x += self.owner.x
2044            y += self.owner.y
2045
2046        glReadBuffer(self.gl_buffer)
2047        glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT)
2048        glPixelStorei(GL_PACK_ALIGNMENT, 1)
2049        glReadPixels(x, y, self.width, self.height,
2050                     self.gl_format, GL_UNSIGNED_BYTE, buffer)
2051        glPopClientAttrib()
2052
2053        return ImageData(self.width, self.height, self.format, buffer)
2054
2055    def get_region(self, x, y, width, height):
2056        if self.owner:
2057            return self.owner.get_region(x + self.x, y + self.y, width, height)
2058
2059        region = self.__class__(x + self.x, y + self.y, width, height)
2060        region.gl_buffer = self.gl_buffer
2061        region.owner = self
2062        return region
2063
2064
2065class ColorBufferImage(BufferImage):
2066    """A color framebuffer.
2067
2068    This class is used to wrap both the primary color buffer (i.e., the back
2069    buffer) or any one of the auxiliary buffers.
2070    """
2071    gl_format = GL_RGBA
2072    format = 'RGBA'
2073
2074    def get_texture(self, rectangle=False, force_rectangle=False):
2075        texture = Texture.create(self.width, self.height, GL_RGBA,
2076                                 rectangle, force_rectangle)
2077        self.blit_to_texture(texture.target, texture.level,
2078                             self.anchor_x, self.anchor_y, 0)
2079        return texture
2080
2081    def blit_to_texture(self, target, level, x, y, z):
2082        glReadBuffer(self.gl_buffer)
2083        glCopyTexSubImage2D(target, level,
2084                            x - self.anchor_x, y - self.anchor_y,
2085                            self.x, self.y, self.width, self.height)
2086
2087
2088class DepthBufferImage(BufferImage):
2089    """The depth buffer.
2090    """
2091    gl_format = GL_DEPTH_COMPONENT
2092    format = 'L'
2093
2094    def get_texture(self, rectangle=False, force_rectangle=False):
2095        assert rectangle == False and force_rectangle == False, \
2096            'Depth textures cannot be rectangular'
2097        if not _is_pow2(self.width) or not _is_pow2(self.height):
2098            raise ImageException(
2099                'Depth texture requires that buffer dimensions be powers of 2')
2100
2101        texture = DepthTexture.create_for_size(GL_TEXTURE_2D, self.width, self.height)
2102        if self.anchor_x or self.anchor_y:
2103            texture.anchor_x = self.anchor_x
2104            texture.anchor_y = self.anchor_y
2105
2106        glReadBuffer(self.gl_buffer)
2107        glCopyTexImage2D(texture.target, 0,
2108                         GL_DEPTH_COMPONENT,
2109                         self.x, self.y, self.width, self.height,
2110                         0)
2111        return texture
2112
2113    def blit_to_texture(self, target, level, x, y, z):
2114        glReadBuffer(self.gl_buffer)
2115        glCopyTexSubImage2D(target, level,
2116                            x - self.anchor_x, y - self.anchor_y,
2117                            self.x, self.y, self.width, self.height)
2118
2119
2120class BufferImageMask(BufferImage):
2121    """A single bit of the stencil buffer.
2122    """
2123    gl_format = GL_STENCIL_INDEX
2124    format = 'L'
2125
2126    # TODO mask methods
2127
2128
2129class ImageGrid(AbstractImage, AbstractImageSequence):
2130    """An imaginary grid placed over an image allowing easy access to
2131    regular regions of that image.
2132
2133    The grid can be accessed either as a complete image, or as a sequence
2134    of images.  The most useful applications are to access the grid
2135    as a :py:class:`~pyglet.image.TextureGrid`::
2136
2137        image_grid = ImageGrid(...)
2138        texture_grid = image_grid.get_texture_sequence()
2139
2140    or as a :py:class:`~pyglet.image.Texture3D`::
2141
2142        image_grid = ImageGrid(...)
2143        texture_3d = Texture3D.create_for_image_grid(image_grid)
2144
2145    """
2146    _items = ()
2147    _texture_grid = None
2148
2149    def __init__(self, image, rows, columns,
2150                 item_width=None, item_height=None,
2151                 row_padding=0, column_padding=0):
2152        """Construct a grid for the given image.
2153
2154        You can specify parameters for the grid, for example setting
2155        the padding between cells.  Grids are always aligned to the
2156        bottom-left corner of the image.
2157
2158        :Parameters:
2159            `image` : AbstractImage
2160                Image over which to construct the grid.
2161            `rows` : int
2162                Number of rows in the grid.
2163            `columns` : int
2164                Number of columns in the grid.
2165            `item_width` : int
2166                Width of each column.  If unspecified, is calculated such
2167                that the entire image width is used.
2168            `item_height` : int
2169                Height of each row.  If unspecified, is calculated such that
2170                the entire image height is used.
2171            `row_padding` : int
2172                Pixels separating adjacent rows.  The padding is only
2173                inserted between rows, not at the edges of the grid.
2174            `column_padding` : int
2175                Pixels separating adjacent columns.  The padding is only
2176                inserted between columns, not at the edges of the grid.
2177        """
2178        super(ImageGrid, self).__init__(image.width, image.height)
2179
2180        if item_width is None:
2181            item_width = (image.width - column_padding * (columns - 1)) // columns
2182        if item_height is None:
2183            item_height = (image.height - row_padding * (rows - 1)) // rows
2184        self.image = image
2185        self.rows = rows
2186        self.columns = columns
2187        self.item_width = item_width
2188        self.item_height = item_height
2189        self.row_padding = row_padding
2190        self.column_padding = column_padding
2191
2192    def get_texture(self, rectangle=False, force_rectangle=False):
2193        return self.image.get_texture(rectangle, force_rectangle)
2194
2195    def get_image_data(self):
2196        return self.image.get_image_data()
2197
2198    def get_texture_sequence(self):
2199        if not self._texture_grid:
2200            self._texture_grid = TextureGrid(self)
2201        return self._texture_grid
2202
2203    def __len__(self):
2204        return self.rows * self.columns
2205
2206    def _update_items(self):
2207        if not self._items:
2208            self._items = []
2209            y = 0
2210            for row in range(self.rows):
2211                x = 0
2212                for col in range(self.columns):
2213                    self._items.append(self.image.get_region(
2214                        x, y, self.item_width, self.item_height))
2215                    x += self.item_width + self.column_padding
2216                y += self.item_height + self.row_padding
2217
2218    def __getitem__(self, index):
2219        self._update_items()
2220        if type(index) is tuple:
2221            row, column = index
2222            assert row >= 0 and column >= 0 and row < self.rows and column < self.columns
2223            return self._items[row * self.columns + column]
2224        else:
2225            return self._items[index]
2226
2227    def __iter__(self):
2228        self._update_items()
2229        return iter(self._items)
2230
2231
2232class TextureGrid(TextureRegion, UniformTextureSequence):
2233    """A texture containing a regular grid of texture regions.
2234
2235    To construct, create an :py:class:`~pyglet.image.ImageGrid` first::
2236
2237        image_grid = ImageGrid(...)
2238        texture_grid = TextureGrid(image_grid)
2239
2240    The texture grid can be accessed as a single texture, or as a sequence
2241    of :py:class:`~pyglet.image.TextureRegion`.  When accessing as a sequence, you can specify
2242    integer indexes, in which the images are arranged in rows from the
2243    bottom-left to the top-right::
2244
2245        # assume the texture_grid is 3x3:
2246        current_texture = texture_grid[3] # get the middle-left image
2247
2248    You can also specify tuples in the sequence methods, which are addressed
2249    as ``row, column``::
2250
2251        # equivalent to the previous example:
2252        current_texture = texture_grid[1, 0]
2253
2254    When using tuples in a slice, the returned sequence is over the
2255    rectangular region defined by the slice::
2256
2257        # returns center, center-right, center-top, top-right images in that
2258        # order:
2259        images = texture_grid[(1,1):]
2260        # equivalent to
2261        images = texture_grid[(1,1):(3,3)]
2262
2263    """
2264    items = ()
2265    rows = 1
2266    columns = 1
2267    item_width = 0
2268    item_height = 0
2269
2270    def __init__(self, grid):
2271        image = grid.get_texture()
2272        if isinstance(image, TextureRegion):
2273            owner = image.owner
2274        else:
2275            owner = image
2276
2277        super(TextureGrid, self).__init__(
2278            image.x, image.y, image.z, image.width, image.height, owner)
2279
2280        items = []
2281        y = 0
2282        for row in range(grid.rows):
2283            x = 0
2284            for col in range(grid.columns):
2285                items.append(
2286                    self.get_region(x, y, grid.item_width, grid.item_height))
2287                x += grid.item_width + grid.column_padding
2288            y += grid.item_height + grid.row_padding
2289
2290        self.items = items
2291        self.rows = grid.rows
2292        self.columns = grid.columns
2293        self.item_width = grid.item_width
2294        self.item_height = grid.item_height
2295
2296    def get(self, row, column):
2297        return self[(row, column)]
2298
2299    def __getitem__(self, index):
2300        if type(index) is slice:
2301            if type(index.start) is not tuple and type(index.stop) is not tuple:
2302                return self.items[index]
2303            else:
2304                row1 = 0
2305                col1 = 0
2306                row2 = self.rows
2307                col2 = self.columns
2308                if type(index.start) is tuple:
2309                    row1, col1 = index.start
2310                elif type(index.start) is int:
2311                    row1 = index.start // self.columns
2312                    col1 = index.start % self.columns
2313                assert row1 >= 0 and col1 >= 0 and row1 < self.rows and col1 < self.columns
2314
2315                if type(index.stop) is tuple:
2316                    row2, col2 = index.stop
2317                elif type(index.stop) is int:
2318                    row2 = index.stop // self.columns
2319                    col2 = index.stop % self.columns
2320                assert row2 >= 0 and col2 >= 0 and row2 <= self.rows and col2 <= self.columns
2321
2322                result = []
2323                i = row1 * self.columns
2324                for row in range(row1, row2):
2325                    result += self.items[i + col1:i + col2]
2326                    i += self.columns
2327                return result
2328        else:
2329            if type(index) is tuple:
2330                row, column = index
2331                assert row >= 0 and column >= 0 and row < self.rows and column < self.columns
2332                return self.items[row * self.columns + column]
2333            elif type(index) is int:
2334                return self.items[index]
2335
2336    def __setitem__(self, index, value):
2337        if type(index) is slice:
2338            for region, image in zip(self[index], value):
2339                if image.width != self.item_width or image.height != self.item_height:
2340                    raise ImageException('Image has incorrect dimensions')
2341                image.blit_into(region, image.anchor_x, image.anchor_y, 0)
2342        else:
2343            image = value
2344            if image.width != self.item_width or image.height != self.item_height:
2345                raise ImageException('Image has incorrect dimensions')
2346            image.blit_into(self[index], image.anchor_x, image.anchor_y, 0)
2347
2348    def __len__(self):
2349        return len(self.items)
2350
2351    def __iter__(self):
2352        return iter(self.items)
2353
2354
2355# Initialise default codecs
2356add_default_image_codecs()
2357