pyglet 2.1.12__py3-none-any.whl → 3.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. pyglet/__init__.py +67 -61
  2. pyglet/__init__.pyi +15 -8
  3. pyglet/app/__init__.py +22 -13
  4. pyglet/app/async_app.py +212 -0
  5. pyglet/app/base.py +2 -1
  6. pyglet/app/{xlib.py → linux.py} +3 -3
  7. pyglet/config/__init__.py +101 -0
  8. pyglet/config/gl/__init__.py +30 -0
  9. pyglet/config/gl/egl.py +120 -0
  10. pyglet/config/gl/macos.py +262 -0
  11. pyglet/config/gl/windows.py +267 -0
  12. pyglet/config/gl/x11.py +142 -0
  13. pyglet/customtypes.py +43 -2
  14. pyglet/display/__init__.py +8 -6
  15. pyglet/display/base.py +3 -63
  16. pyglet/display/cocoa.py +12 -17
  17. pyglet/display/emscripten.py +39 -0
  18. pyglet/display/headless.py +23 -30
  19. pyglet/display/wayland.py +157 -0
  20. pyglet/display/win32.py +4 -17
  21. pyglet/display/xlib.py +19 -27
  22. pyglet/display/xlib_vidmoderestore.py +2 -2
  23. pyglet/enums.py +183 -0
  24. pyglet/event.py +0 -1
  25. pyglet/experimental/geoshader_sprite.py +15 -13
  26. pyglet/experimental/hidraw.py +6 -15
  27. pyglet/experimental/multitexture_sprite.py +31 -19
  28. pyglet/experimental/particles.py +13 -35
  29. pyglet/font/__init__.py +251 -85
  30. pyglet/font/base.py +116 -61
  31. pyglet/font/dwrite/__init__.py +349 -204
  32. pyglet/font/dwrite/dwrite_lib.py +27 -5
  33. pyglet/font/fontconfig.py +14 -6
  34. pyglet/font/freetype.py +138 -87
  35. pyglet/font/freetype_lib.py +19 -0
  36. pyglet/font/group.py +179 -0
  37. pyglet/font/harfbuzz/__init__.py +3 -3
  38. pyglet/font/pyodide_js.py +310 -0
  39. pyglet/font/quartz.py +319 -126
  40. pyglet/font/ttf.py +45 -3
  41. pyglet/font/user.py +14 -19
  42. pyglet/font/win32.py +45 -21
  43. pyglet/graphics/__init__.py +8 -787
  44. pyglet/graphics/allocation.py +115 -1
  45. pyglet/graphics/api/__init__.py +77 -0
  46. pyglet/graphics/api/base.py +299 -0
  47. pyglet/graphics/api/gl/__init__.py +58 -0
  48. pyglet/graphics/api/gl/base.py +24 -0
  49. pyglet/graphics/{vertexbuffer.py → api/gl/buffer.py} +104 -159
  50. pyglet/graphics/api/gl/cocoa/context.py +76 -0
  51. pyglet/graphics/api/gl/context.py +391 -0
  52. pyglet/graphics/api/gl/default_shaders.py +0 -0
  53. pyglet/graphics/api/gl/draw.py +627 -0
  54. pyglet/graphics/api/gl/egl/__init__.py +0 -0
  55. pyglet/graphics/api/gl/egl/context.py +92 -0
  56. pyglet/graphics/api/gl/enums.py +76 -0
  57. pyglet/graphics/api/gl/framebuffer.py +315 -0
  58. pyglet/graphics/api/gl/gl.py +5463 -0
  59. pyglet/graphics/api/gl/gl_info.py +188 -0
  60. pyglet/graphics/api/gl/global_opengl.py +226 -0
  61. pyglet/{gl → graphics/api/gl}/lib.py +34 -18
  62. pyglet/graphics/api/gl/shader.py +1476 -0
  63. pyglet/graphics/api/gl/shapes.py +55 -0
  64. pyglet/graphics/api/gl/sprite.py +102 -0
  65. pyglet/graphics/api/gl/state.py +219 -0
  66. pyglet/graphics/api/gl/text.py +190 -0
  67. pyglet/graphics/api/gl/texture.py +1526 -0
  68. pyglet/graphics/{vertexarray.py → api/gl/vertexarray.py} +11 -13
  69. pyglet/graphics/api/gl/vertexdomain.py +751 -0
  70. pyglet/graphics/api/gl/win32/__init__.py +0 -0
  71. pyglet/graphics/api/gl/win32/context.py +108 -0
  72. pyglet/graphics/api/gl/win32/wgl_info.py +24 -0
  73. pyglet/graphics/api/gl/xlib/__init__.py +0 -0
  74. pyglet/graphics/api/gl/xlib/context.py +174 -0
  75. pyglet/{gl → graphics/api/gl/xlib}/glx_info.py +26 -31
  76. pyglet/graphics/api/gl1/__init__.py +0 -0
  77. pyglet/{gl → graphics/api/gl1}/gl_compat.py +3 -2
  78. pyglet/graphics/api/gl2/__init__.py +0 -0
  79. pyglet/graphics/api/gl2/buffer.py +320 -0
  80. pyglet/graphics/api/gl2/draw.py +600 -0
  81. pyglet/graphics/api/gl2/global_opengl.py +122 -0
  82. pyglet/graphics/api/gl2/shader.py +200 -0
  83. pyglet/graphics/api/gl2/shapes.py +51 -0
  84. pyglet/graphics/api/gl2/sprite.py +79 -0
  85. pyglet/graphics/api/gl2/text.py +175 -0
  86. pyglet/graphics/api/gl2/vertexdomain.py +364 -0
  87. pyglet/graphics/api/webgl/__init__.py +233 -0
  88. pyglet/graphics/api/webgl/buffer.py +302 -0
  89. pyglet/graphics/api/webgl/context.py +234 -0
  90. pyglet/graphics/api/webgl/draw.py +590 -0
  91. pyglet/graphics/api/webgl/enums.py +76 -0
  92. pyglet/graphics/api/webgl/framebuffer.py +360 -0
  93. pyglet/graphics/api/webgl/gl.py +1537 -0
  94. pyglet/graphics/api/webgl/gl_info.py +130 -0
  95. pyglet/graphics/api/webgl/shader.py +1346 -0
  96. pyglet/graphics/api/webgl/shapes.py +92 -0
  97. pyglet/graphics/api/webgl/sprite.py +102 -0
  98. pyglet/graphics/api/webgl/state.py +227 -0
  99. pyglet/graphics/api/webgl/text.py +187 -0
  100. pyglet/graphics/api/webgl/texture.py +1227 -0
  101. pyglet/graphics/api/webgl/vertexarray.py +54 -0
  102. pyglet/graphics/api/webgl/vertexdomain.py +616 -0
  103. pyglet/graphics/api/webgl/webgl_js.pyi +307 -0
  104. pyglet/{image → graphics}/atlas.py +33 -32
  105. pyglet/graphics/base.py +10 -0
  106. pyglet/graphics/buffer.py +245 -0
  107. pyglet/graphics/draw.py +578 -0
  108. pyglet/graphics/framebuffer.py +26 -0
  109. pyglet/graphics/instance.py +178 -69
  110. pyglet/graphics/shader.py +267 -1553
  111. pyglet/graphics/state.py +83 -0
  112. pyglet/graphics/texture.py +703 -0
  113. pyglet/graphics/vertexdomain.py +695 -538
  114. pyglet/gui/ninepatch.py +10 -10
  115. pyglet/gui/widgets.py +120 -10
  116. pyglet/image/__init__.py +20 -1973
  117. pyglet/image/animation.py +12 -12
  118. pyglet/image/base.py +730 -0
  119. pyglet/image/codecs/__init__.py +9 -0
  120. pyglet/image/codecs/bmp.py +53 -30
  121. pyglet/image/codecs/dds.py +53 -31
  122. pyglet/image/codecs/gdiplus.py +38 -14
  123. pyglet/image/codecs/gdkpixbuf2.py +0 -2
  124. pyglet/image/codecs/js_image.py +99 -0
  125. pyglet/image/codecs/ktx2.py +161 -0
  126. pyglet/image/codecs/pil.py +1 -1
  127. pyglet/image/codecs/png.py +1 -1
  128. pyglet/image/codecs/wic.py +11 -2
  129. pyglet/info.py +26 -24
  130. pyglet/input/__init__.py +8 -0
  131. pyglet/input/base.py +163 -105
  132. pyglet/input/controller.py +13 -19
  133. pyglet/input/controller_db.py +39 -24
  134. pyglet/input/emscripten/__init__.py +18 -0
  135. pyglet/input/emscripten/gamepad_js.py +397 -0
  136. pyglet/input/linux/__init__.py +11 -5
  137. pyglet/input/linux/evdev.py +10 -11
  138. pyglet/input/linux/x11_xinput.py +2 -2
  139. pyglet/input/linux/x11_xinput_tablet.py +1 -1
  140. pyglet/input/macos/__init__.py +7 -2
  141. pyglet/input/macos/darwin_gc.py +559 -0
  142. pyglet/input/win32/__init__.py +1 -1
  143. pyglet/input/win32/directinput.py +34 -29
  144. pyglet/input/win32/xinput.py +11 -61
  145. pyglet/lib.py +3 -3
  146. pyglet/libs/__init__.py +1 -1
  147. pyglet/{gl → libs/darwin}/agl.py +1 -1
  148. pyglet/libs/darwin/cocoapy/__init__.py +2 -2
  149. pyglet/libs/darwin/cocoapy/cocoahelpers.py +181 -0
  150. pyglet/libs/darwin/cocoapy/cocoalibs.py +31 -0
  151. pyglet/libs/darwin/cocoapy/cocoatypes.py +27 -0
  152. pyglet/libs/darwin/cocoapy/runtime.py +81 -45
  153. pyglet/libs/darwin/coreaudio.py +4 -4
  154. pyglet/{gl → libs/darwin}/lib_agl.py +9 -8
  155. pyglet/libs/darwin/quartzkey.py +1 -3
  156. pyglet/libs/egl/__init__.py +2 -0
  157. pyglet/libs/egl/egl_lib.py +576 -0
  158. pyglet/libs/egl/eglext.py +51 -5
  159. pyglet/libs/linux/__init__.py +0 -0
  160. pyglet/libs/linux/egl/__init__.py +0 -0
  161. pyglet/libs/linux/egl/eglext.py +22 -0
  162. pyglet/libs/linux/glx/__init__.py +0 -0
  163. pyglet/{gl → libs/linux/glx}/glx.py +13 -14
  164. pyglet/{gl → libs/linux/glx}/glxext_arb.py +408 -192
  165. pyglet/{gl → libs/linux/glx}/glxext_mesa.py +1 -1
  166. pyglet/{gl → libs/linux/glx}/glxext_nv.py +345 -164
  167. pyglet/{gl → libs/linux/glx}/lib_glx.py +3 -2
  168. pyglet/libs/linux/wayland/__init__.py +0 -0
  169. pyglet/libs/linux/wayland/client.py +1068 -0
  170. pyglet/libs/linux/wayland/lib_wayland.py +207 -0
  171. pyglet/libs/linux/wayland/wayland_egl.py +38 -0
  172. pyglet/libs/{wayland → linux/wayland}/xkbcommon.py +26 -0
  173. pyglet/libs/{x11 → linux/x11}/xf86vmode.py +4 -4
  174. pyglet/libs/{x11 → linux/x11}/xinerama.py +2 -2
  175. pyglet/libs/{x11 → linux/x11}/xinput.py +10 -10
  176. pyglet/libs/linux/x11/xrandr.py +0 -0
  177. pyglet/libs/{x11 → linux/x11}/xrender.py +1 -1
  178. pyglet/libs/shared/__init__.py +0 -0
  179. pyglet/libs/shared/spirv/__init__.py +0 -0
  180. pyglet/libs/shared/spirv/lib_shaderc.py +85 -0
  181. pyglet/libs/shared/spirv/lib_spirv_cross.py +126 -0
  182. pyglet/libs/win32/__init__.py +27 -5
  183. pyglet/libs/win32/constants.py +59 -48
  184. pyglet/libs/win32/context_managers.py +20 -3
  185. pyglet/libs/win32/dinput.py +105 -88
  186. pyglet/{gl → libs/win32}/lib_wgl.py +52 -26
  187. pyglet/libs/win32/types.py +58 -23
  188. pyglet/{gl → libs/win32}/wgl.py +32 -25
  189. pyglet/{gl → libs/win32}/wglext_arb.py +364 -2
  190. pyglet/media/__init__.py +9 -10
  191. pyglet/media/codecs/__init__.py +12 -1
  192. pyglet/media/codecs/base.py +99 -96
  193. pyglet/media/codecs/ffmpeg.py +2 -2
  194. pyglet/media/codecs/ffmpeg_lib/libavformat.py +3 -8
  195. pyglet/media/codecs/webaudio_pyodide.py +111 -0
  196. pyglet/media/drivers/__init__.py +9 -4
  197. pyglet/media/drivers/base.py +4 -4
  198. pyglet/media/drivers/openal/__init__.py +1 -1
  199. pyglet/media/drivers/openal/adaptation.py +3 -3
  200. pyglet/media/drivers/pulse/__init__.py +1 -1
  201. pyglet/media/drivers/pulse/adaptation.py +3 -3
  202. pyglet/media/drivers/pyodide_js/__init__.py +8 -0
  203. pyglet/media/drivers/pyodide_js/adaptation.py +288 -0
  204. pyglet/media/drivers/xaudio2/adaptation.py +3 -3
  205. pyglet/media/player.py +276 -193
  206. pyglet/media/player_worker_thread.py +1 -1
  207. pyglet/model/__init__.py +39 -29
  208. pyglet/model/codecs/base.py +4 -4
  209. pyglet/model/codecs/gltf.py +3 -3
  210. pyglet/model/codecs/obj.py +71 -43
  211. pyglet/resource.py +129 -78
  212. pyglet/shapes.py +147 -177
  213. pyglet/sprite.py +47 -164
  214. pyglet/text/__init__.py +44 -54
  215. pyglet/text/caret.py +12 -7
  216. pyglet/text/document.py +19 -17
  217. pyglet/text/formats/html.py +2 -2
  218. pyglet/text/formats/structured.py +10 -40
  219. pyglet/text/layout/__init__.py +20 -13
  220. pyglet/text/layout/base.py +176 -287
  221. pyglet/text/layout/incremental.py +9 -10
  222. pyglet/text/layout/scrolling.py +7 -95
  223. pyglet/window/__init__.py +183 -172
  224. pyglet/window/cocoa/__init__.py +62 -51
  225. pyglet/window/cocoa/pyglet_delegate.py +2 -25
  226. pyglet/window/cocoa/pyglet_view.py +9 -8
  227. pyglet/window/dialog/__init__.py +184 -0
  228. pyglet/window/dialog/base.py +99 -0
  229. pyglet/window/dialog/darwin.py +121 -0
  230. pyglet/window/dialog/linux.py +72 -0
  231. pyglet/window/dialog/windows.py +194 -0
  232. pyglet/window/emscripten/__init__.py +779 -0
  233. pyglet/window/headless/__init__.py +44 -28
  234. pyglet/window/key.py +2 -0
  235. pyglet/window/mouse.py +2 -2
  236. pyglet/window/wayland/__init__.py +377 -0
  237. pyglet/window/win32/__init__.py +101 -46
  238. pyglet/window/xlib/__init__.py +104 -66
  239. {pyglet-2.1.12.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
  240. pyglet-3.0.dev1.dist-info/RECORD +322 -0
  241. {pyglet-2.1.12.dist-info → pyglet-3.0.dev1.dist-info}/WHEEL +1 -1
  242. pyglet/gl/__init__.py +0 -208
  243. pyglet/gl/base.py +0 -499
  244. pyglet/gl/cocoa.py +0 -309
  245. pyglet/gl/gl.py +0 -4625
  246. pyglet/gl/gl.pyi +0 -2320
  247. pyglet/gl/gl_compat.pyi +0 -3097
  248. pyglet/gl/gl_info.py +0 -190
  249. pyglet/gl/headless.py +0 -166
  250. pyglet/gl/wgl_info.py +0 -36
  251. pyglet/gl/wglext_nv.py +0 -1096
  252. pyglet/gl/win32.py +0 -268
  253. pyglet/gl/xlib.py +0 -295
  254. pyglet/image/buffer.py +0 -274
  255. pyglet/image/codecs/s3tc.py +0 -354
  256. pyglet/libs/x11/xrandr.py +0 -166
  257. pyglet-2.1.12.dist-info/RECORD +0 -234
  258. /pyglet/{libs/wayland → graphics/api/gl/cocoa}/__init__.py +0 -0
  259. /pyglet/libs/{egl → linux/egl}/egl.py +0 -0
  260. /pyglet/libs/{egl → linux/egl}/lib.py +0 -0
  261. /pyglet/libs/{ioctl.py → linux/ioctl.py} +0 -0
  262. /pyglet/libs/{wayland → linux/wayland}/gbm.py +0 -0
  263. /pyglet/libs/{x11 → linux/x11}/__init__.py +0 -0
  264. /pyglet/libs/{x11 → linux/x11}/cursorfont.py +0 -0
  265. /pyglet/libs/{x11 → linux/x11}/xlib.py +0 -0
  266. /pyglet/libs/{x11 → linux/x11}/xsync.py +0 -0
  267. {pyglet-2.1.12.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
pyglet/image/__init__.py CHANGED
@@ -58,7 +58,7 @@ If you are using OpenGL directly, you can access the image as a texture::
58
58
  immediately loaded as textures, whereas others go through an intermediate
59
59
  form). To use a texture with pyglet.gl::
60
60
 
61
- from pyglet.gl import *
61
+ from pyglet.graphics.api.gl import *
62
62
  glEnable(texture.target) # typically target is GL_TEXTURE_2D
63
63
  glBindTexture(texture.target, texture.id)
64
64
  # ... draw with the texture
@@ -93,60 +93,27 @@ Retrieving data with the format and pitch given in `ImageData.format` and
93
93
  use of the data in this arbitrary format).
