Source code for spyral.image

"""A module for manipulating Images, which are specially wrapped Pygame
surfaces.
"""

import pygame
import spyral
import copy

def _new_spyral_surface(size):
    """
    Internal method for creating a new Spyral-compliant Pygame surface.
    """
    return pygame.Surface((int(size[0]),
                           int(size[1])),
                          pygame.SRCALPHA, 32).convert_alpha()

[docs]def from_sequence(images, orientation="right", padding=0): """ A function that returns a new Image from a list of images by placing them next to each other. :param images: A list of images to lay out. :type images: List of :class:`Image <spyral.Image>` :param str orientation: Either 'left', 'right', 'above', 'below', or 'square' (square images will be placed in a grid shape, like a chess board). :param padding: The padding between each image. Can be specified as a scalar number (for constant padding between all images) or a list (for different paddings between each image). :type padding: int or a list of ints. :returns: A new :class:`Image <spyral.Image>` """ if orientation == 'square': length = int(math.ceil(math.sqrt(len(images)))) max_height = 0 for index, image in enumerate(images): if index % length == 0: x = 0 y += max_height max_height = 0 else: x += image.width max_height = max(max_height, image.height) sequence.append((image, (x, y))) else: if orientation in ('left', 'right'): selector = spyral.Vec2D(1, 0) else: selector = spyral.Vec2D(0, 1) if orientation in ('left', 'above'): reversed(images) if type(padding) in (float, int, long): padding = [padding] * len(images) else: padding = list(padding) padding.append(0) base = spyral.Vec2D(0, 0) sequence = [] for image, padding in zip(images, padding): sequence.append((image, base)) base = base + selector * (image.size + (padding, padding)) return from_conglomerate(sequence)
[docs]def from_conglomerate(sequence): """ A function that generates a new image from a sequence of (image, position) pairs. These images will be placed onto a singe image large enough to hold all of them. More explicit and less convenient than :func:`from_seqeuence <spyral.image.from_sequence>`. :param sequence: A list of (image, position) pairs, where the positions are :class:`Vec2D <spyral.Vec2D>` s. :type sequence: List of image, position pairs. :returns: A new :class:`Image <spyral.Image>` """ width, height = 0, 0 for image, (x, y) in sequence: width = max(width, x+image.width) height = max(height, y+image.height) new = Image(size=(width, height)) for image, (x, y) in sequence: new.draw_image(image, (x, y)) return new
[docs]def render_nine_slice(image, size): """ Creates a new image by dividing the given image into a 3x3 grid, and stretching the sides and center while leaving the corners the same size. This is ideal for buttons and other rectangular shapes. :param image: The image to stretch. :type image: :class:`Image <spyral.Image>` :param size: The new (width, height) of this image. :type size: :class:`Vec2D <spyral.Vec2D>` :returns: A new :class:`Image <spyral.Image>` similar to the old one. """ bs = spyral.Vec2D(size) bw = size[0] bh = size[1] ps = image.size / 3 pw = int(ps[0]) ph = int(ps[1]) surf = image._surf # Hack: If we don't make it one px large things get cut image = spyral.Image(size=bs + (1, 1)) s = image._surf # should probably fix the math instead, but it works for now topleft = surf.subsurface(pygame.Rect((0, 0), ps)) left = surf.subsurface(pygame.Rect((0, ph), ps)) bottomleft = surf.subsurface(pygame.Rect((0, 2*pw), ps)) top = surf.subsurface(pygame.Rect((pw, 0), ps)) mid = surf.subsurface(pygame.Rect((pw, ph), ps)) bottom = surf.subsurface(pygame.Rect((pw, 2*ph), ps)) topright = surf.subsurface(pygame.Rect((2*pw, 0), ps)) right = surf.subsurface(pygame.Rect((2*ph, pw), ps)) bottomright = surf.subsurface(pygame.Rect((2*ph, 2*pw), ps)) # corners s.blit(topleft, (0, 0)) s.blit(topright, (bw - pw, 0)) s.blit(bottomleft, (0, bh - ph)) s.blit(bottomright, bs - ps) # left and right border for y in range(ph, bh - ph - ph, ph): s.blit(left, (0, y)) s.blit(right, (bw - pw, y)) s.blit(left, (0, bh - ph - ph)) s.blit(right, (bw - pw, bh - ph - ph)) # top and bottom border for x in range(pw, bw - pw - pw, pw): s.blit(top, (x, 0)) s.blit(bottom, (x, bh - ph)) s.blit(top, (bw - pw - pw, 0)) s.blit(bottom, (bw - pw - pw, bh - ph)) # center for x in range(pw, bw - pw - pw, pw): for y in range(ph, bh - ph - ph, ph): s.blit(mid, (x, y)) for x in range(pw, bw - pw - pw, pw): s.blit(mid, (x, bh - ph - ph)) for y in range(ph, bh - ph - ph, ph): s.blit(mid, (bw - pw - pw, y)) s.blit(mid, (bw - pw - pw, bh - ph - ph)) return image
[docs]class Image(object): """ The image is the basic drawable item in spyral. They can be created either by loading from common file formats, or by creating a new image and using some of the draw methods. Images are not drawn on their own, they are placed as the *image* attribute on Sprites to be drawn. Almost all of the methods of an Image instance return the Image itself, enabling commands to be chained in a `fluent interface <http://en.wikipedia.org/wiki/Fluent_interface>`_. :param size: If size is passed, creates a new blank image of that size to draw on. If you do not specify a size, you *must* pass in a filename. :type size: :class:`Vec2D <spyral.Vec2D>` :param str filename: If filename is set, the file with that name is loaded. The appendix has a list of the :ref:`valid image formats<ref.image_formats>`. If you do not specify a filename, you *must* pass in a size. """ def __init__(self, filename=None, size=None): if size is not None and filename is not None: raise ValueError("Must specify exactly one of size and filename.") if size is None and filename is None: raise ValueError("Must specify exactly one of size and filename.") if size is not None: self._surf = _new_spyral_surface(size) self._name = None else: self._surf = pygame.image.load(filename).convert_alpha() self._name = filename self._version = 1 def _get_width(self): return self._surf.get_width() #: The width of this image in pixels (int). Read-only. width = property(_get_width) def _get_height(self): return self._surf.get_height() #: The height of this image in pixels (int). Read-only. height = property(_get_height) def _get_size(self): return spyral.Vec2D(self._surf.get_size()) #: The (width, height) of the image (:class:`Vec2D <spyral.Vec2D`). #: Read-only. size = property(_get_size)
[docs] def fill(self, color): """ Fills the entire image with the specified color. :param color: a three-tuple of RGB values ranging from 0-255. Example: (255, 128, 0) is orange. :type color: a three-tuple of ints. :returns: This image. """ self._surf.fill(color) self._version += 1 spyral.util.scale_surface.clear(self._surf) return self
[docs] def draw_rect(self, color, position, size=None, border_width=0, anchor='topleft'): """ Draws a rectangle on this image. :param color: a three-tuple of RGB values ranging from 0-255. Example: (255, 128, 0) is orange. :type color: a three-tuple of ints. :param position: The starting position of the rect (top-left corner). If position is a Rect, then size should be `None`. :type position: :class:`Vec2D <spyral.Vec2D>` or :class:`Rect <spyral.Rect>` :param size: The size of the rectangle; should not be given if position is a rect. :type size: :class:`Vec2D <spyral.Vec2D>` :param int border_width: The width of the border to draw. If it is 0, the rectangle is filled with the color specified. :param str anchor: The anchor parameter is an :ref:`anchor position <ref.anchors>`. :returns: This image. """ if size is None: rect = spyral.Rect(position) else: rect = spyral.Rect(position, size) offset = self._calculate_offset(anchor, rect.size) pygame.draw.rect(self._surf, color, (rect.pos + offset, rect.size), border_width) self._version += 1 spyral.util.scale_surface.clear(self._surf) return self
[docs] def draw_lines(self, color, points, width=1, closed=False): """ Draws a series of connected lines on a image, with the vertices specified by points. This does not draw any sort of end caps on lines. :param color: a three-tuple of RGB values ranging from 0-255. Example: (255, 128, 0) is orange. :type color: a three-tuple of ints. :param points: A list of points that will be connected, one to another. :type points: A list of :class:`Vec2D <spyral.Vec2D>` s. :param int width: The width of the lines. :param bool closed: If closed is True, the first and last point will be connected. If closed is True and width is 0, the shape will be filled. :returns: This image. """ if width == 1: pygame.draw.aalines(self._surf, color, closed, points) else: pygame.draw.lines(self._surf, color, closed, points, width) self._version += 1 spyral.util.scale_surface.clear(self._surf) return self
[docs] def draw_circle(self, color, position, radius, width=0, anchor='topleft'): """ Draws a circle on this image. :param color: a three-tuple of RGB values ranging from 0-255. Example: (255, 128, 0) is orange. :type color: a three-tuple of ints. :param position: The center of this circle :type position: :class:`Vec2D <spyral.Vec2D>` :param int radius: The radius of this circle :param int width: The width of the circle. If it is 0, the circle is filled with the color specified. :param str anchor: The anchor parameter is an :ref:`anchor position <ref.anchors>`. :returns: This image. """ offset = self._calculate_offset(anchor) pygame.draw.circle(self._surf, color, (position + offset).floor(), radius, width) self._version += 1 spyral.util.scale_surface.clear(self._surf) return self
[docs] def draw_ellipse(self, color, position, size=None, border_width=0, anchor='topleft'): """ Draws an ellipse on this image. :param color: a three-tuple of RGB values ranging from 0-255. Example: (255, 128, 0) is orange. :type color: a three-tuple of ints. :param position: The starting position of the ellipse (top-left corner). If position is a Rect, then size should be `None`. :type position: :class:`Vec2D <spyral.Vec2D>` or :class:`Rect <spyral.Rect>` :param size: The size of the ellipse; should not be given if position is a rect. :type size: :class:`Vec2D <spyral.Vec2D>` :param int border_width: The width of the ellipse. If it is 0, the ellipse is filled with the color specified. :param str anchor: The anchor parameter is an :ref:`anchor position <ref.anchors>`. :returns: This image. """ if size is None: rect = spyral.Rect(position) else: rect = spyral.Rect(position, size) offset = self._calculate_offset(anchor, rect.size) pygame.draw.ellipse(self._surf, color, (rect.pos + offset, rect.size), border_width) self._version += 1 spyral.util.scale_surface.clear(self._surf) return self
[docs] def draw_point(self, color, position, anchor='topleft'): """ Draws a point on this image. :param color: a three-tuple of RGB values ranging from 0-255. Example: (255, 128, 0) is orange. :type color: a three-tuple of ints. :param position: The position of this point. :type position: :class:`Vec2D <spyral.Vec2D>` :param str anchor: The anchor parameter is an :ref:`anchor position <ref.anchors>`. :returns: This image. """ offset = self._calculate_offset(anchor) self._surf.set_at(position + offset, color) self._version += 1 spyral.util.scale_surface.clear(self._surf) return self
[docs] def draw_arc(self, color, start_angle, end_angle, position, size=None, border_width=0, anchor='topleft'): """ Draws an elliptical arc on this image. :param color: a three-tuple of RGB values ranging from 0-255. Example: (255, 128, 0) is orange. :type color: a three-tuple of ints. :param float start_angle: The starting angle, in radians, of the arc. :param float end_angle: The ending angle, in radians, of the arc. :param position: The starting position of the ellipse (top-left corner). If position is a Rect, then size should be `None`. :type position: :class:`Vec2D <spyral.Vec2D>` or :class:`Rect <spyral.Rect>` :param size: The size of the ellipse; should not be given if position is a rect. :type size: :class:`Vec2D <spyral.Vec2D>` :param int border_width: The width of the ellipse. If it is 0, the ellipse is filled with the color specified. :param str anchor: The anchor parameter is an :ref:`anchor position <ref.anchors>`. :returns: This image. """ if size is None: rect = spyral.Rect(position) else: rect = spyral.Rect(position, size) offset = self._calculate_offset(anchor, rect.size) pygame.draw.arc(self._surf, color, (rect.pos + offset, rect.size), start_angle, end_angle, border_width) self._version += 1 spyral.util.scale_surface.clear(self._surf) return self
[docs] def draw_image(self, image, position=(0, 0), anchor='topleft'): """ Draws another image over this one. :param image: The image to overlay on top of this one. :type image: :class:`Image <spyral.Image>` :param position: The position of this image. :type position: :class:`Vec2D <spyral.Vec2D>` :param str anchor: The anchor parameter is an :ref:`anchor position <ref.anchors>`. :returns: This image. """ offset = self._calculate_offset(anchor, image._surf.get_size()) self._surf.blit(image._surf, position + offset) self._version += 1 spyral.util.scale_surface.clear(self._surf) return self
[docs] def rotate(self, angle): """ Rotates the image by angle degrees clockwise. This may change the image dimensions if the angle is not a multiple of 90. Successive rotations degrate image quality. Save a copy of the original if you plan to do many rotations. :param float angle: The number of degrees to rotate. :returns: This image. """ self._surf = pygame.transform.rotate(self._surf, angle).convert_alpha() self._version += 1 return self
[docs] def scale(self, size): """ Scales the image to the destination size. :param size: The new size of the image. :type size: :class:`Vec2D <spyral.Vec2D>` :returns: This image. """ self._surf = pygame.transform.smoothscale(self._surf, size).convert_alpha() self._version += 1 return self
[docs] def flip(self, flip_x=True, flip_y=True): """ Flips the image horizontally, vertically, or both. :param bool flip_x: whether to flip horizontally. :param bool flip_y: whether to flip vertically. :returns: This image. """ self._version += 1 self._surf = pygame.transform.flip(self._surf, flip_x, flip_y).convert_alpha() return self
[docs] def copy(self): """ Returns a copy of this image that can be changed while preserving the original. :returns: A new image. """ new = copy.copy(self) new._surf = self._surf.copy() return new
[docs] def crop(self, position, size=None): """ Removes the edges of an image, keeping the internal rectangle specified by position and size. :param position: The upperleft corner of the internal rectangle that will be preserved. :type position: a :class:`Vec2D <spyral.Vec2D>` or a :class:`Rect <spyral.Rect>`. :param size: The size of the internal rectangle to preserve. If a Rect was passed in for position, this should be None. :type size: :class:`Vec2D <spyral.Vec2D>` or None. :returns: This image. """ if size is None: rect = spyral.Rect(position) else: rect = spyral.Rect(position, size) new = _new_spyral_surface(size) new.blit(self._surf, (0, 0), (rect.pos, rect.size)) self._surf = new self._version += 1 return self
def _calculate_offset(self, anchor_type, size=(0, 0)): """ Internal method for calculating the offset associated with an anchor type. :param anchor_type: A string indicating the position of the anchor, taken from :ref:`anchor position <ref.anchors>`. A numerical offset can also be specified. :type anchor_type: str or a :class:`Vec2D <spyral.Vec2D>`. :param size: The size of the region to offset in. :type size: :class:`Vec2D <spyral.Vec2D>`. """ w, h = self._surf.get_size() w2, h2 = size if anchor_type == 'topleft': return spyral.Vec2D(0, 0) elif anchor_type == 'topright': return spyral.Vec2D(w - w2, 0) elif anchor_type == 'midtop': return spyral.Vec2D((w - w2) / 2., 0) elif anchor_type == 'bottomleft': return spyral.Vec2D(0, h - h2) elif anchor_type == 'bottomright': return spyral.Vec2D(w - w2, h - h2) elif anchor_type == 'midbottom': return spyral.Vec2D((w - w2) / 2., h - h2) elif anchor_type == 'midleft': return spyral.Vec2D(0, (h - h2) / 2.) elif anchor_type == 'midright': return spyral.Vec2D(w - w2, (h - h2) / 2.) elif anchor_type == 'center': return spyral.Vec2D((w - w2) / 2., (h - h2) / 2.) else: return spyral.Vec2D(anchor_type) - spyral.Vec2D(w2, h2)