Utils

brighten(image, amount)

Use brighten_color to brighten all pixels in an image by amount.

Parameters:
  • image (Surface) –

    the input image

  • amount (int) –

    how much to increase brightness

robingame/image/utils.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def brighten(image: Surface, amount: int):
    """
    Use `brighten_color` to brighten all pixels in an image by `amount`. \

    Args:
        image: the input image
        amount: how much to increase brightness
    """
    width, height = image.get_size()
    # surface.copy() inherits surface's colorkey; preserving transparency
    new_image = image.copy()

    # iterate over all the pixels in the old surface, and write a pixel to the new surface in the
    # corresponding position. If the colour of the present pixel has an entry in the
    # color_mapping dict, then write the new colour instead of the old one.
    for x in range(width):
        for y in range(height):
            color = image.get_at((x, y))[:]
            new_color = brighten_color(color, amount)
            if new_color:
                new_image.set_at((x, y), pygame.Color(*new_color))
            else:
                new_image.set_at((x, y), pygame.Color(*color))

    return new_image

brighten_color(color, amount)

Increase all channels to brighten a colour. Does not allow values greater than 255.

Parameters:
  • color (Color | tuple) –

    the input colour

  • amount (int) –

    how much to increase each channel

Returns:
  • Color

    the output colour

robingame/image/utils.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def brighten_color(color: Color | tuple, amount: int) -> Color:
    """
    Increase all channels to brighten a colour.
    Does not allow values greater than 255.

    Args:
        color: the input colour
        amount: how much to increase each channel

    Returns:
        the output colour
    """
    color = Color(color)
    r = limit_value(color.r + amount, between=(0, 255))
    g = limit_value(color.g + amount, between=(0, 255))
    b = limit_value(color.b + amount, between=(0, 255))
    return Color(r, g, b, color.a)

empty_image(*args, **kwargs)

Generate an empty Surface with .convert_alpha() already called.

Returns:
  • Surface

    an empty Surface

robingame/image/utils.py
65
66
67
68
69
70
71
72
73
74
def empty_image(*args, **kwargs) -> Surface:
    """
    Generate an empty Surface with `.convert_alpha()` already called.

    Returns:
        an empty Surface
    """
    img = Surface(*args, **kwargs).convert_alpha()
    img.fill((0, 0, 0, 0))
    return img

flip_image(image, flip_x=False, flip_y=False)

Return a flipped copy of an image.

Parameters:
  • image (Surface) –

    input image

  • flip_x (bool) –

    flip horizontally

  • flip_y (bool) –

    flip vertically

Returns:
  • Surface

    output image

robingame/image/utils.py
170
171
172
173
174
175
176
177
178
179
180
181
182
def flip_image(image: Surface, flip_x: bool = False, flip_y: bool = False) -> Surface:
    """
    Return a flipped copy of an image.

    Args:
        image: input image
        flip_x: flip horizontally
        flip_y: flip vertically

    Returns:
        output image
    """
    return pygame.transform.flip(image, bool(flip_x), bool(flip_y))

flip_images(images, flip_x=False, flip_y=False)

Apply flip_image to a list of images.

robingame/image/utils.py
185
186
187
188
189
def flip_images(images: [Surface], flip_x: bool = False, flip_y: bool = False):
    """
    Apply `flip_image` to a list of images.
    """
    return [flip_image(image, flip_x, flip_y) for image in images]

init_display()

Make sure the pygame display is initialised (required for loading images). If the display already exists, return it. If not, generate a new 1x1 pixel display.

Returns:
  • Surface

    the pygame display

robingame/image/utils.py
11
12
13
14
15
16
17
18
19
20
21
22
23
def init_display() -> Surface:
    """
    Make sure the pygame display is initialised (required for loading images).
    If the display already exists, return it. If not, generate a new 1x1 pixel display.

    Returns:
        the pygame display
    """
    if not pygame.display.get_init():
        pygame.display.init()
        return pygame.display.set_mode((1, 1))
    else:
        return pygame.display.get_surface()

load_image(filename, colorkey=None)

Load an image. Abstracts away some of the pygame pitfalls.