94
94
 
95
95
  """
96
- from __future__ import annotations
97
96
 
98
- import re
99
- import weakref
97
+ from __future__ import annotations
100
98
 
101
- from abc import ABC, abstractmethod
102
- from typing import TYPE_CHECKING
103
- from ctypes import byref, c_int, c_ubyte, c_uint, sizeof
99
+ from typing import TYPE_CHECKING, BinaryIO, Sequence
104
100
 
105
101
  import pyglet
106
- from pyglet.gl import (
107
- GLException, GL_ALPHA, GL_BACK, GL_BGR, GL_BGRA, GL_COLOR_ATTACHMENT0, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL,
108
- GL_DRAW_FRAMEBUFFER, GL_FLOAT, GL_FRAMEBUFFER, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, GL_LINEAR,
109
- GL_LINEAR_MIPMAP_LINEAR, GL_LUMINANCE, GL_MAX_TEXTURE_SIZE, GL_MAX_ARRAY_TEXTURE_LAYERS,
110
- GL_PACK_ALIGNMENT, GL_READ_WRITE, GL_RED, GL_RG, GL_RGB, GL_RGBA, GL_RGBA32F,
111
- GL_RGBA8, GL_STENCIL, GL_STENCIL_INDEX, GL_TEXTURE0, GL_TEXTURE_2D, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_3D,
112
- GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TRIANGLES, GL_UNPACK_ALIGNMENT, GL_UNPACK_ROW_LENGTH,
113
- GL_UNPACK_SKIP_PIXELS, GL_UNPACK_SKIP_ROWS, GL_UNSIGNED_BYTE, GL_VIEWPORT, GLint, GLubyte, GLuint, glActiveTexture,
114
- glBindFramebuffer, glBindImageTexture, glBindTexture, glBindVertexArray, glCheckFramebufferStatus,
115
- glCompressedTexImage2D, glCompressedTexSubImage2D, glCompressedTexSubImage3D, glCopyTexSubImage2D, glCopyTexImage2D,
116
- glDeleteFramebuffers, glDeleteTextures, glDeleteVertexArrays, glDrawElements, glFlush, glFramebufferTexture2D,
117
- glGenFramebuffers, glGenTextures, glGenVertexArrays, glGenerateMipmap, glGetFramebufferAttachmentParameteriv,
118
- glGetIntegerv, glGetTexImage, glPixelStorei, glReadBuffer, glReadPixels, glTexImage2D, glTexImage3D,
119
- glTexParameteri, glTexSubImage2D, glTexSubImage3D, gl_info
120
- )
121
- from pyglet.graphics.shader import Attribute
122
- from pyglet.graphics.vertexbuffer import BufferObject
123
- from pyglet.util import asbytes
124
-
125
- from . import atlas
126
- from .animation import Animation, AnimationFrame
127
- from .buffer import Framebuffer, Renderbuffer, get_max_color_attachments
128
- from .codecs import add_default_codecs as _add_default_codecs
129
- from .codecs import registry as _codec_registry
130
102
 
131
103
  if TYPE_CHECKING:
132
- from typing import BinaryIO, Sequence, Callable, Literal
133
- from collections.abc import Iterator
134
- from .codecs import ImageDecoder, ImageEncoder
135
-
104
+ from pyglet.image.base import _AbstractImage
136
105
 
137
- class ImageException(Exception):
138
- pass
106
+ from pyglet.image import animation # noqa: F401
107
+ from pyglet.image.animation import Animation, AnimationFrame # noqa: F401, TC001
108
+ from pyglet.image.base import ImageData, ImageDataRegion, CompressedImageData, ImageException # noqa: F401
139
109
 
110
+ from pyglet.image.base import ImageGrid, ImagePattern, _color_as_bytes # noqa: F401
111
+ from pyglet.image.codecs import ImageDecoder
112
+ from pyglet.image.codecs import add_default_codecs as _add_default_codecs
113
+ from pyglet.image.codecs import registry as _codec_registry
140
114
 
141
- class TextureArraySizeExceeded(ImageException):
142
- """Exception occurs ImageData dimensions are larger than the array supports."""
143
115
 
144
-
145
- class TextureArrayDepthExceeded(ImageException):
146
- """Exception occurs when depth has hit the maximum supported of the array."""
147
-
148
-
149
- def load(filename: str, file: BinaryIO | None = None, decoder: ImageDecoder | None = None) -> AbstractImage:
116
+ def load(filename: str, file: BinaryIO | None = None, decoder: ImageDecoder | None = None) -> ImageData:
150
117
  """Load an image from a file on disk, or from an open file-like object.
151
118
 
152
119
  Args:
