Как работать с изображениями. Модули image и transform

Ссылка на проект занятия (lesson 8.car_move.zip): https://github.com/selfedu-rus/pygame

На этом занятии мы с вами узнаем как использовать изображения в библиотеке Pygame. Здесь сразу следует знать одну вещь: родной формат графических данных для Pygame – это

bmp (от англ. Bitmap Picture)

то есть, не сжатые наборы пикселей изображения. Такие графические файлы занимают много места по сравнению с другими распространенными форматами:

  • PNG (расширение png) – используется сжатие без потерь с использованием алгоритмов ДИКМ и LZW;
  • JPEG (расширение jpg) – используется сжатие с потерями (алгоритм ДКП – аналог Фурье-преобразования с косинусными гармониками).

Существуют и другие форматы представления изображений, но они применяются гораздо реже, чем PNG или JPEG.

Как же нам понять: какой формат данных выбирать? В действительности, все просто. Во-первых, нужно узнать: поддерживает ли Pygame на текущем устройстве какие-либо форматы, кроме BMP. Для этого следует выполнить функцию:

pygame.image.get_extended()

и если она вернет значение True (то есть 1), то в программе можно использовать PNG, JPEG, GIF и другие известные форматы. А, иначе, только BMP. Далее, при выборе между PNG и JPEG руководствуются следующим правилом:

Для фотореалистичных изображений лучше всего использовать JPEG, т.к. незначительные потери практически не скажутся на визуальном восприятии, но изображение будет хорошо сжато.

Для искусственных изображений с большим наличием однотонных областей (например, клип-арт) где четкость границ и однотонность заливки имеет первостепенное значение, лучше выбирать формат PNG. Кроме того, этот формат хранит альфа-канал для прозрачного фона (в JPEG такой возможности нет).

Программа ниже инициализирует Pygame и выводит в консоль значение функции get_extended():

import pygame
pygame.init()
 
W, H = 600, 400
 
sc = pygame.display.set_mode((600, 400))
pygame.display.set_caption("Изображения")
pygame.display.set_icon(pygame.image.load("app.bmp"))
 
clock = pygame.time.Clock()
FPS = 60
 
WHITE = (255, 255, 255)
RED = (255, 0, 0)
YELLOW = (239, 228, 176)
 
print( pygame.image.get_extended() )
 
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
 
    clock.tick(FPS)

Как видите, в данном случае Pygame возвращает 1, значит, можно использовать форматы PNG и JPEG.

Теперь загрузим в программе изображение машинки:

car_surf = pygame.image.load("images/car.bmp")

Здесь используется функция load(), в которой указывается путь к изображению (относительно исполняемого файла на Питоне) и на выходе она формирует поверхность с изображением машинки. Далее, мы можем отобразить содержимое этой поверхности уже знакомым нам способом:

car_rect = car_surf.get_rect(center=(W//2, H//2))
 
sc.blit(car_surf, car_rect)
pygame.display.update()

После выполнения программы увидим в центре окна изображение машины:

Выглядит не очень. Добавим фон в виде изображения песка:

bg_surf = pygame.image.load("images/sand.jpg")
sc.blit(bg_surf, (0, 0))

Стало лучше, но белый фон у машинки явно выделяется на фоне песка. Давайте укажем Pygame, что белый цвет следует воспринимать как прозрачный:

car_surf.set_colorkey((255, 255, 255))

Теперь намного лучше. Однако, если имеется файл с альфа-каналом (прозрачным фоном), то оно будет сразу отображаться нужным образом:

finish_surf = pygame.image.load("images/finish.png")
sc.blit(finish_surf, (0, 0))

Следующий важный момент, особенно при разработке динамических игр, перевод пикселей загруженных изображений в формат пикселей главной поверхности:

car_surf = pygame.image.load("images/car.bmp").convert()
finish_surf = pygame.image.load("images/finish.png").convert_alpha()
bg_surf = pygame.image.load("images/sand.jpg").convert()

В этом случае, перерисовка поверхностей будет выполняться быстрее. Причем, обратите внимание, изображение в формате PNG с альфа-каналом преобразуется методом convert_alpha(), а не convert().

Вообще, эти строчки равносильны следующей операции:

car_surf = pygame.image.load("images/car.bmp")
car_surf = car_surf.convert()

То есть, методы convert() и convert_alpha() – это методы класса Surface, которые возвращают новую поверхность с измененным представлением пикселей. При этом прежняя поверхность остается без изменений. Например, если переписать последнюю строчку вот так:

car_surf2 = car_surf.convert()

то пиксели car_surf2 будут приведены к формату главной поверхности, а пиксели car_surf останутся прежними – без изменений.

Трансформация поверхностей

Предположим, что мы теперь хотели бы уменьшить масштаб изображения фона, чтобы песок был более мелкий. Это можно сделать с помощью модуля

pygame.transform

содержащий различные функции трансформации поверхностей. Подробное их описание можно посмотреть на странице официальной документации:

https://www.pygame.org/docs/ref/transform.html

Итак, мы воспользуемся функцией:

pygame.transform.scale(Surface, (width, height), DestSurface = None) -> Surface

Здесь первый параметр – преобразуемая поверхность; (width, height) – ее новые значения ширины и высоты. В нашей программе вызовем эту функцию так:

bg_surf = pygame.transform.scale(bg_surf, (bg_surf.get_width()//3, bg_surf.get_height()//3))

Мы здесь уменьшаем размеры bg_surf в три раза по обеим координатам. Теперь, при отображении песок выглядит более мелким.

Давайте теперь сделаем так, чтобы машинка перемещалась при нажатии на курсорные клавиши. Для начала мы сформируем изображения машинки при движении вверх, влево, вправо и вниз:

car_up = car_surf
car_down = pygame.transform.flip(car_surf, 0, 1)
car_left = pygame.transform.rotate(car_surf, 90)
car_right = pygame.transform.rotate(car_surf, -90)

Далее, определим переменные для хранения текущего вида машинки и ее скорости:

car = car_up
speed = 5

А внутри главного цикла будем выполнять перерисовку главного окна с текущим видом и положением машинки:

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit()
 
    bt = pygame.key.get_pressed()
    if bt[pygame.K_LEFT]:
        car = car_left
        car_rect.x -= speed
        if car_rect.x < 0:
            car_rect.x = 0
    elif bt[pygame.K_RIGHT]:
        car = car_right
        car_rect.x += speed
        if car_rect.x > W-car_rect.height:
            car_rect.x = W-car_rect.height
    elif bt[pygame.K_UP]:
        car = car_up
        car_rect.y -= speed
        if car_rect.y < 0:
            car_rect.y = 0
    elif bt[pygame.K_DOWN]:
        car = car_down
        car_rect.y += speed
        if car_rect.y > H-car_rect.height:
            car_rect.y = H-car_rect.height
 
    sc.blit(bg_surf, (0, 0))
    sc.blit(finish_surf, (0, 0))
    sc.blit(car, car_rect)
 
    pygame.display.update()
 
    clock.tick(FPS)

Смотрите, мы здесь проверяем: нажата ли какая-либо курсорная клавиша, и если срабатывает одно из условий, то переменная car ссылается на нужное изображение машинки и происходит изменение ее текущих координат на величину скорости. Далее, выполняется перерисовка главного окна и мы видим машинку с новым положением. Так происходит ее перемещение в пределах клиентского окна приложения.

Фактически мы с вами рассмотрели основы работы с изображениями в Pygame с помощью двух модулей:

  • pygame.image – загрузка/сохранение изображений;
  • pygame.transform – трансформация поверхностей.

Эти модули содержат множество полезных функций, с которыми подробнее можно ознакомиться на страницах документации:

https://www.pygame.org/docs/ref/transform.html

https://www.pygame.org/docs/ref/image.html

Использование функций этих модулей достаточно просто и очевидно, поэтому нет смысла подробно о них говорить. Все делается по аналогии с рассмотренными на этом занятии.