Parameters:
  • filename (str | Path) –

    path to the image file

  • colorkey (Color | int) –

    sets the color to treat as transparent (like the green in greenscreen). if -1 is passed, then the color of the top-left pixel will be used.

Returns:
  • Surface

    the loaded image

robingame/image/utils.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def load_image(filename: str | Path, colorkey: Color | int = None) -> Surface:
    """
    Load an image. Abstracts away some of the pygame pitfalls.

    Args:
        filename: path to the image file
        colorkey: sets the color to treat as transparent (like the green in greenscreen).
            if `-1` is passed, then the color of the top-left pixel will be used.

    Returns:
        the loaded image
    """
    init_display()
    try:
        image = pygame.image.load(filename)
    except pygame.error:
        print("Unable to load image:", filename)
        raise

    # colorkey needs to be set before .convert_alpha() is called, because Surfaces with a
    # per-pixel transparency (i.e. after convert_alpha) ignore colorkey.
    if colorkey is not None:
        if colorkey == -1:
            colorkey = image.get_at((0, 0))
        image.set_colorkey(colorkey, pygame.RLEACCEL)

    image = image.convert_alpha()
    return image

load_image_sequence(pattern, colorkey=None, num_images=0)

Load a sequence of images.

Parameters:
  • pattern (Path | str) –

    glob pattern for the image sequence. E.g. if your folder of image contains "example1.png", "example2.png", etc, then your pattern should be "example*.png"

  • colorkey (Color) –

    used to recolor images (see load_image)

  • num_images (int) –

    used to limit how many images are loaded (default = load all images that match the pattern)

Returns:
  • [Surface]

    a list of images

robingame/image/utils.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
def load_image_sequence(
    pattern: Path | str,
    colorkey: Color = None,
    num_images: int = 0,
) -> [Surface]:
    """
    Load a sequence of images.

    Args:
        pattern: glob pattern for the image sequence. E.g. if your folder of image contains
            `"example1.png", "example2.png"`, etc, then your pattern should be `"example*.png"`
        colorkey: used to recolor images (see `load_image`)
        num_images: used to limit how many images are loaded (default = load all images that
            match the pattern)

    Returns:
        a list of images
    """
    pattern = Path(pattern).as_posix()
    files = glob.glob(pattern)
    if not files:
        raise FileNotFoundError(f"Couldn't find any images matching pattern '{pattern}'")
    images = [load_image(file, colorkey) for file in files]
    if num_images:
        images = images[:num_images]
    return images

load_spritesheet(filename, image_size=None, colorkey=None, num_images=0)

Load the image file. Don't call this until pygame.display has been initiated. Split the spritesheet into images and return a list of images.

If image_size is None, load the whole spritesheet as one sprite.

Parameters:
  • filename (Path | str) –

    path to the spritesheet file

  • image_size (int, int) –

    size of the individual frames of the spritesheet (in pixels)

  • colorkey (Color) –

    used to set transparency (see load_image)

  • num_images (int) –

    can be used to limit the number of frames loaded (default = load all)

Returns:
  • [Surface]

    a list of images

robingame/image/utils.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def load_spritesheet(
    filename: Path | str,
    image_size: (int, int) = None,
    colorkey: Color = None,
    num_images: int = 0,
) -> [Surface]:
    """
    Load the image file. Don't call this until pygame.display has been initiated. Split the
    spritesheet into images and return a list of images.

    If image_size is None, load the whole spritesheet as one sprite.

    Args:
        filename: path to the spritesheet file
        image_size: size of the individual frames of the spritesheet (in pixels)
        colorkey: used to set transparency (see `load_image`)
        num_images: can be used to limit the number of frames loaded (default = load all)

    Returns:
        a list of images
    """
    filename = Path(filename)
    if not filename.exists():
        raise FileNotFoundError(f"Couldn't find {filename}")
    sheet = load_image(filename=filename.as_posix(), colorkey=colorkey)

    if image_size:
        width, height = image_size
        num_horizontal = sheet.get_rect().width // width
        num_vertical = sheet.get_rect().height // height
        rects = [
            pygame.Rect((width * i, height * j, width, height))
            for j in range(num_vertical)
            for i in range(num_horizontal)
        ]
        images = [sheet.subsurface(rect) for rect in rects]
        images = list(filter(not_empty, images))
        if num_images:
            images = images[:num_images]
    else:
        images = [sheet]
    return images

