from collections import namedtuple
from typing import Any, Optional
import moderngl
from moderngl_window.exceptions import ImproperlyConfigured
from moderngl_window.loaders.texture.pillow import PillowLoader, image_data
from moderngl_window.meta.base import ResourceDescription
from moderngl_window.meta.texture import TextureDescription
FaceInfo = namedtuple("FaceInfo", ["width", "height", "data", "components"])
[docs]
class Loader(PillowLoader):
kind = "cube"
meta: TextureDescription
def __init__(self, meta: ResourceDescription):
super().__init__(meta)
[docs]
def load(self) -> moderngl.TextureCube:
"""Load a texture cube as described by the supplied ``TextureDescription```
Returns:
moderngl.TextureCube: The TextureArray instance
"""
pos_x = self._load_face(self.meta.pos_x, face_name="pos_x")
pos_y = self._load_face(self.meta.pos_y, face_name="pos_y")
pos_z = self._load_face(self.meta.pos_z, face_name="pos_z")
neg_x = self._load_face(self.meta.neg_x, face_name="neg_x")
neg_y = self._load_face(self.meta.neg_y, face_name="neg_y")
neg_z = self._load_face(self.meta.neg_z, face_name="neg_z")
self._validate([pos_x, pos_y, pos_z, neg_x, neg_y, neg_z])
texture = self.ctx.texture_cube(
(pos_x.width, pos_x.height),
pos_x.components,
pos_x.data + neg_x.data + pos_y.data + neg_y.data + pos_z.data + neg_z.data,
)
texture.extra = {"meta": self.meta}
if self.meta.mipmap_levels is not None:
self.meta.mipmap = True
if self.meta.mipmap:
if isinstance(self.meta.mipmap_levels, tuple):
texture.build_mipmaps(*self.meta.mipmap_levels)
else:
texture.build_mipmaps()
if self.meta.anisotropy:
texture.anisotropy = self.meta.anisotropy
return texture
def _load_face(self, path: Optional[str], face_name: Optional[str] = None) -> FaceInfo:
"""Obtain raw byte data for a face
Returns:
tuple[int, bytes]: number of components, byte data
"""
if not path:
raise ImproperlyConfigured(f"{face_name} texture face not supplied")
image = self._load_texture(path)
components, data = image_data(image)
return FaceInfo(width=image.size[0], height=image.size[1], data=data, components=components)
def _validate(self, faces: list[FaceInfo]) -> Any:
"""Validates each face ensuring components and size it the same"""
components = faces[0].components
data_size = len(faces[0].data)
for face in faces:
if face.components != components:
raise ImproperlyConfigured(
"Cubemap face textures have different number of components"
)
if len(face.data) != data_size:
raise ImproperlyConfigured("Cubemap face textures must all have the same size")
return components