Source code for moderngl_window.loaders.texture.pillow

import logging
from pathlib import Path
from typing import Optional, Union

try:
    from PIL import Image
except ImportError as ex:
    raise ImportError("Texture loader 'PillowLoader' requires Pillow: {}".format(ex))

from moderngl_window.exceptions import ImproperlyConfigured
from moderngl_window.loaders.base import BaseLoader
from moderngl_window.meta.base import ResourceDescription
from moderngl_window.meta.texture import TextureDescription
from moderngl_window.resources.textures import TextureAny

logger = logging.getLogger(__name__)


[docs] class PillowLoader(BaseLoader): """Base loader using PIL/Pillow""" kind = "__unknown__" image: Image.Image meta: TextureDescription def __init__(self, meta: ResourceDescription): super().__init__(meta)
[docs] def load(self) -> TextureAny: raise NotImplementedError()
def _open_image(self) -> Image.Image: if self.meta.image: self.image = self.meta.image else: self.meta.resolved_path = self.find_texture(self.meta.path) logger.info("loading %s", self.meta.resolved_path) if not self.meta.resolved_path: raise ImproperlyConfigured("Cannot find texture: {}".format(self.meta.path)) self.image = Image.open(self.meta.resolved_path) # If the image is animated (like a gif anim) we convert it into a vertical strip if ( hasattr(self.image, "is_animated") and self.image.is_animated and hasattr(self.image, "n_frames") ): self.layers = self.image.n_frames anim = Image.new( self.image.palette.mode if self.image.palette is not None else "L", (self.image.width, self.image.height * self.image.n_frames), ) anim.putalpha(0) for frame_number in range(self.image.n_frames): self.image.seek(frame_number) frame = self._palette_to_raw(self.image, mode="RGBA") anim.paste(frame, (0, frame_number * self.image.height)) self.image = anim self.image = self._apply_modifiers(self.image) return self.image def _load_texture(self, path: Union[str, Path]) -> Image.Image: """Find and load separate texture. Useful when multiple textue files needs to be loaded""" resolved_path = self.find_texture(path) logger.info("loading %s", resolved_path) if not resolved_path: raise ImproperlyConfigured("Cannot find texture: {}".format(path)) image = Image.open(resolved_path) return self._apply_modifiers(image) def _apply_modifiers(self, image: Image.Image) -> Image.Image: if self.meta.flip_x: image = image.transpose(Image.Transpose.FLIP_LEFT_RIGHT) if self.meta.flip_y: image = image.transpose(Image.Transpose.FLIP_TOP_BOTTOM) return self._palette_to_raw(image) def _palette_to_raw(self, image: Image.Image, mode: Optional[str] = None) -> Image.Image: """Converts image to raw if palette is present""" if image.palette and image.palette.mode.lower() in ["rgb", "rgba"]: mode = mode or image.palette.mode logger.debug("Converting P image to %s using palette", mode) return image.convert(mode) return image def _close_image(self) -> None: self.image.close()
def image_data(image: Image.Image) -> tuple[int, bytes]: """Get components and bytes for an image. The number of components is assumed by image size and the byte length of the raw data. Returns: tuple[int, bytes]: Number of components, byte data """ # NOTE: We might want to check the actual image.mode # and convert to an acceptable format. # At the moment we load the data as is. data = image.tobytes() components = len(data) // (image.size[0] * image.size[1]) logger.debug( "image_data size=[%s, %s] components=%s bytes=%s", image.size[0], image.size[1], components, len(data), ) return components, data