Source code for tmxlib.image_base

"""Image base classes
---------------------

"""

from __future__ import division

import warnings

from tmxlib import helpers, fileio


def _clamp(value, minimum, maximum):
    if value < minimum:
        return minimum
    elif value > maximum:
        return maximum
    else:
        return value


[docs]class ImageBase(helpers.SizeMixin): """Image base class This defines the basic image API, shared by :class:`~tmxlib.image_base.Image` and :class:`~tmxlib.image_base.ImageRegion`. Pixels are represented as (r, g, b, a) float tuples, with components in the range of 0 to 1. """ x, y = helpers.unpacked_properties('top_left')
[docs] def __getitem__(self, pos): """Get a pixel or region With a pair of integers, this returns a pixel via :meth:`~tmxlib.image_base.Image.get_pixel`: :param pos: pair of integers, (x, y) :return: pixel at (x, y) as a (r, g, b, a) float tuple With a pair of slices, returns a sub-image: :param pos: pair of slices, (left:right, top:bottom) :return: a :class:`~tmxlib.image_base.ImageRegion` """ x, y = pos try: left = x.start right = x.stop top = y.start bottom = y.stop except AttributeError: return self.get_pixel(x, y) else: for c in x, y: if c.step not in (None, 1): raise ValueError('step not supported for slicing images') left, top = self._wrap_coords( 0 if left is None else left, 0 if top is None else top) right, bottom = self._wrap_coords( self.width if right is None else right, self.height if bottom is None else bottom) left = _clamp(left, 0, self.width) right = _clamp(right, left, self.width) top = _clamp(top, 0, self.height) bottom = _clamp(bottom, top, self.height) return ImageRegion(self, (left, top), (right - left, bottom - top))
def _parent_info(self): """Return (x offset, y offset, immutable image) Used to make sure the parents of ImageRegion is always an Image, not another region or a canvas. """ return 0, 0, self
[docs]class Image(ImageBase, fileio.ReadWriteBase): """An image. Conceptually, an 2D array of pixels. .. note:: This is an abstract base class. Use :func:`tmxlib.image.open` or :data:`tmxlib.image.preferred_image_class` to get a usable subclass. init arguments that become attributes: .. autoattribute:: data .. autoattribute:: size If given in constructor, the image doesn't have to be decoded to get this information, somewhat speeding up operations that don't require pixel access. If it's given in constructor and it does not equal the actual image size, an exception will be raised as soon as the image is decoded. .. attribute:: source The file name of this image, if it is to be saved separately from maps/tilesets that use it. .. attribute:: trans A color key used for transparency .. note:: Currently, loading images that use color-key transparency is very inefficient. If possible, use the alpha channel instead. Images support indexing (``img[x, y]``); see :meth:`tmxlib.image_base.ImageBase.__getitem__` """ # XXX: Make `trans` actually work _rw_obj_type = 'image' # Implement ImageRegion API top_left = 0, 0 def __init__(self, data=None, trans=None, size=None, source=None): self._data = data self.source = source self._size = size self.trans = trans @property
[docs] def size(self): """Size of the image, in pixels. """ if self._size: return self._size else: self.load_image() # XXX: Not available without an image backend! return self.size
@property
[docs] def data(self): """Data of this image, as read from disk. """ if self._data: return self._data else: try: base_path = self.base_path except AttributeError: base_path = None serializer = fileio.serializer_getdefault(object=self) self._data = serializer.load_file(self.source, base_path=base_path) return self._data
[docs] def load_image(self): """Load the image from self.data, and set self._size If self._size is already set, assert that it equals """ raise TypeError('Image data not available')
[docs] def get_pixel(self, x, y): """Get the color of the pixel at position (x, y) as a RGBA 4-tuple. Supports negative indices by wrapping around in the obvious way. """ raise TypeError('Image data not available')
[docs]class ImageRegion(ImageBase): """A rectangular region of a larger image init arguments that become attributes: .. attribute:: parent The "parent" image .. attribute:: top_left The coordinates of the top-left corner of the region. Will also available as ``x`` and ``y`` attributes. .. attribute:: size The size of the region. Will also available as ``width`` and ``height`` attributes. """ def __init__(self, parent, top_left, size): self.top_left = top_left self.size = size if self.x < 0 or self.y < 0: raise ValueError('Image region coordinates may not be negative') if (self.x + self.width > parent.width or self.y + self.height > parent.height): raise ValueError('Image region extends outside parent image') px, py, self.parent = parent._parent_info() self.x += px self.y += py @property def image(self): warnings.warn("ImageRegion.image is deprecated; use parent instead", category=DeprecationWarning) return self.parent @image.setter def image(self, value): warnings.warn("ImageRegion.image is deprecated; use parent instead", category=DeprecationWarning) self.parent = value @property def trans(self): return self.parent.trans
[docs] def get_pixel(self, x, y): """Get the color of the pixel at position (x, y) as a RGBA 4-tuple. Supports negative indices by wrapping around in the obvious way. """ x, y = self._wrap_coords(x, y) if not (0 <= int(x) < self.width): raise ValueError('x coordinate out of bounds') if not (0 <= int(y) < self.height): raise ValueError('y coordinate out of bounds') return self.parent.get_pixel(x + self.x, y + self.y)
def _repr_png_(self): crop_box = self.x, self.y, self.x + self.width, self.y + self.height return self.parent._repr_png_(crop_box) def _parent_info(self): return self.x, self.y, self.parent