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