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