not_empty(surface)

Check if a surface has any non-zero pixels. Surface.get_bounding_rect() returns the smallest rectangle on the surface containing data. If the surface is empty, it will return Rect(0, 0, 0, 0), for which any returns False.

robingame/image/utils.py
56
57
58
59
60
61
62
def not_empty(surface: Surface) -> bool:
    """
    Check if a surface has any non-zero pixels. `Surface.get_bounding_rect()` returns the
    smallest rectangle on the surface containing data. If the surface is empty, it will return
    Rect(0, 0, 0, 0), for which `any` returns False.
    """
    return any(surface.get_bounding_rect())

pad_alpha(colour_tuple)

Add the 4th (alpha) channel to a length 3 color tuple. By default it sets the new alpha channel to full opacity (255).

Parameters:
  • colour_tuple (Color | tuple) –
Returns:
  • Color

    a colour

robingame/image/utils.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def pad_alpha(colour_tuple: Color | tuple) -> Color:
    """
    Add the 4th (alpha) channel to a length 3 color tuple.
    By default it sets the new alpha channel to full opacity (255).

    Args:
        colour_tuple:

    Returns:
        a colour
    """
    if len(colour_tuple) == 3:
        # if no alpha channel supplied, assume it's full opacity
        return (*colour_tuple, 255)
    elif len(colour_tuple) == 4:
        return colour_tuple
    else:
        raise Exception("bogus colour, man")

recolor_image(surface, color_mapping)

Return a recolored copy of an image.

Parameters:
  • surface (Surface) –

    input image

  • color_mapping (dict) –

    dictionary of old colors (keys) to new colors (values). Unfortunately they have to be RGB tuples, not pygame Colors, because Color is an unhashable type...

Returns:
  • Surface

    output image

robingame/image/utils.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def recolor_image(surface: Surface, color_mapping: dict) -> Surface:
    """
    Return a recolored copy of an image.

    Args:
        surface: input image
        color_mapping: dictionary of old colors (keys) to new colors (values).
            Unfortunately they have to be RGB tuples, not pygame Colors, because Color is an
            unhashable type...

    Returns:
        output image
    """
    # make sure the colourmap has alpha channel on all colours
    color_mapping = {pad_alpha(k): pad_alpha(v) for k, v in color_mapping.items()}
    width, height = surface.get_size()
    # surface.copy() inherits surface's colorkey; preserving transparency
    new_surface = surface.copy()

    # iterate over all the pixels in the old surface, and write a pixel to the new surface in the
    # corresponding position. If the colour of the present pixel has an entry in the
    # color_mapping dict, then write the new colour instead of the old one.
    for x in range(width):
        for y in range(height):
            color = surface.get_at((x, y))[:]
            new_color = color_mapping.get(color)
            if new_color:
                new_surface.set_at((x, y), pygame.Color(*new_color))
            else:
                new_surface.set_at((x, y), pygame.Color(*color))

    return new_surface

recolor_images(images, colormap)

Apply recolor_image to a list of images.

robingame/image/utils.py
226
227
228
229
230
def recolor_images(images: [Surface], colormap: dict) -> [Surface]:
    """
    Apply `recolor_image` to a list of images.
    """
    return [recolor_image(image, colormap) for image in images]

scale_image(image, scale)

Return a scaled copy of an image.

Parameters:
  • image (Surface) –

    input image

  • scale (float) –

    factor by which to scale image

Returns:
  • Surface

    output image

robingame/image/utils.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def scale_image(image: Surface, scale: float) -> Surface:
    """
    Return a scaled copy of an image.

    Args:
        image: input image
        scale: factor by which to scale image

    Returns:
        output image
    """
    width, height = image.get_rect().size
    image = pygame.transform.scale(image, (width * scale, height * scale))
    return image

scale_images(images, scale)

Apply scale_image to a list of images.

robingame/image/utils.py
163
164
165
166
167
def scale_images(images: [Surface], scale: float) -> [Surface]:
    """
    Apply `scale_image` to a list of images.
    """
    return [scale_image(image, scale) for image in images]