Библиотека Pillow
содержит некоторую базовую поддержку последовательностей изображений (также называемых форматами анимации). Поддерживаемые форматы последовательностей включают FLI
/FLC
, GIF
и несколько экспериментальных форматов. Файлы TIFF
также могут содержать более одного кадра.
Когда открывается файл, содержащий последовательность изображений, то Pillow
автоматически загружает первый кадр в последовательности. Для перемещения между разными кадрами можно использовать методы Image.seek()
и Image.tell()
:
from PIL import Image with Image.open('animation.gif') as img: # перейти ко второму кадру img.seek(1) try: while 1: img.seek(img.tell() + 1) # здесь делаем что-нибудь # с текущем кадром `img` except EOFError: pass # конец последовательности
Из примера видно, что когда последовательность кадров завершится, получаем исключение EOFError
.
Следующий класс позволяет использовать оператор for/in
для циклического перебора последовательности:
from PIL import ImageSequence with Image.open('animation.gif') as img: for frame in ImageSequence.Iterator(img): # здесь делаем что-нибудь # с текущем кадром `frame` # `frame` - объект изображения
Pillow читает версии формата файла GIF
: GIF87a и GIF89a, а записывает файлы в формате GIF87a по умолчанию, если не используются функции GIF89a. Файлы записываются в кодировке LZW.
Файлы GIF изначально читаются как изображения в оттенках серого (L
) или в режиме палитры (P
). Поиск более поздних кадров в изображении P
изменит изображение на RGB
(или RGBA
, если первый кадр был прозрачным).
Изображения в режиме P
заменяются на RGB
, потому что каждый кадр GIF может содержать свою собственную индивидуальную палитру до 256 цветов. Когда новый кадр помещается на предыдущий кадр, эти цвета могут объединяться, и могут превысить ограничение режима P
в 256 цветов, по этому изображение преобразуется в RGB.
Если необходимо, чтобы первый кадр изображения в режиме P
также был RGB
, то нужно применить настройку:
from PIL import GifImagePlugin GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
Чтобы восстановить поведение по умолчанию, когда изображения в режиме P
преобразуются в RGB или RGBA только после первого кадра:
from PIL import GifImagePlugin GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
Кадры GIF не всегда содержат отдельные палитры. Если есть только глобальная палитра, то все цвета могут поместиться в режиме P
. Если нужно, чтобы в этом случае кадры сохранялись как P
, также доступна настройка:
from PIL import GifImagePlugin GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
Функция PIL.Image.open()
может иметь следующие свойства GIF формата, содержащихся в словаре img.info
:
background
: Цвет фона по умолчанию (цветовой индекс палитры).transparency
: Индекс цвета прозрачности. Этот ключ опускается, если изображение непрозрачно.version
: Версия (либо GIF87a, либо GIF89a).duration
: Может отсутствовать. Время отображения текущего кадра GIF-файла в миллисекундах.loop
: Может отсутствовать. Сколько раз GIF должен зацикливаться. 0 означает, что цикл будет бесконечным.comment
: Может отсутствовать. Комментарий к изображению.extension
: Может отсутствовать. Содержит информацию о конкретном приложении.При вызове метода Image.save()
для записи GIF-файла доступны следующие дополнительные аргумента:
img.save(out, save_all=True, append_images=[im1, im2, ...])
save_all
: Если True
, то все кадры изображения будут сохранены. Если отсутствует или False
, то будет сохранен только первый кадр изображения.append_images
: Список изображений для добавления в качестве дополнительных кадров. Каждое из изображений в списке может быть одиночным или многокадровым. В настоящее время аргумент поддерживается для форматов GIF
, PDF
, PNG
, TIFF
и WebP
.
Он также поддерживается для ICO
и ICNS
. Если передаются изображения соответствующих размеров, то они будут использоваться вместо уменьшения основного изображения.
include_color_table
: добавлять или не добавлять локальную таблицу цветов.
interlace
: Является ли изображение чересстрочным. По умолчанию это так, если размер изображения меньше 16 пикселей по ширине или высоте.
disposal
: Указывает способ обработки графического изображения после его отображения.
Способ обработки 2 использует предыдущий кадр в качестве фона и изменяет только непрозрачные пиксели в текущем кадре. Метод 1 скопирует все изображение поверх другого, оставив прозрачность нетронутой.
Как использовать? Можно передать одно целое число (единый способ обработки для всех кадров) или последовательность, которая будет означать способ обработки для каждого отдельного кадра изображения.
palette
: Использовать указанную палитру для сохраненного изображения. Палитра должна быть объектом bytes
или bytearray
, содержащим элементы палитры в формате RGBRGB….
Она должна быть не более 768 байт. В качестве альтернативы палитру можно передать как объект PIL.ImagePalette.ImagePalette
.
optimize
: Если True
, то Pillow попытайтесь сжать палитру, удалив неиспользуемые цвета. Это полезно только в том случае, если палитру можно сжать до следующей меньшей степени из 2 элементов.
Обратите внимание, что если изображение, которое сохраняется, взято из существующего
GIF
, то оно может иметь перечисленные ниже свойства в своем словаре. Большинство методов объектаImage
игнорируют словарь при возврате новых изображений. Если эта свойства понадобится позже (при записи измененногоGIF
), то необходимо сохранить ссылку на словарьImage.info
, возвращаемый функциейPIL.Image.open()
.
transparency
: Индекс цвета прозрачности.
duration
: Может отсутствовать. Время отображения текущего кадра GIF-файла в миллисекундах.
loop
: Может отсутствовать. Сколько раз GIF должен зацикливаться. 0 означает, что цикл будет бесконечным.
comment
: Может отсутствовать. Комментарий к изображению.
from PIL import Image, ImageDraw # параметры анимации frame = 10 width = 200 center = width // 2 max_radius = int(center * 1.5) fill = '#fbceb1' color = '#b3b1fb' frames = [] for i in range(0, max_radius, frame): # создаем кадры/фреймы для GIF-изображения img = Image.new('P', (width, width), fill) draw = ImageDraw.Draw(img) draw.ellipse((center - i, center - i, center + i, center + i), fill=color) frames.append(img) # сохраняем GIF-изображение frames[0].save('circle.gif', save_all=True, loop=0, append_images=frames[1:], optimize=False, duration=3)
from PIL import (Image, ImageOps, ImageSequence) # список для обработанных фреймов frames = [] # откроем файл который создали ранее with Image.open('circle.gif') as img: for frame in ImageSequence.Iterator(img): # обрабатываем каждый кадр # например инвертируем цвета frame = ImageOps.invert(frame.convert('RGB')) # и уменьшим в 2 раза size = (frame.size[0]//2, frame.size[1]//2) frame = frame.resize(size) # добавляем обраборанный фрейм в список frames.append(frame) # сохраняем обработанное GIF-изображение frames[0].save('circle-proctss.gif', save_all=True, loop=0, append_images=frames[1:], optimize=False, duration=3)
with Image.open(...) as img: if img.tile[0][0] == 'gif': # читаем только первое "локальное изображение" # из этого GIF-файла box = img.tile[0][1] img = img.crop(box)