@@ -190,7 +157,7 @@ def load_animation(filename: str, file: BinaryIO | None = None, decoder: ImageDe
190
157
  return _codec_registry.decode_animation(filename, file)
191
158
 
192
159
 
193
- def create(width: int, height: int, pattern: ImagePattern | None = None) -> AbstractImage:
160
+ def create(width: int, height: int, pattern: ImagePattern | None = None) -> _AbstractImage:
194
161
  """Create an image optionally filled with the given pattern.
195
162
 
196
163
  :Parameters:
@@ -211,35 +178,6 @@ def create(width: int, height: int, pattern: ImagePattern | None = None) -> Abst
211
178
  return pattern.create_image(width, height)
212
179
 
213
180
 
214
- def get_max_texture_size() -> int:
215
- """Query the maximum texture size available"""
216
- size = c_int()
217
- glGetIntegerv(GL_MAX_TEXTURE_SIZE, size)
218
- return size.value
219
-
220
-
221
- def get_max_array_texture_layers() -> int:
222
- """Query the maximum TextureArray depth"""
223
- max_layers = c_int()
224
- glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, max_layers)
225
- return max_layers.value
226
-
227
-
228
- def _color_as_bytes(color: Sequence[int, int, int, int]) -> bytes:
229
- if len(color) != 4:
230
- raise TypeError("color is expected to have 4 components")
231
- return bytes(color)
232
-
233
-
234
- class ImagePattern(ABC):
235
- """Abstract image creation class."""
236
-
237
- @abstractmethod
238
- def create_image(self, width: int, height: int) -> AbstractImage:
239
- """Create an image of the given size."""
240
- raise NotImplementedError('method must be defined in subclass')
241
-
242
-
243
181
  class SolidColorImagePattern(ImagePattern):
244
182
  """Creates an image filled with a solid RGBA color."""
245
183
 
@@ -253,7 +191,7 @@ class SolidColorImagePattern(ImagePattern):
253
191
  """
254
192
  self.color = _color_as_bytes(color)
255
193
 
256
- def create_image(self, width: int, height: int) -> AbstractImage:
194
+ def create_image(self, width: int, height: int) -> _AbstractImage:
257
195
  data = self.color * width * height
258
196
  return ImageData(width, height, 'RGBA', data)
259
197
 
@@ -261,8 +199,11 @@ class SolidColorImagePattern(ImagePattern):
261
199
  class CheckerImagePattern(ImagePattern):
262
200
  """Create an image with a tileable checker image."""
263
201
 
264
- def __init__(self, color1: Sequence[int, int, int, int] = (150, 150, 150, 255),
265
- color2: Sequence[int, int, int, int] = (200, 200, 200, 255)):
202
+ def __init__(
203
+ self,
204
+ color1: Sequence[int, int, int, int] = (150, 150, 150, 255),
205
+ color2: Sequence[int, int, int, int] = (200, 200, 200, 255),
206
+ ):
266
207
  """Initialise with the given colors.
267
208
 
268
209
  Args:
@@ -278,7 +219,7 @@ class CheckerImagePattern(ImagePattern):
278
219
  self.color1 = _color_as_bytes(color1)
279
220
  self.color2 = _color_as_bytes(color2)
280
221
 
281
- def create_image(self, width: int, height: int) -> AbstractImage:
222
+ def create_image(self, width: int, height: int) -> _AbstractImage:
282
223
  hw = width // 2
283
224
  hh = height // 2
284
225
  row1 = self.color1 * hw + self.color2 * hw
@@ -287,1899 +228,5 @@ class CheckerImagePattern(ImagePattern):
287
228
  return ImageData(width, height, 'RGBA', data)
288
229
 
289
230
 
290
- class AbstractImage(ABC):
291
- """Abstract class representing an image."""
292
-
293
- anchor_x: int = 0
294
- """X coordinate of anchor, relative to left edge of image data."""
295
-
296
- anchor_y: int = 0
297
- """Y coordinate of anchor, relative to bottom edge of image data."""
298
-
299
- def __init__(self, width: int, height: int):
300
- """Initialized in subclass."""
301
- self.width = width
302
- self.height = height
303
-
304
- def __repr__(self) -> str:
305
- return f"{self.__class__.__name__}(size={self.width}x{self.height})"
306
-
307
- @abstractmethod
308
- def get_image_data(self) -> ImageData:
309
- """Get an ImageData view of this image.
310
-
311
- Changes to the returned instance may or may not be reflected in this image.
312
- """
313
-
314
- @abstractmethod
315
- def get_texture(self, rectangle: bool = False) -> Texture:
316
- """A :py:class:`~pyglet.image.Texture` view of this image.
317
-
318
- Args:
319
- rectangle:
320
- Unused. Kept for backwards compatibility.
321
- """
322
-
323
- @abstractmethod
324
- def get_mipmapped_texture(self) -> Texture:
325
- """Retrieve a :py:class:`~pyglet.image.Texture` instance with all mipmap levels filled in."""
326
-
327
- @abstractmethod
328
- def get_region(self, x: int, y: int, width: int, height: int) -> AbstractImage:
329
- """Retrieve a rectangular region of this image."""
330
-
331
- def save(self, filename: str | None = None,
332
- file: BinaryIO | None = None, encoder: ImageEncoder | None = None) -> None:
333
- """Save this image to a file.
334
-
335
- Args:
336
- filename:
337
- Used to set the image file format, and to open the output file
338
- if ``file`` is unspecified.
339
- file:
340
- An optional file-like object to write image data to.
341
- encoder:
342
- If unspecified, all encoders matching the filename extension
343
- are tried, or all encoders if no filename is provided. If all
344
- fail, the exception from the first one attempted is raised.
345
- """
346
- if file is None:
347
- assert filename is not None, "Either filename or file must be specified."
348
- file = open(filename, 'wb')
349
-
350
- if encoder is not None:
351
- encoder.encode(self, filename, file)
352
- else:
353
- _codec_registry.encode(self, filename, file)
354
-
355
- @abstractmethod
356
- def blit(self, x: int, y: int, z: int = 0) -> None:
357
- """Draw this image to the active framebuffer.
358
-
359
- The image will be drawn with the lower-left corner at
360
- (``x - anchor_x``, ``y - anchor_y``, ``z``).
361
-
362
- .. note:: This is a relatively slow method, as a full OpenGL setup and
363
- draw call is required for each blit. For performance, consider
364
- creating a Sprite from the Image, and adding it to a Batch.
365
- """
366
-
367
- @abstractmethod
368
- def blit_into(self, source, x: int, y: int, z: int) -> None:
369
- """Draw the provided ``source`` data INTO this image.
370
-
371
- ``source`` will be copied into this image such that its anchor point
372
- is aligned with the ``x`` and ``y`` parameters. If this image is a 3D
373
- Texture, the ``z`` coordinate gives the image slice to copy into.
374
-
375
- Note that if ``source`` is larger than this image (or the positioning
376
- would cause the copy to go out of bounds), an exception may be raised.
377
- To prevent errors, you can use the ``source.get_region(x, y, w, h)``
378
- method to get a smaller section that will fall within the bounds of
379
- this image.
380
- """
381
-
382
- @abstractmethod
383
- def blit_to_texture(self, target: int, level: int, x: int, y: int, z: int, internalformat: int = None) -> None:
384
- """Draw this image on the currently bound texture at ``target``.
385
-
386
- This image is copied into the texture such that this image's anchor
387
- point is aligned with the given ``x`` and ``y`` coordinates of the
388
- destination texture. If the currently bound texture is a 3D texture,
389
- the ``z`` coordinate gives the image slice to blit into.
390
- """
391
-
392
-
393
- class AbstractImageSequence(ABC):
394
- """Abstract sequence of images.
395
-
396
- Image sequence are useful for storing image animations or slices of a volume.
397
- The class implements the sequence interface (``__len__``, ``__getitem__``,
398
- ``__setitem__``).
399
- """
400
-
401
- @abstractmethod
402
- def get_texture_sequence(self) -> TextureSequence:
403
- """Get a TextureSequence.
404
-
405
- :rtype: `TextureSequence`
406
-
407
- .. versionadded:: 1.1
408
- """
409
-
410
- def get_animation(self, period: float, loop: bool = True) -> Animation:
411
- """Create an animation over this image sequence for the given constant
412
- framerate.
413
-
414
- Args:
415
- period:
416
- Number of seconds to display each frame.
417
- loop:
418
- If True, the animation will loop continuously.
419
- """
420
- return Animation.from_image_sequence(self, period, loop)
421
-
422
- @abstractmethod
423
- def __getitem__(self, item) -> AbstractImage:
424
- """Retrieve one or more images by index."""
425
-
426
- @abstractmethod
427
- def __setitem__(self, item, image: AbstractImage) -> AbstractImage:
428
- """Replace one or more images in the sequence.
429
-
430
- Args:
431
- image:
432
- The replacement image. The actual instance may not be used,
433
- depending on this implementation.
434
- """
435
-
436
- @abstractmethod
437
- def __len__(self) -> int:
438
- """Length of the image sequence."""
439
-
440
- @abstractmethod
441
- def __iter__(self) -> Iterator[AbstractImage]:
442
- """Iterate over the images in sequence."""
443
-
444
-
445
- class TextureSequence(AbstractImageSequence):
446
- """Interface for a sequence of textures.
447
-
448
- Typical implementations store multiple :py:class:`~pyglet.image.TextureRegion`s
449
- within one :py:class:`~pyglet.image.Texture` to minimise state changes.
450
- """
451
-
452
- def __getitem__(self, item) -> Texture:
453
- raise NotImplementedError
454
-
455
- def __setitem__(self, item, texture: type[Texture]) -> AbstractImage:
456
- raise NotImplementedError
457
-
458
- def __len__(self) -> int:
459
- raise NotImplementedError
460
-
461
- def __iter__(self) -> Iterator[Texture]:
462
- raise NotImplementedError
463
-
464
- def get_texture_sequence(self) -> TextureSequence:
465
- return self
466
-
467
-
468
- class UniformTextureSequence(TextureSequence):
469
- """Interface for a sequence of textures, each with the same dimensions."""
470
-
471
-
472
- class ImageData(AbstractImage):
473
- """An image represented as a string of unsigned bytes."""
474
-
475
- _swap1_pattern = re.compile(asbytes('(.)'), re.DOTALL)
476
- _swap2_pattern = re.compile(asbytes('(.)(.)'), re.DOTALL)
477
- _swap3_pattern = re.compile(asbytes('(.)(.)(.)'), re.DOTALL)
478
- _swap4_pattern = re.compile(asbytes('(.)(.)(.)(.)'), re.DOTALL)
479
-
480
- _current_texture = None
481
- _current_mipmap_texture = None
482
-
483
- def __init__(self, width: int, height: int, fmt: str, data: bytes, pitch: int | None = None):
484
- """Initialise image data.
485
-
486
- Args:
487
- width:
488
- Width of image data
489
- height:
490
- Height of image data
491
- fmt:
492
- A valid format string, such as 'RGB', 'RGBA', 'ARGB', etc.
493
- data:
494
- A sequence of bytes containing the raw image data.
495
- pitch:
496
- If specified, the number of bytes per row. Negative values
497
- indicate a top-to-bottom arrangement. Defaults to
498
- ``width * len(format)``.
499
- """
500
- super().__init__(width, height)
501
-
502
- self._current_format = self._desired_format = fmt.upper()
503
- self._current_data = data
504
- self.pitch = pitch or width * len(fmt)
505
- self._current_pitch = self.pitch
506
- self.mipmap_images = []
507
-
508
- def __getstate__(self):
509
- return {
510
- 'width': self.width,
511
- 'height': self.height,
512
- '_current_data': self.get_bytes(self._current_format, self._current_pitch),
513
- '_current_format': self._current_format,
514
- '_desired_format': self._desired_format,
515
- '_current_pitch': self._current_pitch,
516
- 'pitch': self.pitch,
517
- 'mipmap_images': self.mipmap_images,
518
- }
519
-
520
- def get_image_data(self) -> ImageData:
521
- return self
522
-
523
- @property
524
- def format(self) -> str:
525
- """Format string of the data. Read-write."""
526
- return self._desired_format
527
-
528
- @format.setter
529
- def format(self, fmt: str):
530
- self._desired_format = fmt.upper()
531
- self._current_texture = None
532
-
533
- def get_bytes(self, fmt: str | None = None, pitch: int | None = None) -> bytes:
534
- """Get the byte data of the image
535
-
536
- This method returns the raw byte data of the image, with optional conversion.
537
- To convert the data into another format, you can provide ``fmt`` and ``pitch``
538
- arguments. For example, if the image format is ``RGBA``, and you wish to get
539
- the byte data in ``RGB`` format::
540
-
541
- rgb_pitch = my_image.width // len('RGB')
542
- rgb_img_bytes = my_image.get_bytes(fmt='RGB', pitch=rgb_pitch)
543
-
544
- The image ``pitch`` may be negative, so be sure to check that when converting
545
- to another format. Switching the sign of the ``pitch`` will cause the image
546
- to appear "upside-down".
547
-
548
- Args:
549
- fmt:
550
- If provided, get the data in another format.
551
- pitch:
552
- The number of bytes per row. This generally means the length
553
- of the format string * the number of pixels per row.
554
- Negative values indicate a top-to-bottom arrangement.
555
-
556
- Note:
557
- Conversion to another format is done on the CPU, and can be
558
- somewhat costly for larger images. Consider performing conversion
559
- at load time for framerate sensitive applications.
560
- """
561
- fmt = fmt or self._desired_format
562
- pitch = pitch or self._current_pitch
563
-
564
- if fmt == self._current_format and pitch == self._current_pitch:
565
- return self._current_data
566
- return self._convert(fmt, pitch)
567
-
568
- def set_bytes(self, fmt: str, pitch: int, data: bytes) -> None:
569
- """Set the byte data of the image.
570
-
571
- Args:
572
- fmt:
573
- The format string of the supplied data.
574
- For example: "RGB" or "RGBA"
575
- pitch:
576
- The number of bytes per row. This generally means the length
577
- of the format string * the number of pixels per row.
578
- Negative values indicate a top-to-bottom arrangement.
579
- data:
580
- Image data as bytes.
581
- """
582
- self._current_format = fmt
583
- self._current_pitch = pitch
584
- self._current_data = data
585
- self._current_texture = None
586
- self._current_mipmap_texture = None
587
-
588
- def get_data(self, fmt: str | None = None, pitch: int | None = None) -> bytes:
589
- """Get the byte data of the image.
590
-
591
- Warning:
592
- This method is deprecated and will be removed in the next version.
593
- Use :py:meth:`~get_bytes` instead.
594
- """
595
- return self.get_bytes(fmt, pitch)
596
-
597
- def set_data(self, fmt: str, pitch: int, data: bytes) -> None:
598
- """Set the byte data of the image.
599
-
600
- Warning:
601
- This method is deprecated and will be removed in the next version.
602
- Use :py:meth:`~set_bytes` instead.
603
- """
604
- self.set_bytes(fmt, pitch, data)
605
-
606
- def set_mipmap_image(self, level: int, image: AbstractImage) -> None:
607
- """Set a user-defined mipmap image for a particular level.
608
-
609
- These mipmap images will be applied to textures obtained via
610
- :py:meth:`~get_mipmapped_texture`, instead of automatically
611
- generated images for each level.
612
-
613
- Args:
614
- level:
615
- Mipmap level to set image at, must be >= 1.
616
- image:
617
- Image to set. Must have correct dimensions for that mipmap
618
- level (i.e., width >> level, height >> level)
619
- """
620
- if level == 0:
621
- msg = 'Cannot set mipmap image at level 0 (it is this image)'
622
- raise ImageException(msg)
623
-
624
- # Check dimensions of mipmap
625
- width, height = self.width, self.height
626
- for i in range(level):
627
- width >>= 1
628
- height >>= 1
629
- if width != image.width or height != image.height:
630
- raise ImageException(f"Mipmap image has wrong dimensions for level {level}")
631
-
632
- # Extend mipmap_images list to required level
633
- self.mipmap_images += [None] * (level - len(self.mipmap_images))
634
- self.mipmap_images[level - 1] = image
635
-
636
- def create_texture(self, cls: type[Texture], rectangle: bool = False) -> Texture:
637
- """Given a texture class, create a texture containing this image."""
638
- internalformat = self._get_internalformat(self._desired_format)
639
- texture = cls.create(self.width, self.height, GL_TEXTURE_2D, internalformat, blank_data=False)
640
- if self.anchor_x or self.anchor_y:
641
- texture.anchor_x = self.anchor_x
642
- texture.anchor_y = self.anchor_y
643
-
644
- self.blit_to_texture(texture.target, texture.level, self.anchor_x, self.anchor_y, 0, None)
645
-
646
- return texture
647
-
648
- def get_texture(self, rectangle: bool = False) -> Texture:
649
- if not self._current_texture:
650
- self._current_texture = self.create_texture(Texture)
651
- return self._current_texture
652
-
653
- def get_mipmapped_texture(self) -> Texture:
654
- """Return a Texture with mipmaps.
655
-
656
- If :py:class:`~pyglet.image.set_mipmap_Image` has been called with at least
657
- one image, the set of images defined will be used. Otherwise, mipmaps will be
658
- automatically generated.
659
- """
660
- if self._current_mipmap_texture:
661
- return self._current_mipmap_texture
662
-
663
- texture = Texture.create(self.width, self.height, GL_TEXTURE_2D, None, blank_data=False)
664
- if self.anchor_x or self.anchor_y:
665
- texture.anchor_x = self.anchor_x
666
- texture.anchor_y = self.anchor_y
667
-
668
- internalformat = self._get_internalformat(self.format)
669
-
670
- glBindTexture(texture.target, texture.id)
671
- glTexParameteri(texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
672
-
673
- if self.mipmap_images:
674
- self.blit_to_texture(texture.target, texture.level, self.anchor_x, self.anchor_y, 0, internalformat)
675
- level = 0
676
- for image in self.mipmap_images:
677
- level += 1
678
- if image:
679
- image.blit_to_texture(texture.target, level, self.anchor_x, self.anchor_y, 0, internalformat)
680
- # TODO: should set base and max mipmap level if some mipmaps are missing.
681
- else:
682
- glGenerateMipmap(texture.target)
683
- self.blit_to_texture(texture.target, texture.level, self.anchor_x, self.anchor_y, 0, internalformat)
684
-
685
- self._current_mipmap_texture = texture
686
- return texture
687
-
688
- def get_region(self, x: int, y: int, width: int, height: int) -> ImageDataRegion:
689
- """Retrieve a rectangular region of this image data."""
690
- return ImageDataRegion(x, y, width, height, self)
691
-
692
- def blit(self, x: int, y: int, z: int = 0, width: int | None = None, height: int | None = None) -> None:
693
- self.get_texture().blit(x, y, z, width, height)
694
-
695
- def blit_into(self, source, x: int, y: int, z: int) -> None:
696
- raise NotImplementedError(f"Not implemented for {self}")
697
-
698
- def blit_to_texture(self, target: int, level: int, x: int, y: int, z: int, internalformat: int = None):
699
- """Draw this image to the currently bound texture at ``target``.
700
-
701
- This image's anchor point will be aligned to the given ``x`` and ``y``
702
- coordinates. If the currently bound texture is a 3D texture, the ``z``
703
- parameter gives the image slice to blit into.
704
-
705
- If ``internalformat`` is specified, ``glTexImage`` is used to initialise
706
- the texture; otherwise, ``glTexSubImage`` is used to update a region.
707
- """
708
- x -= self.anchor_x
709
- y -= self.anchor_y
710
-
711
- data_format = self.format
712
- data_pitch = abs(self._current_pitch)
713
-
714
- # Determine pixel format from format string
715
- fmt, gl_type = self._get_gl_format_and_type(data_format)
716
-
717
- if fmt is None:
718
- # Need to convert data to a standard form
719
- data_format = {
720
- 1: 'R',
721
- 2: 'RG',
722
- 3: 'RGB',
723
- 4: 'RGBA',
724
- }.get(len(data_format))
725
- fmt, gl_type = self._get_gl_format_and_type(data_format)
726
-
727
- # Get data in required format (hopefully will be the same format it's already
728
- # in, unless that's an obscure format, upside-down or the driver is old).
729
- data = self._convert(data_format, data_pitch)
730
-
731
- if data_pitch & 0x1:
732
- align = 1
733
- elif data_pitch & 0x2:
734
- align = 2
735
- else:
736
- align = 4
737
- row_length = data_pitch // len(data_format)
738
-
739
- glPixelStorei(GL_UNPACK_ALIGNMENT, align)
740
- glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length)
741
- self._apply_region_unpack()
742
-
743
- if target == GL_TEXTURE_3D or target == GL_TEXTURE_2D_ARRAY:
744
- assert not internalformat
745
- glTexSubImage3D(target, level,
746
- x, y, z,
747
- self.width, self.height, 1,
748
- fmt, gl_type,
749
- data)
750
- elif internalformat:
751
- glTexImage2D(target, level,
752
- internalformat,
753
- self.width, self.height,
754
- 0,
755
- fmt, gl_type,
756
- data)
757
- else:
758
- glTexSubImage2D(target, level,
759
- x, y,
760
- self.width, self.height,
761
- fmt, gl_type,
762
- data)
763
-
764
- # Unset GL_UNPACK_ROW_LENGTH:
765
- glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)
766
- self._default_region_unpack()
767
-
768
- # Flush image upload before data get GC'd:
769
- glFlush()
770
-
771
- def _apply_region_unpack(self):
772
- # Not needed on full images
773
- pass
774
-
775
- def _default_region_unpack(self):
776
- # Not needed on full images
777
- pass
778
-
779
- def _convert(self, fmt: str, pitch: int) -> bytes:
780
- """Return data in the desired format.
781
-
782
- This method does not alter this instance's current format or pitch.
783
- """
784
- if fmt == self._current_format and pitch == self._current_pitch:
785
- return self._current_data
786
-
787
- self._ensure_bytes()
788
- data = self._current_data
789
- current_pitch = self._current_pitch
790
- current_format = self._current_format
791
- sign_pitch = current_pitch // abs(current_pitch)
792
- if fmt != self._current_format:
793
- # Create replacement string, e.g. r'\4\1\2\3' to convert RGBA to ARGB
794
- repl = asbytes('')
795
- for c in fmt:
796
- try:
797
- idx = current_format.index(c) + 1
798
- except ValueError:
799
- idx = 1
800
- repl += asbytes(r'\%d' % idx)
801
-
802
- if len(current_format) == 1:
803
- swap_pattern = self._swap1_pattern
804
- elif len(current_format) == 2:
805
- swap_pattern = self._swap2_pattern
806
- elif len(current_format) == 3:
807
- swap_pattern = self._swap3_pattern
808
- elif len(current_format) == 4:
809
- swap_pattern = self._swap4_pattern
810
- else:
811
- raise ImageException('Current image format is wider than 32 bits.')
812
-
813
- packed_pitch = self.width * len(current_format)
814
- if abs(self._current_pitch) != packed_pitch:
815
- # Pitch is wider than pixel data, need to go row-by-row.
816
- new_pitch = abs(self._current_pitch)
817
- rows = [data[i:i + new_pitch] for i in range(0, len(data), new_pitch)]
818
- rows = [swap_pattern.sub(repl, r[:packed_pitch]) for r in rows]
819
- data = b''.join(rows)
820
- else:
821
- # Rows are tightly packed, apply regex over whole image.
822
- data = swap_pattern.sub(repl, data)
823
-
824
- # After conversion, rows will always be tightly packed
825
- current_pitch = sign_pitch * (len(fmt) * self.width)
826
-
827
- if pitch != current_pitch:
828
- diff = abs(current_pitch) - abs(pitch)
829
- if diff > 0:
830
- # New pitch is shorter than old pitch, chop bytes off each row
831
- new_pitch = abs(pitch)
832
- rows = [data[i:i + new_pitch - diff] for i in range(0, len(data), new_pitch)]
833
- data = b''.join(rows)
834
-
835
- elif diff < 0:
836
- # New pitch is longer than old pitch, add '0' bytes to each row
837
- new_pitch = abs(current_pitch)
838
- padding = bytes(1) * -diff
839
- rows = [data[i:i + new_pitch] + padding for i in range(0, len(data), new_pitch)]
840
- data = b''.join(rows)
841
-
842
- if current_pitch * pitch < 0:
843
- # Pitch differs in sign, swap row order
844
- new_pitch = abs(pitch)
845
- rows = [data[i:i + new_pitch] for i in range(0, len(data), new_pitch)]
846
- rows.reverse()
847
- data = b''.join(rows)
848
-
849
- return data
850
-
851
- def _ensure_bytes(self) -> None:
852
- if type(self._current_data) is not bytes:
853
- self._current_data = asbytes(self._current_data)
854
-
855
- @staticmethod
856
- def _get_gl_format_and_type(fmt):
857
- if fmt == 'R':
858
- return GL_RED, GL_UNSIGNED_BYTE
859
- elif fmt == 'RG':
860
- return GL_RG, GL_UNSIGNED_BYTE
861
- elif fmt == 'RGB':
862
- return GL_RGB, GL_UNSIGNED_BYTE
863
- elif fmt == 'BGR':
864
- return GL_BGR, GL_UNSIGNED_BYTE
865
- elif fmt == 'RGBA':
866
- return GL_RGBA, GL_UNSIGNED_BYTE
867
- elif fmt == 'BGRA':
868
- return GL_BGRA, GL_UNSIGNED_BYTE
869
-
870
- elif fmt == 'L':
871
- return GL_LUMINANCE, GL_UNSIGNED_BYTE
872
- elif fmt == 'A':
873
- return GL_ALPHA, GL_UNSIGNED_BYTE
874
-
875
- return None, None
876
-
877
- @staticmethod
878
- def _get_internalformat(fmt):
879
- if fmt == 'R':
880
- return GL_RED
881
- elif fmt == 'RG':
882
- return GL_RG
883
- elif fmt == 'RGB':
884
- return GL_RGB
885
- elif fmt == 'RGBA':
886
- return GL_RGBA
887
- elif fmt == 'D':
888
- return GL_DEPTH_COMPONENT
889
- elif fmt == 'DS':
890
- return GL_DEPTH_STENCIL
891
-
892
- elif fmt == 'L':
893
- return GL_LUMINANCE
894
- elif fmt == 'A':
895
- return GL_ALPHA
896
-
897
- return GL_RGBA
898
-
899
-
900
- class ImageDataRegion(ImageData):
901
- def __init__(self, x, y, width, height, image_data):
902
- super().__init__(width, height,
903
- image_data._current_format,
904
- image_data._current_data,
905
- image_data._current_pitch)
906
- self.x = x
907
- self.y = y
908
-
909
- def __getstate__(self):
910
- return {
911
- 'width': self.width,
912
- 'height': self.height,
913
- '_current_data': self.get_bytes(self._current_format, self._current_pitch),
914
- '_current_format': self._current_format,
915
- '_desired_format': self._desired_format,
916
- '_current_pitch': self._current_pitch,
917
- 'pitch': self.pitch,
918
- 'mipmap_images': self.mipmap_images,
919
- 'x': self.x,
920
- 'y': self.y,
921
- }
922
-
923
- def get_bytes(self, fmt=None, pitch=None):
924
- x1 = len(self._current_format) * self.x
925
- x2 = len(self._current_format) * (self.x + self.width)
926
-
927
- self._ensure_bytes()
928
- data = self._convert(self._current_format, abs(self._current_pitch))
929
- new_pitch = abs(self._current_pitch)
930
- rows = [data[i:i + new_pitch] for i in range(0, len(data), new_pitch)]
931
- rows = [row[x1:x2] for row in rows[self.y:self.y + self.height]]
932
- self._current_data = b''.join(rows)
933
- self._current_pitch = self.width * len(self._current_format)
934
- self._current_texture = None
935
- self.x = 0
936
- self.y = 0
937
-
938
- fmt = fmt or self._desired_format
939
- pitch = pitch or self._current_pitch
940
- return super().get_bytes(fmt, pitch)
941
-
942
- def set_bytes(self, fmt, pitch, data):
943
- self.x = 0
944
- self.y = 0
945
- super().set_bytes(fmt, pitch, data)
946
-
947
- def _apply_region_unpack(self):
948
- glPixelStorei(GL_UNPACK_SKIP_PIXELS, self.x)
949
- glPixelStorei(GL_UNPACK_SKIP_ROWS, self.y)
950
-
951
- def _default_region_unpack(self):
952
- glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0)
953
- glPixelStorei(GL_UNPACK_SKIP_ROWS, 0)
954
-
955
- def get_region(self, x, y, width, height):
956
- x += self.x
957
- y += self.y
958
- return super().get_region(x, y, width, height)
959
-
960
-
961
- class CompressedImageData(AbstractImage):
962
- """Compressed image data suitable for direct uploading to GPU."""
963
-
964
- _current_texture = None
965
- _current_mipmap_texture = None
966
-
967
- def __init__(self, width: int, height: int, gl_format: int, data: bytes,
968
- extension: str | None = None,
969
- decoder: Callable[[bytes, int, int], AbstractImage] | None = None) -> None:
970
- """Construct a CompressedImageData with the given compressed data.
971
-
972
- Args:
973
- width:
974
- The width of the image.
975
- height:
976
- The height of the image.
977
- gl_format:
978
- GL constant giving the format of the compressed data.
979
- For example: ``GL_COMPRESSED_RGBA_S3TC_DXT5_EXT``.
980
- data:
981
- An array of bytes containing the compressed image data.
982
- extension:
983
- If specified, gives the name of a GL extension to check for
984
- before creating a texture.
985
- decoder:
986
- An optional fallback function used to decode the compressed data.
987
- This function is called if the required extension is not present.
988
- """
989
- super().__init__(width, height)
990
- self.data = data
991
- self.gl_format = gl_format
992
- self.extension = extension
993
- self.decoder = decoder
994
- self.mipmap_data = []
995
-
996
- def set_mipmap_data(self, level: int, data: bytes) -> None:
997
- """Set compressed image data for a mipmap level.
998
-
999
- Supplied data gives a compressed image for the given mipmap level.
1000
- This image data must be in the same format as was used in the
1001
- constructor. The image data must also be of the correct dimensions for
1002
- the level (i.e., width >> level, height >> level); but this is not checked.
1003
- If *any* mipmap levels are specified, they are used; otherwise, mipmaps for
1004
- ``mipmapped_texture`` are generated automatically.
1005
- """
1006
- # Extend mipmap_data list to required level
1007
- self.mipmap_data += [None] * (level - len(self.mipmap_data))
1008
- self.mipmap_data[level - 1] = data
1009
-
1010
- def _have_extension(self) -> bool:
1011
- return self.extension is None or gl_info.have_extension(self.extension)
1012
-
1013
- def get_texture(self, rectangle=False) -> Texture:
1014
- if self._current_texture:
1015
- return self._current_texture
1016
-
1017
- if rectangle:
1018
- raise ImageException('Compressed texture rectangles not supported')
1019
-
1020
- texture = Texture.create(self.width, self.height, GL_TEXTURE_2D, None)
1021
-
1022
- if self.anchor_x or self.anchor_y:
1023
- texture.anchor_x = self.anchor_x
1024
- texture.anchor_y = self.anchor_y
1025
-
1026
- glBindTexture(texture.target, texture.id)
1027
- glTexParameteri(texture.target, GL_TEXTURE_MIN_FILTER, texture.min_filter)
1028
- glTexParameteri(texture.target, GL_TEXTURE_MAG_FILTER, texture.mag_filter)
1029
-
1030
- if self._have_extension():
1031
- glCompressedTexImage2D(texture.target, texture.level,
1032
- self.gl_format,
1033
- self.width, self.height, 0,
1034
- len(self.data), self.data)
1035
- elif self.decoder:
1036
- image = self.decoder(self.data, self.width, self.height)
1037
- texture = image.get_texture()
1038
- assert texture.width == self.width
1039
- assert texture.height == self.height
1040
- else:
1041
- msg = f"No extension or fallback decoder is available to decode {self}"
1042
- raise ImageException(msg)
1043
-
1044
- glFlush()
1045
- self._current_texture = texture
1046
- return texture
1047
-
1048
- def get_mipmapped_texture(self) -> Texture:
1049
- if self._current_mipmap_texture:
1050
- return self._current_mipmap_texture
1051
-
1052
- if not self._have_extension():
1053
- # TODO: mip-mapped software decoded compressed textures.
1054
- # For now, just return a non-mipmapped texture.
1055
- return self.get_texture()
1056
-
1057
- texture = Texture.create(self.width, self.height, GL_TEXTURE_2D, None)
1058
-
1059
- if self.anchor_x or self.anchor_y:
1060
- texture.anchor_x = self.anchor_x
1061
- texture.anchor_y = self.anchor_y
1062
-
1063
- glBindTexture(texture.target, texture.id)
1064
-
1065
- glTexParameteri(texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
1066
-
1067
- if not self.mipmap_data:
1068
- glGenerateMipmap(texture.target)
1069
-
1070
- glCompressedTexImage2D(texture.target, texture.level,
1071
- self.gl_format,
1072
- self.width, self.height, 0,
1073
- len(self.data), self.data)
1074
-
1075
- width, height = self.width, self.height
1076
- level = 0
1077
- for data in self.mipmap_data:
1078
- width >>= 1
1079
- height >>= 1
1080
- level += 1
1081
- glCompressedTexImage2D(texture.target, level, self.gl_format, width, height, 0, len(data), data)
1082
-
1083
- glFlush()
1084
-
1085
- self._current_mipmap_texture = texture
1086
- return texture
1087
-
1088
- def blit_to_texture(self, target: int, level: int, x: int, y: int, z: int, internalformat: int = None):
1089
- if not self._have_extension():
1090
- raise ImageException(f"{self.extension} is required to decode {self}")
1091
-
1092
- # TODO: use glCompressedTexImage2D/3D if `internalformat` is specified.
1093
-
1094
- if target == GL_TEXTURE_3D:
1095
- glCompressedTexSubImage3D(target, level,
1096
- x - self.anchor_x, y - self.anchor_y, z,
1097
- self.width, self.height, 1,
1098
- self.gl_format,
1099
- len(self.data), self.data)
1100
- else:
1101
- glCompressedTexSubImage2D(target, level,
1102
- x - self.anchor_x, y - self.anchor_y,
1103
- self.width, self.height,
1104
- self.gl_format,
1105
- len(self.data), self.data)
1106
-
1107
- def get_image_data(self) -> CompressedImageData:
1108
- return self
1109
-
1110
- def get_region(self, x: int, y: int, width: int, height: int) -> AbstractImage:
1111
- raise NotImplementedError(f"Not implemented for {self}")
1112
-
1113
- def blit(self, x: int, y: int, z: int = 0) -> None:
1114
- self.get_texture().blit(x, y, z)
1115
-
1116
- def blit_into(self, source, x: int, y: int, z: int) -> None:
1117
- raise NotImplementedError(f"Not implemented for {self}")
1118
-
1119
-
1120
- class Texture(AbstractImage):
1121
- """An image loaded into GPU memory
1122
-
1123
- Typically, you will get an instance of Texture by accessing calling
1124
- the ``get_texture()`` method of any AbstractImage class (such as ImageData).
1125
- """
1126
-
1127
- region_class: TextureRegion # Set to TextureRegion after it's defined
1128
- """The class to use when constructing regions of this texture.
1129
- The class should be a subclass of TextureRegion.
1130
- """
1131
-
1132
- tex_coords = (0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0)
1133
- """12-tuple of float, named (u1, v1, r1, u2, v2, r2, ...).
1134
- ``u, v, r`` give the 3D texture coordinates for vertices 1-4. The vertices
1135
- are specified in the order bottom-left, bottom-right, top-right and top-left.
1136
- """
1137
-
1138
- tex_coords_order: tuple[int, int, int, int] = (0, 1, 2, 3)
1139
- """The default vertex winding order for a quad.
1140
- This defaults to counter-clockwise, starting at the bottom-left.
1141
- """
1142
-
1143
- target: int
1144
- """The GL texture target (e.g., ``GL_TEXTURE_2D``)."""
1145
-
1146
- colors = (0, 0, 0, 0) * 4
1147
- """The GL texture target (e.g., ``GL_TEXTURE_2D``)."""
1148
-
1149
- level: int = 0
1150
- """The mipmap level of this texture."""
1151
-
1152
- images = 1
1153
-
1154
- x: int = 0
1155
- y: int = 0
1156
- z: int = 0
1157
-
1158
- default_min_filter = GL_LINEAR
1159
- """The default minification filter. Defaults to GL_LINEAR.
1160
- If a texture is created without specifying a filter, this
1161
- default will be used.
1162
- """
1163
-
1164
- default_mag_filter = GL_LINEAR
1165
- """The default magnification filter. Defaults to GL_LINEAR.
1166
- If a texture is created without specifying a filter, this
1167
- default will be used.
1168
- """
1169
-
1170
- def __init__(self, width: int, height: int, target: int, tex_id: int,
1171
- min_filter: int | None = None, mag_filter: int | None = None) -> None:
1172
- super().__init__(width, height)
1173
- self.target = target
1174
- self.id = tex_id
1175
- self.min_filter = min_filter or self.default_min_filter
1176
- self.mag_filter = mag_filter or self.default_mag_filter
1177
- self._context = pyglet.gl.current_context
1178
-
1179
- def delete(self) -> None:
1180
- """Delete this texture and the memory it occupies.
1181
-
1182
- Textures are invalid after deletion, and may no longer be used.
1183
- """
1184
- glDeleteTextures(1, GLuint(self.id))
1185
- self.id = None
1186
-
1187
- def __del__(self):
1188
- if self.id is not None:
1189
- try:
1190
- self._context.delete_texture(self.id)
1191
- self.id = None
1192
- except (AttributeError, ImportError):
1193
- pass # Interpreter is shutting down
1194
-
1195
- def bind(self, texture_unit: int = 0) -> None:
1196
- """Bind to a specific Texture Unit by number."""
1197
- glActiveTexture(GL_TEXTURE0 + texture_unit)
1198
- glBindTexture(self.target, self.id)
1199
-
1200
- def bind_image_texture(self, unit: int, level: int = 0, layered: bool = False,
1201
- layer: int = 0, access: int = GL_READ_WRITE, fmt: int = GL_RGBA32F):
1202
- """Bind as an ImageTexture for use with a :py:class:`~pyglet.shader.ComputeShaderProgram`.
1203
-
1204
- .. note:: OpenGL 4.3, or 4.2 with the GL_ARB_compute_shader extension is required.
1205
- """
1206
- glBindImageTexture(unit, self.id, level, layered, layer, access, fmt)
1207
-
1208
- @classmethod
1209
- def create(cls, width: int, height: int, target: int = GL_TEXTURE_2D,
1210
- internalformat: int | None = GL_RGBA8, min_filter: int | None = None, mag_filter: int | None = None,
1211
- fmt: int = GL_RGBA, blank_data: bool = True) -> Texture:
1212
- """Create a Texture
1213
-
1214
- Create a Texture with the specified dimensions, target and format.
1215
- On return, the texture will be bound.
1216
-
1217
- Args:
1218
- width:
1219
- Width of texture in pixels.
1220
- height:
1221
- Height of texture in pixels.
1222
- target:
1223
- GL constant giving texture target to use, typically ``GL_TEXTURE_2D``.
1224
- internalformat:
1225
- GL constant giving internal format of texture; for example, ``GL_RGBA``.
1226
- The internal format decides how the texture data will be stored internally.
1227
- If ``None``, the texture will be created but not initialized.
1228
- min_filter:
1229
- The minifaction filter used for this texture, commonly ``GL_LINEAR`` or ``GL_NEAREST``
1230
- mag_filter:
1231
- The magnification filter used for this texture, commonly ``GL_LINEAR`` or ``GL_NEAREST``
1232
- fmt:
1233
- GL constant giving format of texture; for example, ``GL_RGBA``.
1234
- The format specifies what format the pixel data we're expecting to write
1235
- to the texture and should ideally be the same as for internal format.
1236
- blank_data:
1237
- If True, initialize the texture data with all zeros. If False, do not pass initial data.
1238
- """
1239
- min_filter = min_filter or cls.default_min_filter
1240
- mag_filter = mag_filter or cls.default_mag_filter
1241
-
1242
- tex_id = GLuint()
1243
- glGenTextures(1, byref(tex_id))
1244
- glBindTexture(target, tex_id.value)
1245
- glTexParameteri(target, GL_TEXTURE_MIN_FILTER, min_filter)
1246
- glTexParameteri(target, GL_TEXTURE_MAG_FILTER, mag_filter)
1247
-
1248
- if internalformat is not None:
1249
- blank = (GLubyte * (width * height * 4))() if blank_data else None
1250
- glTexImage2D(target, 0,
1251
- internalformat,
1252
- width, height,
1253
- 0,
1254
- fmt,
1255
- GL_UNSIGNED_BYTE,
1256
- blank)
1257
- glFlush()
1258
-
1259
- return cls(width, height, target, tex_id.value, min_filter, mag_filter)
1260
-
1261
- def get_image_data(self, z: int = 0) -> ImageData:
1262
- """Get the image data of this texture.
1263
-
1264
- Bind the texture, and read the pixel data back from the GPU.
1265
- This can be a somewhat costly operation.
1266
-
1267
- Modifying the returned ImageData object has no effect on the
1268
- texture itself. Uploading ImageData back to the GPU/texture
1269
- can be done with the :py:meth:`~Texture.blit_into` method.
1270
-
1271
- Args:
1272
- z:
1273
- For 3D textures, the image slice to retrieve.
1274
- """
1275
- glBindTexture(self.target, self.id)
1276
-
1277
- # Always extract complete RGBA data. Could check internalformat
1278
- # to only extract used channels. XXX
1279
- fmt = 'RGBA'
1280
- gl_format = GL_RGBA
1281
-
1282
- buf = (GLubyte * (self.width * self.height * self.images * len(fmt)))()
1283
-
1284
- # TODO: Clean up this temporary hack
1285
- if pyglet.gl.current_context.get_info().get_opengl_api() == "gles":
1286
- fbo = c_uint()
1287
- glGenFramebuffers(1, fbo)
1288
- glBindFramebuffer(GL_FRAMEBUFFER, fbo.value)
1289
- glPixelStorei(GL_PACK_ALIGNMENT, 1)
1290
- glCheckFramebufferStatus(GL_FRAMEBUFFER)
1291
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.id, self.level)
1292
- glReadPixels(0, 0, self.width, self.height, gl_format, GL_UNSIGNED_BYTE, buf)
1293
- glBindFramebuffer(GL_FRAMEBUFFER, 0)
1294
- glDeleteFramebuffers(1, fbo)
1295
- else:
1296
- glPixelStorei(GL_PACK_ALIGNMENT, 1)
1297
- glGetTexImage(self.target, self.level, gl_format, GL_UNSIGNED_BYTE, buf)
1298
-
1299
- data = ImageData(self.width, self.height, fmt, buf)
1300
- if self.images > 1:
1301
- data = data.get_region(0, z * self.height, self.width, self.height)
1302
- return data
1303
-
1304
- def get_texture(self, rectangle: bool = False) -> Texture:
1305
- return self
1306
-
1307
- def blit(self, x: int, y: int, z: int = 0, width: int | None = None, height: int | None = None) -> None:
1308
- """Blit the texture to the screen.
1309
-
1310
- This is a costly operation, and should not be used for performance critical
1311
- code. Blitting a texture requires binding it, setting up throwaway buffers,
1312
- creating a VAO, uploading attribute data, and then making a single draw call.
1313
- This is quite wasteful and slow, so blitting should not be used for more than
1314
- a few images. This method is provided to assist with debugging, but not intended
1315
- for drawing of multiple images.
1316
-
1317
- Instead, consider creating a :py:class:`~pyglet.sprite.Sprite` with the Texture,
1318
- and drawing it as part of a larger :py:class:`~pyglet.graphics.Batch`.
1319
- """
1320
- x1 = x - self.anchor_x
1321
- y1 = y - self.anchor_y
1322
- x2 = x1 + (width is None and self.width or width)
1323
- y2 = y1 + (height is None and self.height or height)
1324
- position = x1, y1, z, x2, y1, z, x2, y2, z, x1, y2, z
1325
- indices = [0, 1, 2, 0, 2, 3]
1326
-
1327
- glActiveTexture(GL_TEXTURE0)
1328
- glBindTexture(self.target, self.id)
1329
-
1330
- # Create and bind a throwaway VAO
1331
- vao_id = GLuint()
1332
- glGenVertexArrays(1, vao_id)
1333
- glBindVertexArray(vao_id)
1334
-
1335
- # Activate shader program:
1336
- program = pyglet.graphics.get_default_blit_shader()
1337
- program.use()
1338
- pos_attrs = program.attributes['position']
1339
- tex_attrs = program.attributes['tex_coords']
1340
-
1341
- # vertex position data:
1342
- position_attribute = Attribute('position', pos_attrs['location'], pos_attrs['count'], GL_FLOAT, False, False)
1343
- position_buffer = BufferObject(4 * position_attribute.stride)
1344
- data = (position_attribute.c_type * len(position))(*position)
1345
- position_buffer.set_data(data)
1346
- position_attribute.enable()
1347
- position_attribute.set_pointer(position_buffer.ptr)
1348
-
1349
- # texture coordinate data:
1350
- texcoord_attribute = Attribute('tex_coords', tex_attrs['location'], tex_attrs['count'], GL_FLOAT, False, False)
1351
- texcoord_buffer = BufferObject(4 * texcoord_attribute.stride)
1352
- data = (texcoord_attribute.c_type * len(self.tex_coords))(*self.tex_coords)
1353
- texcoord_buffer.set_data(data)
1354
- texcoord_attribute.enable()
1355
- texcoord_attribute.set_pointer(texcoord_buffer.ptr)
1356
-
1357
- # index data:
1358
- index_array = (c_ubyte * len(indices))(*indices)
1359
- index_buffer = BufferObject(sizeof(index_array))
1360
- index_buffer.set_data(index_array)
1361
- index_buffer.bind_to_index_buffer()
1362
-
1363
- glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_BYTE, 0)
1364
- glFlush()
1365
-
1366
- # Deactivate shader program:
1367
- program.stop()
1368
- # Discard everything after blitting:
1369
- position_buffer.delete()
1370
- texcoord_buffer.delete()
1371
- glBindVertexArray(0)
1372
- glDeleteVertexArrays(1, vao_id)
1373
-
1374
- def get_mipmapped_texture(self) -> Texture:
1375
- raise NotImplementedError(f"Not implemented for {self}.")
1376
-
1377
- def blit_into(self, source: AbstractImage, x: int, y: int, z: int):
1378
- glBindTexture(self.target, self.id)
1379
- source.blit_to_texture(self.target, self.level, x, y, z)
1380
-
1381
- def blit_to_texture(self, target: int, level: int, x: int, y: int, z: int, internalformat: int = None):
1382
- raise NotImplementedError(f"Not implemented for {self}")
1383
-
1384
- def get_region(self, x: int, y: int, width: int, height: int) -> TextureRegion:
1385
- return self.region_class(x, y, 0, width, height, self)
1386
-
1387
- def get_transform(self, flip_x: bool = False, flip_y: bool = False,
1388
- rotate: Literal[0, 90, 180, 270, 360] = 0) -> TextureRegion:
1389
- """Create a copy of this image applying a simple transformation.
1390
-
1391
- The transformation is applied to the texture coordinates only;
1392
- :py:meth:`~pyglet.image.AbstractImage.get_image_data` will return the
1393
- untransformed data. The transformation is applied around the anchor point.
1394
-
1395
- Args:
1396
- flip_x:
1397
- If True, the returned image will be flipped horizontally.
1398
- flip_y:
1399
- If True, the returned image will be flipped vertically.
1400
- rotate:
1401
- Degrees of clockwise rotation of the returned image. Only
1402
- 90-degree increments are supported.
1403
- """
1404
- transform = self.get_region(0, 0, self.width, self.height)
1405
- bl, br, tr, tl = 0, 1, 2, 3
1406
- transform.anchor_x = self.anchor_x
1407
- transform.anchor_y = self.anchor_y
1408
- if flip_x:
1409
- bl, br, tl, tr = br, bl, tr, tl
1410
- transform.anchor_x = self.width - self.anchor_x
1411
- if flip_y:
1412
- bl, br, tl, tr = tl, tr, bl, br
1413
- transform.anchor_y = self.height - self.anchor_y
1414
- rotate %= 360
1415
- if rotate < 0:
1416
- rotate += 360
1417
- if rotate == 0:
1418
- pass
1419
- elif rotate == 90:
1420
- bl, br, tr, tl = br, tr, tl, bl
1421
- transform.anchor_x, transform.anchor_y = transform.anchor_y, transform.width - transform.anchor_x
1422
- elif rotate == 180:
1423
- bl, br, tr, tl = tr, tl, bl, br
1424
- transform.anchor_x = transform.width - transform.anchor_x
1425
- transform.anchor_y = transform.height - transform.anchor_y
1426
- elif rotate == 270:
1427
- bl, br, tr, tl = tl, bl, br, tr
1428
- transform.anchor_x, transform.anchor_y = transform.height - transform.anchor_y, transform.anchor_x
1429
- else:
1430
- raise ImageException("Only 90 degree rotations are supported.")
1431
- if rotate in (90, 270):
1432
- transform.width, transform.height = transform.height, transform.width
1433
- transform._set_tex_coords_order(bl, br, tr, tl)
1434
- return transform
1435
-
1436
- def _set_tex_coords_order(self, bl, br, tr, tl):
1437
- tex_coords = (self.tex_coords[:3],
1438
- self.tex_coords[3:6],
1439
- self.tex_coords[6:9],
1440
- self.tex_coords[9:])
1441
- self.tex_coords = tex_coords[bl] + tex_coords[br] + tex_coords[tr] + tex_coords[tl]
1442
-
1443
- order = self.tex_coords_order
1444
- self.tex_coords_order = (order[bl], order[br], order[tr], order[tl])
1445
-
1446
- @property
1447
- def uv(self) -> tuple[float, float, float, float]:
1448
- """Tuple containing the left, bottom, right, top 2D texture coordinates."""
1449
- tex_coords = self.tex_coords
1450
- return tex_coords[0], tex_coords[1], tex_coords[3], tex_coords[7]
1451
-
1452
- def __repr__(self) -> str:
1453
- return f"{self.__class__.__name__}(id={self.id}, size={self.width}x{self.height})"
1454
-
1455
-
1456
- class TextureRegion(Texture):
1457
- """A rectangular region of a texture, presented as if it were a separate texture."""
1458
-
1459
- def __init__(self, x: int, y: int, z: int, width: int, height: int, owner: Texture):
1460
- super().__init__(width, height, owner.target, owner.id)
1461
-
1462
- self.x = x
1463
- self.y = y
1464
- self.z = z
1465
- self._width = width
1466
- self._height = height
1467
- self.owner = owner
1468
- owner_u1 = owner.tex_coords[0]
1469
- owner_v1 = owner.tex_coords[1]
1470
- owner_u2 = owner.tex_coords[3]
1471
- owner_v2 = owner.tex_coords[7]
1472
- scale_u = owner_u2 - owner_u1
1473
- scale_v = owner_v2 - owner_v1
1474
- u1 = x / owner.width * scale_u + owner_u1
1475
- v1 = y / owner.height * scale_v + owner_v1
1476
- u2 = (x + width) / owner.width * scale_u + owner_u1
1477
- v2 = (y + height) / owner.height * scale_v + owner_v1
1478
- r = z / owner.images + owner.tex_coords[2]
1479
- self.tex_coords = (u1, v1, r, u2, v1, r, u2, v2, r, u1, v2, r)
1480
-
1481
- def get_image_data(self):
1482
- image_data = self.owner.get_image_data(self.z)
1483
- return image_data.get_region(self.x, self.y, self.width, self.height)
1484
-
1485
- def get_region(self, x: int, y: int, width: int, height: int) -> TextureRegion:
1486
- x += self.x
1487
- y += self.y
1488
- region = self.region_class(x, y, self.z, width, height, self.owner)
1489
- region._set_tex_coords_order(*self.tex_coords_order)
1490
- return region
1491
-
1492
- def blit_into(self, source: AbstractImage, x: int, y: int, z: int) -> None:
1493
- assert source.width <= self._width and source.height <= self._height, f"{source} is larger than {self}"
1494
- self.owner.blit_into(source, x + self.x, y + self.y, z + self.z)
1495
-
1496
- def __repr__(self) -> str:
1497
- return (f"{self.__class__.__name__}(id={self.id},"
1498
- f" size={self.width}x{self.height}, owner={self.owner.width}x{self.owner.height})")
1499
-
1500
- def delete(self) -> None:
1501
- """Deleting a TextureRegion has no effect. Operate on the owning texture instead."""
1502
-
1503
- def __del__(self):
1504
- pass
1505
-
1506
-
1507
- Texture.region_class = TextureRegion
1508
-
1509
-
1510
- class Texture3D(Texture, UniformTextureSequence):
1511
- """A texture with more than one image slice.
1512
-
1513
- Use the :py:meth:`create_for_images` or :py:meth:`create_for_image_grid`
1514
- classmethod to construct a Texture3D.
1515
- """
1516
- item_width: int = 0
1517
- item_height: int = 0
1518
- items: tuple
1519
-
1520
- @classmethod
1521
- def create_for_images(cls, images, internalformat=GL_RGBA, blank_data=True):
1522
- item_width = images[0].width
1523
- item_height = images[0].height
1524
-
1525
- if not all(img.width == item_width and img.height == item_height for img in images):
1526
- raise ImageException('Images do not have same dimensions.')
1527
-
1528
- texture = cls.create(item_width, item_height, GL_TEXTURE_3D, None)
1529
- if images[0].anchor_x or images[0].anchor_y:
1530
- texture.anchor_x = images[0].anchor_x
1531
- texture.anchor_y = images[0].anchor_y
1532
-
1533
- texture.images = len(images)
1534
-
1535
- blank = (GLubyte * (texture.width * texture.height * texture.images * 4))() if blank_data else None
1536
- glBindTexture(texture.target, texture.id)
1537
- glTexImage3D(texture.target, texture.level,
1538
- internalformat,
1539
- texture.width, texture.height, texture.images, 0,
1540
- internalformat, GL_UNSIGNED_BYTE,
1541
- blank)
1542
-
1543
- items = []
1544
- for i, image in enumerate(images):
1545
- item = cls.region_class(0, 0, i, item_width, item_height, texture)
1546
- items.append(item)
1547
- image.blit_to_texture(texture.target, texture.level, image.anchor_x, image.anchor_y, z=i)
1548
-
1549
- glFlush()
1550
-
1551
- texture.items = items
1552
- texture.item_width = item_width
1553
- texture.item_height = item_height
1554
- return texture
1555
-
1556
- @classmethod
1557
- def create_for_image_grid(cls, grid, internalformat=GL_RGBA):
1558
- return cls.create_for_images(grid[:], internalformat)
1559
-
1560
- def __len__(self):
1561
- return len(self.items)
1562
-
1563
- def __getitem__(self, index):
1564
- return self.items[index]
1565
-
1566
- def __setitem__(self, index, value):
1567
- if type(index) is slice:
1568
- glBindTexture(self.target, self.id)
1569
-
1570
- for item, image in zip(self[index], value):
1571
- image.blit_to_texture(self.target, self.level, image.anchor_x, image.anchor_y, item.z)
1572
- else:
1573
- self.blit_into(value, value.anchor_x, value.anchor_y, self[index].z)
1574
-
1575
- def __iter__(self) -> Iterator[TextureRegion]:
1576
- return iter(self.items)
1577
-
1578
-
1579
- class TextureArrayRegion(TextureRegion):
1580
- """A region of a TextureArray, presented as if it were a separate texture."""
1581
-
1582
- def __repr__(self):
1583
- return f"{self.__class__.__name__}(id={self.id}, size={self.width}x{self.height}, layer={self.z})"
1584
-
1585
-
1586
- class TextureArray(Texture, UniformTextureSequence):
1587
- def __init__(self, width, height, target, tex_id, max_depth):
1588
- super().__init__(width, height, target, tex_id)
1589
- self.max_depth = max_depth
1590
- self.items = []
1591
-
1592
- @classmethod
1593
- def create(cls, width: int, height: int, internalformat: int = GL_RGBA,
1594
- min_filter: int | None = None, mag_filter: int | None = None, max_depth: int = 256) -> TextureArray:
1595
- """Create an empty TextureArray.
1596
-
1597
- You may specify the maximum depth, or layers, the Texture Array should have. This defaults
1598
- to 256, but will be hardware and driver dependent.
1599
-
1600
- Args:
1601
- width:
1602
- Width of the texture.
1603
- height:
1604
- Height of the texture.
1605
- internalformat:
1606
- GL constant giving the internal format of the texture array; for example, ``GL_RGBA``.
1607
- min_filter:
1608
- The minifaction filter used for this texture array, commonly ``GL_LINEAR`` or ``GL_NEAREST``
1609
- mag_filter:
1610
- The magnification filter used for this texture array, commonly ``GL_LINEAR`` or ``GL_NEAREST``
1611
- max_depth:
1612
- The number of layers in the texture array.
1613
-
1614
- .. versionadded:: 2.0
1615
- """
1616
- min_filter = min_filter or cls.default_min_filter
1617
- mag_filter = mag_filter or cls.default_mag_filter
1618
-
1619
- max_depth_limit = get_max_array_texture_layers()
1620
- assert max_depth <= max_depth_limit, f"TextureArray max_depth supported is {max_depth_limit}."
1621
-
1622
- tex_id = GLuint()
1623
- glGenTextures(1, byref(tex_id))
1624
- glBindTexture(GL_TEXTURE_2D_ARRAY, tex_id.value)
1625
- glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, min_filter)
1626
- glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, mag_filter)
1627
-
1628
- glTexImage3D(GL_TEXTURE_2D_ARRAY, 0,
1629
- internalformat,
1630
- width, height, max_depth,
1631
- 0,
1632
- internalformat, GL_UNSIGNED_BYTE,
1633
- 0)
1634
- glFlush()
1635
-
1636
- texture = cls(width, height, GL_TEXTURE_2D_ARRAY, tex_id.value, max_depth)
1637
- texture.min_filter = min_filter
1638
- texture.mag_filter = mag_filter
1639
-
1640
- return texture
1641
-
1642
- def _verify_size(self, image: AbstractImage) -> None:
1643
- if image.width > self.width or image.height > self.height:
1644
- raise TextureArraySizeExceeded(
1645
- f'Image ({image.width}x{image.height}) exceeds the size of the TextureArray ({self.width}x'
1646
- f'{self.height})')
1647
-
1648
- def add(self, image: pyglet.image.ImageData) -> TextureArrayRegion:
1649
- if len(self.items) >= self.max_depth:
1650
- raise TextureArrayDepthExceeded("TextureArray is full.")
1651
-
1652
- self._verify_size(image)
1653
- start_length = len(self.items)
1654
- item = self.region_class(0, 0, start_length, image.width, image.height, self)
1655
-
1656
- self.blit_into(image, image.anchor_x, image.anchor_y, start_length)
1657
- self.items.append(item)
1658
- return item
1659
-
1660
- def allocate(self, *images: AbstractImage) -> list[TextureArrayRegion]:
1661
- """Allocates multiple images at once."""
1662
- if len(self.items) + len(images) > self.max_depth:
1663
- raise TextureArrayDepthExceeded("The amount of images being added exceeds the depth of this TextureArray.")
1664
-
1665
- glBindTexture(self.target, self.id)
1666
-
1667
- start_length = len(self.items)
1668
- for i, image in enumerate(images):
1669
- self._verify_size(image)
1670
- item = self.region_class(0, 0, start_length + i, image.width, image.height, self)
1671
- self.items.append(item)
1672
- image.blit_to_texture(self.target, self.level, image.anchor_x, image.anchor_y, start_length + i)
1673
-
1674
- return self.items[start_length:]
1675
-
1676
- @classmethod
1677
- def create_for_image_grid(cls, grid, internalformat: int = GL_RGBA) -> TextureArray:
1678
- texture_array = cls.create(grid[0].width, grid[0].height, internalformat, max_depth=len(grid))
1679
- texture_array.allocate(*grid[:])
1680
- return texture_array
1681
-
1682
- def __len__(self) -> int:
1683
- return len(self.items)
1684
-
1685
- def __getitem__(self, index) -> TextureArrayRegion:
1686
- return self.items[index]
1687
-
1688
- def __setitem__(self, index, value) -> None:
1689
- if type(index) is slice:
1690
- glBindTexture(self.target, self.id)
1691
-
1692
- for old_item, image in zip(self[index], value):
1693
- self._verify_size(image)
1694
- item = self.region_class(0, 0, old_item.z, image.width, image.height, self)
1695
- image.blit_to_texture(self.target, self.level, image.anchor_x, image.anchor_y, old_item.z)
1696
- self.items[old_item.z] = item
1697
- else:
1698
- self._verify_size(value)
1699
- item = self.region_class(0, 0, index, value.width, value.height, self)
1700
- self.blit_into(value, value.anchor_x, value.anchor_y, index)
1701
- self.items[index] = item
1702
-
1703
- def __iter__(self) -> Iterator[TextureRegion]:
1704
- return iter(self.items)
1705
-
1706
-
1707
- TextureArray.region_class = TextureArrayRegion
1708
- TextureArrayRegion.region_class = TextureArrayRegion
1709
-
1710
-
1711
- class TileableTexture(Texture):
1712
- """A texture that can be tiled efficiently.
1713
-
1714
- Use :py:class:`~pyglet.image.create_for_image` classmethod to construct.
1715
- """
1716
-
1717
- def get_region(self, x: int, y: int, width: int, height: int):
1718
- raise ImageException(f"Cannot get region of {self}")
1719
-
1720
- def blit_tiled(self, x: int, y: int, z: int, width: int, height: int) -> None:
1721
- """Blit this texture tiled over the given area.
1722
-
1723
- The image will be tiled with the bottom-left corner of the destination
1724
- rectangle aligned with the anchor point of this texture.
1725
- """
1726
- u1 = self.anchor_x / self.width
1727
- v1 = self.anchor_y / self.height
1728
- u2 = u1 + width / self.width
1729
- v2 = v1 + height / self.height
1730
- w, h = width, height
1731
- t = self.tex_coords
1732
-
1733
- vertices = (x, y, z,
1734
- x + w, y, z,
1735
- x + w, y + h, z,
1736
- x, y + h, z)
1737
-
1738
- tex_coords = (u1, v1, t[2],
1739
- u2, v1, t[5],
1740
- u2, v2, t[8],
1741
- u1, v2, t[11])
1742
-
1743
- glActiveTexture(GL_TEXTURE0)
1744
- glBindTexture(self.target, self.id)
1745
- pyglet.graphics.draw_indexed(4, GL_TRIANGLES, [0, 1, 2, 0, 2, 3],
1746
- position=('f', vertices),
1747
- tex_coords=('f', tex_coords))
1748
- glBindTexture(self.target, 0)
1749
-
1750
- @classmethod
1751
- def create_for_image(cls, image: AbstractImage) -> Texture:
1752
- image = image.get_image_data()
1753
- return image.create_texture(cls)
1754
-
1755
-
1756
- class ImageGrid(AbstractImage, AbstractImageSequence):
1757
- """An imaginary grid placed over an image allowing easy access to
1758
- regular regions of that image.
1759
-
1760
- The grid can be accessed either as a complete image, or as a sequence
1761
- of images. The most useful applications are to access the grid
1762
- as a :py:class:`~pyglet.image.TextureGrid`::
1763
-
1764
- image_grid = ImageGrid(...)
1765
- texture_grid = image_grid.get_texture_sequence()
1766
-
1767
- or as a :py:class:`~pyglet.image.Texture3D`::
1768
-
1769
- image_grid = ImageGrid(...)
1770
- texture_3d = Texture3D.create_for_image_grid(image_grid)
1771
-
1772
- """
1773
-
1774
- _items: list
1775
- _texture_grid: TextureGrid = None
1776
-
1777
- def __init__(self, image: AbstractImage, rows: int, columns: int, item_width: int | None = None,
1778
- item_height: int | None = None, row_padding: int = 0, column_padding: int = 0) -> None:
1779
- """Construct a grid for the given image.
1780
-
1781
- You can specify parameters for the grid, for example setting
1782
- the padding between cells. Grids are always aligned to the
1783
- bottom-left corner of the image.
1784
-
1785
- Args:
1786
- image:
1787
- Image over which to construct the grid.
1788
- rows:
1789
- Number of rows in the grid.
1790
- columns:
1791
- Number of columns in the grid.
1792
- item_width:
1793
- Width of each column. If unspecified, is calculated such
1794
- that the entire image width is used.
1795
- item_height:
1796
- Height of each row. If unspecified, is calculated such that
1797
- the entire image height is used.
1798
- row_padding:
1799
- Pixels separating adjacent rows. The padding is only
1800
- inserted between rows, not at the edges of the grid.
1801
- column_padding:
1802
- Pixels separating adjacent columns. The padding is only
1803
- inserted between columns, not at the edges of the grid.
1804
- """
1805
- super().__init__(image.width, image.height)
1806
- self._items = []
1807
- self.image = image
1808
- self.rows = rows
1809
- self.columns = columns
1810
- self.item_width = item_width or (image.width - column_padding * (columns - 1)) // columns
1811
- self.item_height = item_height or (image.height - row_padding * (rows - 1)) // rows
1812
- self.row_padding = row_padding
1813
- self.column_padding = column_padding
1814
-
1815
- def get_texture(self, rectangle: bool = False) -> Texture:
1816
- return self.image.get_texture(rectangle)
1817
-
1818
- def get_image_data(self) -> ImageData:
1819
- return self.image.get_image_data()
1820
-
1821
- def get_texture_sequence(self) -> TextureGrid:
1822
- if not self._texture_grid:
1823
- self._texture_grid = TextureGrid(self)
1824
- return self._texture_grid
1825
-
1826
- def get_mipmapped_texture(self) -> Texture:
1827
- raise NotImplementedError(f"Not implemented for {self}.")
1828
-
1829
- def get_region(self, x: int, y: int, width: int, height: int) -> AbstractImage:
1830
- raise NotImplementedError(f"Not implemented for {self}.")
1831
-
1832
- def blit(self, x: int, y: int, z: int = 0) -> None:
1833
- raise NotImplementedError(f"Not implemented for {self}.")
1834
-
1835
- def blit_into(self, source, x: int, y: int, z: int) -> None:
1836
- raise NotImplementedError(f"Not implemented for {self}.")
1837
-
1838
- def blit_to_texture(self, target: int, level: int, x: int, y: int, z: int, internalformat: int = None):
1839
- raise NotImplementedError(f"Not implemented for {self}.")
1840
-
1841
- def _update_items(self) -> None:
1842
- if not self._items:
1843
- y = 0
1844
- for row in range(self.rows):
1845
- x = 0
1846
- for col in range(self.columns):
1847
- self._items.append(self.image.get_region(x, y, self.item_width, self.item_height))
1848
- x += self.item_width + self.column_padding
1849
- y += self.item_height + self.row_padding
1850
-
1851
- def __getitem__(self, index) -> ImageDataRegion:
1852
- self._update_items()
1853
- if type(index) is tuple:
1854
- row, column = index
1855
- assert 0 <= row < self.rows and 0 <= column < self.columns
1856
- return self._items[row * self.columns + column]
1857
- else:
1858
- return self._items[index]
1859
-
1860
- def __setitem__(self, index: int, value: AbstractImage):
1861
- raise NotImplementedError
1862
-
1863
- def __len__(self) -> int:
1864
- return self.rows * self.columns
1865
-
1866
- def __iter__(self) -> Iterator[ImageDataRegion]:
1867
- self._update_items()
1868
- return iter(self._items)
1869
-
1870
-
1871
- class TextureGrid(TextureRegion, UniformTextureSequence):
1872
- """A texture containing a regular grid of texture regions.
1873
-
1874
- To construct, create an :py:class:`~pyglet.image.ImageGrid` first::
1875
-
1876
- image_grid = ImageGrid(...)
1877
- texture_grid = TextureGrid(image_grid)
1878
-
1879
- The texture grid can be accessed as a single texture, or as a sequence
1880
- of :py:class:`~pyglet.image.TextureRegion`. When accessing as a sequence, you can specify
1881
- integer indexes, in which the images are arranged in rows from the
1882
- bottom-left to the top-right::
1883
-
1884
- # assume the texture_grid is 3x3:
1885
- current_texture = texture_grid[3] # get the middle-left image
1886
-
1887
- You can also specify tuples in the sequence methods, which are addressed
1888
- as ``row, column``::
1889
-
1890
- # equivalent to the previous example:
1891
- current_texture = texture_grid[1, 0]
1892
-
1893
- When using tuples in a slice, the returned sequence is over the
1894
- rectangular region defined by the slice::
1895
-
1896
- # returns center, center-right, center-top, top-right images in that
1897
- # order:
1898
- images = texture_grid[(1,1):]
1899
- # equivalent to
1900
- images = texture_grid[(1,1):(3,3)]
1901
-
1902
- """
1903
- items: list
1904
- rows: int
1905
- columns: int
1906
- item_width: int
1907
- item_height: int
1908
-
1909
- def __init__(self, grid: ImageGrid) -> None:
1910
- image = grid.get_texture()
1911
- if isinstance(image, TextureRegion):
1912
- owner = image.owner
1913
- else:
1914
- owner = image
1915
-
1916
- super().__init__(image.x, image.y, image.z, image.width, image.height, owner)
1917
-
1918
- items = []
1919
- y = 0
1920
- for row in range(grid.rows):
1921
- x = 0
1922
- for col in range(grid.columns):
1923
- items.append(self.get_region(x, y, grid.item_width, grid.item_height))
1924
- x += grid.item_width + grid.column_padding
1925
- y += grid.item_height + grid.row_padding
1926
-
1927
- self.items = items
1928
- self.rows = grid.rows
1929
- self.columns = grid.columns
1930
- self.item_width = grid.item_width
1931
- self.item_height = grid.item_height
1932
-
1933
- def get(self, row: int, column: int):
1934
- return self[(row, column)]
1935
-
1936
- def __getitem__(self, index: int | tuple[int, int] | slice) -> TextureRegion | list[TextureRegion]:
1937
- if type(index) is slice:
1938
- if type(index.start) is not tuple and type(index.stop) is not tuple:
1939
- return self.items[index]
1940
- else:
1941
- row1 = 0
1942
- col1 = 0
1943
- row2 = self.rows
1944
- col2 = self.columns
1945
- if type(index.start) is tuple:
1946
- row1, col1 = index.start
1947
- elif type(index.start) is int:
1948
- row1 = index.start // self.columns
1949
- col1 = index.start % self.columns
1950
- assert 0 <= row1 < self.rows and 0 <= col1 < self.columns
1951
-
1952
- if type(index.stop) is tuple:
1953
- row2, col2 = index.stop
1954
- elif type(index.stop) is int:
1955
- row2 = index.stop // self.columns
1956
- col2 = index.stop % self.columns
1957
- assert 0 <= row2 <= self.rows and 0 <= col2 <= self.columns
1958
-
1959
- result = []
1960
- i = row1 * self.columns
1961
- for row in range(row1, row2):
1962
- result += self.items[i + col1:i + col2]
1963
- i += self.columns
1964
- return result
1965
- else:
1966
- if type(index) is tuple:
1967
- row, column = index
1968
- assert 0 <= row < self.rows and 0 <= column < self.columns
1969
- return self.items[row * self.columns + column]
1970
- elif type(index) is int:
1971
- return self.items[index]
1972
-
1973
- def __setitem__(self, index: int | slice, value: AbstractImage | Sequence[AbstractImage]):
1974
- if type(index) is slice:
1975
- for region, image in zip(self[index], value):
1976
- if image.width != self.item_width or image.height != self.item_height:
1977
- raise ImageException('Image has incorrect dimensions')
1978
- image.blit_into(region, image.anchor_x, image.anchor_y, 0)
1979
- else:
1980
- image = value
1981
- if image.width != self.item_width or image.height != self.item_height:
1982
- raise ImageException('Image has incorrect dimensions')
1983
- image.blit_into(self[index], image.anchor_x, image.anchor_y, 0)
1984
-
1985
- def __len__(self) -> int:
1986
- return len(self.items)
1987
-
1988
- def __iter__(self) -> Iterator[TextureRegion]:
1989
- return iter(self.items)
1990
-
1991
-
1992
- # Default Framebuffer classes:
1993
- ###############################################################
1994
-
1995
-
1996
- class BufferManager:
1997
- """Manages the set of framebuffers for a context.
1998
-
1999
- Use :py:func:`~pyglet.image.get_buffer_manager` to obtain the instance
2000
- of this class for the current context.
2001
- """
2002
-
2003
- def __init__(self):
2004
- self._color_buffer = None
2005
- self._depth_buffer = None
2006
- self.free_stencil_bits = None
2007
- self._refs = []
2008
-
2009
- @staticmethod
2010
- def get_viewport() -> tuple:
2011
- """Get the current OpenGL viewport dimensions (left, bottom, right, top)."""
2012
- viewport = (GLint * 4)()
2013
- glGetIntegerv(GL_VIEWPORT, viewport)
2014
- return tuple(viewport)
2015
-
2016
- def get_color_buffer(self) -> ColorBufferImage:
2017
- """Get the color buffer."""
2018
- viewport = self.get_viewport()
2019
- viewport_width = viewport[2]
2020
- viewport_height = viewport[3]
2021
- if (not self._color_buffer or
2022
- viewport_width != self._color_buffer.width or
2023
- viewport_height != self._color_buffer.height):
2024
- self._color_buffer = ColorBufferImage(*viewport)
2025
- return self._color_buffer
2026
-
2027
- def get_depth_buffer(self) -> DepthBufferImage:
2028
- """Get the depth buffer."""
2029
- viewport = self.get_viewport()
2030
- viewport_width = viewport[2]
2031
- viewport_height = viewport[3]
2032
- if (not self._depth_buffer or
2033
- viewport_width != self._depth_buffer.width or
2034
- viewport_height != self._depth_buffer.height):
2035
- self._depth_buffer = DepthBufferImage(*viewport)
2036
- return self._depth_buffer
2037
-
2038
- def get_buffer_mask(self) -> BufferImageMask:
2039
- """Get a free bitmask buffer.
2040
-
2041
- A bitmask buffer is a buffer referencing a single bit in the stencil
2042
- buffer. If no bits are free, ``ImageException`` is raised. Bits are
2043
- released when the bitmask buffer is garbage collected.
2044
- """
2045
- if self.free_stencil_bits is None:
2046
- try:
2047
- stencil_bits = GLint()
2048
- glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER,
2049
- GL_STENCIL,
2050
- GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE,
2051
- stencil_bits)
2052
- self.free_stencil_bits = list(range(stencil_bits.value))
2053
- except GLException:
2054
- pass
2055
-
2056
- if not self.free_stencil_bits:
2057
- raise ImageException('No free stencil bits are available.')
2058
-
2059
- stencil_bit = self.free_stencil_bits.pop(0)
2060
- x, y, width, height = self.get_viewport()
2061
- bufimg = BufferImageMask(x, y, width, height)
2062
- bufimg.stencil_bit = stencil_bit
2063
-
2064
- def release_buffer(ref, owner=self):
2065
- owner.free_stencil_bits.insert(0, stencil_bit)
2066
-
2067
- self._refs.append(weakref.ref(bufimg, release_buffer))
2068
-
2069
- return bufimg
2070
-
2071
-
2072
- def get_buffer_manager() -> BufferManager:
2073
- """Get the buffer manager for the current OpenGL context."""
2074
- context = pyglet.gl.current_context
2075
- if not hasattr(context, 'image_buffer_manager'):
2076
- context.image_buffer_manager = BufferManager()
2077
- return context.image_buffer_manager
2078
-
2079
-
2080
- class BufferImage(AbstractImage):
2081
- """An abstract "default" framebuffer."""
2082
-
2083
- #: The OpenGL read and write target for this buffer.
2084
- gl_buffer = GL_BACK
2085
-
2086
- #: The OpenGL format constant for image data.
2087
- gl_format = 0
2088
-
2089
- #: The format string used for image data.
2090
- format = ''
2091
-
2092
- owner = None
2093
-
2094
- def __init__(self, x, y, width, height):
2095
- super().__init__(width, height)
2096
- self.x = x
2097
- self.y = y
2098
- self.width = width
2099
- self.height = height
2100
-
2101
- def get_image_data(self):
2102
- buf = (GLubyte * (len(self.format) * self.width * self.height))()
2103
-
2104
- x = self.x
2105
- y = self.y
2106
- if self.owner:
2107
- x += self.owner.x
2108
- y += self.owner.y
2109
-
2110
- glReadBuffer(self.gl_buffer)
2111
- glPixelStorei(GL_PACK_ALIGNMENT, 1)
2112
- glReadPixels(x, y, self.width, self.height, self.gl_format, GL_UNSIGNED_BYTE, buf)
2113
- return ImageData(self.width, self.height, self.format, buf)
2114
-
2115
- def get_region(self, x, y, width, height):
2116
- if self.owner:
2117
- return self.owner.get_region(x + self.x, y + self.y, width, height)
2118
-
2119
- region = self.__class__(x + self.x, y + self.y, width, height)
2120
- region.gl_buffer = self.gl_buffer
2121
- region.owner = self
2122
- return region
2123
-
2124
- def get_texture(self) -> Texture:
2125
- raise NotImplementedError(f"Not implemented for {self}")
2126
-
2127
- def get_mipmapped_texture(self) -> Texture:
2128
- raise NotImplementedError(f"Not implemented for {self}")
2129
-
2130
- def blit(self, x: int, y: int, z: int = 0) -> None:
2131
- raise NotImplementedError(f"Not implemented for {self}")
2132
-
2133
- def blit_into(self, source, x: int, y: int, z: int) -> None:
2134
- raise NotImplementedError(f"Not implemented for {self}")
2135
-
2136
- def blit_to_texture(self, target: int, level: int, x: int, y: int, z: int, internalformat: int = None):
2137
- raise NotImplementedError(f"Not implemented for {self}")
2138
-
2139
-
2140
- class ColorBufferImage(BufferImage):
2141
- """A color framebuffer.
2142
-
2143
- This class is used to wrap the primary color buffer (i.e., the back
2144
- buffer)
2145
- """
2146
- gl_format = GL_RGBA
2147
- format = 'RGBA'
2148
-
2149
- def get_texture(self):
2150
- texture = Texture.create(self.width, self.height, GL_TEXTURE_2D, GL_RGBA, blank_data=False)
2151
- self.blit_to_texture(texture.target, texture.level, self.anchor_x, self.anchor_y, 0)
2152
- return texture
2153
-
2154
- def blit_to_texture(self, target: int, level: int, x: int, y: int, z: int, internalformat: int = None):
2155
- # TODO: use glCopyTexImage2D if `internalformat` is specified.
2156
- glReadBuffer(self.gl_buffer)
2157
- glCopyTexSubImage2D(target, level, x-self.anchor_x, y-self.anchor_y, self.x, self.y, self.width, self.height)
2158
-
2159
-
2160
- class DepthBufferImage(BufferImage):
2161
- """The depth buffer.
2162
- """
2163
- gl_format = GL_DEPTH_COMPONENT
2164
- format = 'R'
2165
-
2166
- def get_texture(self):
2167
- image_data = self.get_image_data()
2168
- return image_data.get_texture()
2169
-
2170
- def blit_to_texture(self, target: int, level: int, x: int, y: int, z: int, internalformat: int = None):
2171
- # TODO: use glCopyTexImage2D if `internalformat` is specified.
2172
- glReadBuffer(self.gl_buffer)
2173
- glCopyTexSubImage2D(target, level, x-self.anchor_x, y-self.anchor_y, self.x, self.y, self.width, self.height)
2174
-
2175
-
2176
- class BufferImageMask(BufferImage):
2177
- """A single bit of the stencil buffer."""
2178
- gl_format = GL_STENCIL_INDEX
2179
- format = 'R'
2180
-
2181
- # TODO mask methods
2182
-
2183
-
2184
231
  # Initialise default codecs
2185
232
  _add_default_codecs()