方格精選

Python Pillow 套件-製作GIF動畫

閱讀時間約 20 分鐘

圖像互換格式(Graphics Interchange Format, GIF)是一種媒體格式,可以將多偵的圖像組合成一個連續的動畫。無論是在社交媒體上分享有趣的動畫,還是在網站上展示產品的動態效果,GIF都是一個絕佳的選擇。

上一篇文章說明了Pillow套件的基礎操作,例如圖像的縮放,裁減,旋轉、合併等等,而這篇文章,將介紹如何使用上一篇提到的技巧,製作出流暢的GIF動畫。

Pillow將檔案儲存成GIF格式的函式與上一章介紹的一樣,是使用 Image.save() ,而 save​有多個參數可以控制GIF的屬性,以下是常見的參數:

image.save(fp: str, 
           save_all: bool,
           append_images: list,
           optimize: bool,
           duration: int | list,
           loop: int)
  1. image :GIF第一幀的圖。
  2. save_all:是否保存多幀GIF。
  3. append_images :要追加為GIF的其他圖像串列。
  4. duration:每幀的持續時間,以毫秒為單位。可以是單一整數(對所有幀使用相同的持續時間)或整數串列(每幀的持續時間)。
  5. loop:GIF的播放次數。0表示無限循環,1表示播放一次,2表示播放兩次,依此類推

範例1-使用繪製製作動畫

先來看圖像:

square_animation.gif

square_animation.gif

這是一個200x200的圖像,中心有一個紅色矩形,從100x100,每幀邊長各增長10像素,直到填滿200x200的空間,每幀的持續時間為100ms,並且無限循環。

在說明如何製作之前,先來介紹一個可以生成新圖像的函式:

Image.new(mode: str, size: tuple | str, color)
  1. mode:圖像的顏色模式。常見的模式有:
    • 'RGB': 三通道,紅色、綠色和藍色。
    • 'RGBA': 四通道,紅色、綠色、藍色和透明度/alpha。
    • 'L': 灰度模式。
    • '1': 二值模式,只有黑和白。
  2. size:圖像的尺寸,表示為(width, height)
  3. color:背景顏色。對於'RGB'模式,這將是一個三元組,例如(255, 255, 255)表示白色,也可以使用顏色的英文名,如 'white' 。對於'L'模式,這將是一個介於0(黑色)和255(白色)之間的整數。

例如生成一個100x100,背景顏色為藍色的圖像:

from PIL import Image
img = Image.new('RGB', (100, 100), (0, 0, 255))
img.save(r'blue_square.png')
blue.png

blue.png

介紹完 new()之後,就可以開始介紹如何製作了。

製作過程

首先,我們需要一個200x200、背景為透明的圖像:

from PIL import Image, ImageDraw
img = Image.new('RGBA', (200, 200), (255, 255, 255, 0))

接著,在圖像的中間繪製100x100的紅色矩形:

draw = ImageDraw.Draw(img)
upper_left = (100 - 50, 100 - 50)
lower_right = (100 + 50, 100 + 50)
draw.rectangle([upper_left, lower_right], fill='red')
img.save(r'red_square.png')
red_square.png

red_square.png

接下來,因為每次邊長都會增加十,也就是說,下一張圖像的矩形大小會是110x110,為了維持在圖像中央,矩形左上角的點座標會是(100 - 55, 100 - 500),右下地的點座標則是(100 + 55, 100 + 55),依此類推,下一次就是(100 - 60, 100 - 60)與(100 + 60, 100 + 60),直到(0, 0)與(200, 200)時,紅色矩形填滿整個圖像。我們利用 for 迴圈來繪製每一張圖像,並且將圖像儲存至 images​ 的列表內,程式碼如下:

# 創建一個圖像列表
images = []
for i in range(100, 201, 10):
    # 創建一個200x200的透明背景
    img = Image.new('RGBA', (200, 200), (255, 255, 255, 0))
   
    # 在正中間繪製紅色矩形
    draw = ImageDraw.Draw(img)
    upper_left = (100 - i//2, 100 - i//2)
    lower_right = (100 + i//2, 100 + i//2)
    draw.rectangle([upper_left, lower_right], fill='red')
   
    images.append(img)

接下來,我們只要將圖像利用 save 輸出成GIF就完成了,第一幀圖像是 images列表的第一個元素,因此使用 images[0].save() 來製作GIF,按照上面的格式依序填入參數:

images[0].save(r'square_animation.gif',
                save_all = True,
                append_images = images[1:],
                optimize = False,
                duration = 100,
                loop = 0)

值得注意的是 append_images = images[1:],使用列表分割使得 images[0] 不會出現兩幀。以下是完整的程式碼:

from PIL import Image, ImageDraw

# 創建一個圖像列表
images = []
for i in range(100, 201, 10):
    # 創建一個200x200的透明背景
    img = Image.new('RGBA', (200, 200), (255, 255, 255, 0))
   
    # 在正中間繪製紅色正方形
    draw = ImageDraw.Draw(img)
    upper_left = (100 - i//2, 100 - i//2)
    lower_right = (100 + i//2, 100 + i//2)
    draw.rectangle([upper_left, lower_right], fill='red')
   
    images.append(img)

# 將圖像列表保存為GIF
images[0].save(r'square_animation.gif',
               save_all = True,
               append_images = images[1:],
               optimize = False,
               duration = 100,
               loop = 0)

範例二-圖像旋轉

接下來用上一篇文章提到的 rotate() 製作如下動畫:

rotated_animation,gif

rotated_animation,gif


這是一個100x100的水藍色矩形,以逆時針每次旋轉10度,無限循環,每幀的持續時間為100ms。

首先一樣使用 new() 來製作一個100x100的水藍色圖像:


from PIL import Image
base_img = Image.new('RGB', (100, 100), color = 'aqua')
base_img.save(r'aqua.png')
aqua.png

aqua.png

來看看轉五十度的圖像:

rotated.png

rotated.png

旋轉成功,那我們就可以使用 for 迴圈來快速製作旋轉10度到350度的圖像,由於旋轉0度等於360度, saveloop 參數會自動幫我們回到第一幀圖像,因此我們 for 迴圈只要到 350 就行了。

rotated_images = []
# 旋轉圖像
for angle in range(10, 360, 10):
    rotated_images.append(base_img.rotate(angle))

將圖像列表合併成GIF動畫:

# 將旋轉的圖像保存為GIF
rotated_images[0].save(r'rotated_animation.gif',
                      save_all = True,
                      append_images = rotated_images[1:],
                      optimize = False,
                      duration = 100,
                      loop = 0)

製作旋轉圖像也可以使用Python的生成式,完整程式碼如下:

from PIL import Image
# 創建一個水藍色圖像
base_img = Image.new('RGB', (100, 100), color = 'aqua')

# 旋轉圖像
rotated_images = [base_img.rotate(angle) for angle in range(0, 360, 10)]

# 將旋轉的圖像保存為GIF
rotated_images[0].save(r'rotated_animation.gif',
                      save_all = True,
                      append_images = rotated_images[1:],
                      optimize = False,
                      duration = 100,
                      loop = 0)

範例三-圖像裁剪

現在有一張風景圖:

landscape.png

landscape.png

我們希望能擁有以下效果:

sliding_window.gif

sliding_window.gif

這張gif的大小為640x300,每幀擷取的部份的會往下5像素,到最底後會回彈,並以每幀往上5巷素的方式移動,直到回到圖像的最頂端,無限循環,每幀的持續時間為50ms。

一開始,我們先讀入 landscape.png,並顯示其大小:

from PIL import Image
img = Image.open(r'landscape.png')
width, height = img.size
print(f'大小:{width}x{height}')
大小:640x640

先擷取(0, 0)到(640, 300)的範圍:

cropped = img.crop((0, 0, 640, 300))
cropped.save(r'cropped.png')
cropped.png

cropped.png

接著只要一路以每幀5像素的速度向下裁剪,直到碰到底部,也就是裁剪到(0, 340, 640, 640):

for step in range(0, height - 300 + 1, 5):
    box = (0, 0 + step, width, 300 + step)
    cropped = img.crop(box)
    images.append(cropped)

由於碰到底部要回彈,也就是將剛剛的過程反著再來一遍,我們可以直接複製 images內倒數第二個元素到第二個的元素,也就是去掉頭尾,然後連接到原本 images的後面,程式碼如下:

images += images[-2:0:-1]

接著將圖像列表轉為GIF並儲存:

images[0].save(r'example3\sliding_window.gif',
                save_all = True,
                append_images = images[1:],
                optimize = False,
                duration = 50,
                loop = 0)

完整程式碼如下:

from PIL import Image

img = Image.open(r'landscape.png')
images = []
width, height = img.size

for step in range(0, height - 300 + 1, 5):
    box = (0, 0 + step, width, 300 + step)
    cropped = img.crop(box)
    images.append(cropped)

images += images[-2:0:-1]

images[0].save(r'sliding_window.gif',
                save_all = True,
                append_images = images[1:],
                optimize = False,
                duration = 50,
                loop = 0)

範例四-圖像合併

有三張風景圖:

0.png

0.png

1.png

1.png

2.png

2.png

我們可以直接使用 blend()來達成如下效果:

blend.gif

blend.gif

利用Python的生成式將圖像依序讀入並存於 images列表中 :

from PIL import Image

images = [Image.open(f'{img}.png') for img in range(3)]

images列表存為GIF:

images[0].save(r'blend.gif', 
               save_all = True,
               append_images = images[1:],
               optimize = False,
               duration = 500,
               loop = 0)

但這樣轉場過於單調,我們可以透過調整 alpha參數來製作淡出的效果,淡出的過程每幀持續時間100ms,每次淡出圖像的透明度增加10%,圖像每幀持續時間300​ms:

fade_out.gif

fade_out.gif

一樣先讀入圖像,不過為了等等製作淡出動畫方便, base_images的最後我們再增加一張開頭的圖像:

from PIL import Image

base_images = [Image.open(f'{img}.png') for img in range(3)]
base_images.append(base_images[0])
images = []

先將目前顯示的圖像加入到 images中,接著開始製作淡出動畫,淡出圖像的透明度每幀增加10%:

for i in range(len(base_images) - 1):
    # 將目前圖像加入至images
    images.append(base_images[i])

    # 每幀淡出圖像的透明度增加10%
    for alpha in range(10):
        blend = Image.blend(base_images[i], base_images[i + 1], alpha = alpha / 10)
        images.append(blend)

淡出過程有10幀圖像,這個過程每幀100ms,而顯示完整圖像時每幀300ms,完整圖像分別位於 images[0]images[11]images[22],也就是說,當索引值是11的倍數時,持續時間須為300ms,剩下的每幀持續時間為100ms,我們利用以下程式碼製作 durations陣列:

for i in range(len(images)):
    if i % 11:
        durtions.append(100)
   
    else:
        durtions.append(300)

最後將圖像列表儲存為GIF就成功啦:

images[0].save(r'example4\fade_out.gif', 
               save_all = True,
               append_images = images[1:],
               optimize = False,
               duration = durtions,
               loop = 0)

完整程式碼如下:

from PIL import Image

base_images = [Image.open(f'example4\\{img}.png') for img in range(3)]
base_images.append(base_images[0])
images = []

for i in range(len(base_images) - 1):
    # 將目前圖像加入至images
    images.append(base_images[i])

    # 每幀淡出圖像的透明度增加10%
    for alpha in range(10):
        blend = Image.blend(base_images[i], base_images[i + 1], alpha = alpha / 10)
        images.append(blend)

durtions = []

for i in range(len(images)):
    if i % 11:
        durtions.append(100)
   
    else:
        durtions.append(300)

images[0].save(r'example4\fade_out.gif',
               save_all = True,
               append_images = images[1:],
               optimize = False,
               duration = durtions,
               loop = 0)

以上就是一些利用Pillow套件製作gif的範例,範例程式碼與圖片可以在此查看與下載:YukiHataRin/vocus_gif (github.com)。感謝大家的閱讀!

13會員
4內容數
留言0
查看全部
發表第一個留言支持創作者!
HataRin的沙龍 的其他內容
在當今的數位時代,圖像處理已成為許多應用和項目的核心部分,從網站設計到機器學習,高效且靈活的圖像處理工具變得越來越重要。本文將介紹一個好用的Python套件-Pillow,Pillow套件是Python Imaging Library(PIL)的一個分支雖,然它不像Photoshop等軟體一樣強大,
在當今的數位時代,圖像處理已成為許多應用和項目的核心部分,從網站設計到機器學習,高效且靈活的圖像處理工具變得越來越重要。本文將介紹一個好用的Python套件-Pillow,Pillow套件是Python Imaging Library(PIL)的一個分支雖,然它不像Photoshop等軟體一樣強大,
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
實務工作上,我們很可能會遇到需要對每張工作表的某一固定位置儲存格求和的情況。比如說,E5儲存格存放每個月的業績總額,以下的程式碼可以求得一整年的總營業額。 以下的程式碼則是用更具Python風格、更精簡且更漂亮的列表推導式(List Comprehension)來輕鬆達成同樣的功能。
Thumbnail
我們利用工作表的move_range()方法,可以輕鬆地移動儲存格。其中,rows參數的值表示向下移幾列,cols參數的值表示向右移幾行;當rows參數的值是負數時,則表示向上移幾列;cols參數的值負數時,則表示向上移幾行, 也可以一次移動一整個區塊範圍的儲存格。 利用工作表的freeze_pan
Thumbnail
我們可以利用工作表的append()方法,在工作表的列尾添加資料列。 利用迴圈的技巧,我們可以批次賦予區塊內所有儲存格相同的值。 我們也可以在指定的列(行)之前插入指定數量的空白列(行),從指定的列(行)開始向下(右)刪除指定數量的列(行)。
Thumbnail
其餘讀取整列、整行和所有儲存格等的方法,詳見以下程式碼的註解說明。
Thumbnail
區塊儲存格 假設我們有一張工作表如下,而我們打算讀取C3:D6區塊儲存格的值。 以下的程式碼可以列印出C3:D6區塊儲存格的資料,注意openpyxl預設是採以列為主(row-major)拜訪儲存格。 如果需要以行為主(column-major)的方式印出區塊儲存格的值,可以改用以下的程式碼。 列印
Thumbnail
儲存格的存取和變數的存取一樣直覺與簡單,但是要注意儲存格的值和儲存格物件是兩個不同的東東;其中,儲存格的值是儲存格物件的一個屬性(value)。工作表ws中儲存格A4可以用ws['A4']這樣的方式表示,也可以用ws.cell(row = 4, column = 2)表示,或簡單地用ws.cell(
Thumbnail
學會了工作簿和工作表基本的操作後,讓我們來做一些練習。 1. 新建一個工作簿,並在其內新增12張工作表;將工作表的名稱分別定為1月、2月、3月……12月。 解答: 2. 續上題,批次將工作表的名稱更改為竹科1月、竹科2月、竹科3月……竹科12月。 解答:
Thumbnail
一、命名工作表 二、新增工作表 你可以使用create_sheet()方法來新增一張工作表,並命名新的工作表名稱。 三、複製工作表 你可以使用copy_worksheet()方法來複製一張工作表。 四、列印所有工作表名稱 五、刪除工作表 你可以使用remove()方法來刪除工作表"物件"。 注意:
Thumbnail
一、套件安裝 在使用Python操控Excel前,需要先安裝openpyxl模組。 二、建立工作簿 不需要先建立Excel檔案,就可以開始使用openpyxl;只要引用Workbook類別,就可以開始工作。 一個工作簿被建立起來的時候,至少會含有一張工作表。你可以用active屬性來使用這張工作表。
Thumbnail
還記得之前介紹我的LINE Bot中第一個功能嗎?沒錯,就是部落格的選單,這選單看起來是不是比較華麗一點,但其實製作上相當簡單就可以完成,要如何製作就是我們今天要教的「客製化選單」
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
實務工作上,我們很可能會遇到需要對每張工作表的某一固定位置儲存格求和的情況。比如說,E5儲存格存放每個月的業績總額,以下的程式碼可以求得一整年的總營業額。 以下的程式碼則是用更具Python風格、更精簡且更漂亮的列表推導式(List Comprehension)來輕鬆達成同樣的功能。
Thumbnail
我們利用工作表的move_range()方法,可以輕鬆地移動儲存格。其中,rows參數的值表示向下移幾列,cols參數的值表示向右移幾行;當rows參數的值是負數時,則表示向上移幾列;cols參數的值負數時,則表示向上移幾行, 也可以一次移動一整個區塊範圍的儲存格。 利用工作表的freeze_pan
Thumbnail
我們可以利用工作表的append()方法,在工作表的列尾添加資料列。 利用迴圈的技巧,我們可以批次賦予區塊內所有儲存格相同的值。 我們也可以在指定的列(行)之前插入指定數量的空白列(行),從指定的列(行)開始向下(右)刪除指定數量的列(行)。
Thumbnail
其餘讀取整列、整行和所有儲存格等的方法,詳見以下程式碼的註解說明。
Thumbnail
區塊儲存格 假設我們有一張工作表如下,而我們打算讀取C3:D6區塊儲存格的值。 以下的程式碼可以列印出C3:D6區塊儲存格的資料,注意openpyxl預設是採以列為主(row-major)拜訪儲存格。 如果需要以行為主(column-major)的方式印出區塊儲存格的值,可以改用以下的程式碼。 列印
Thumbnail
儲存格的存取和變數的存取一樣直覺與簡單,但是要注意儲存格的值和儲存格物件是兩個不同的東東;其中,儲存格的值是儲存格物件的一個屬性(value)。工作表ws中儲存格A4可以用ws['A4']這樣的方式表示,也可以用ws.cell(row = 4, column = 2)表示,或簡單地用ws.cell(
Thumbnail
學會了工作簿和工作表基本的操作後,讓我們來做一些練習。 1. 新建一個工作簿,並在其內新增12張工作表;將工作表的名稱分別定為1月、2月、3月……12月。 解答: 2. 續上題,批次將工作表的名稱更改為竹科1月、竹科2月、竹科3月……竹科12月。 解答:
Thumbnail
一、命名工作表 二、新增工作表 你可以使用create_sheet()方法來新增一張工作表,並命名新的工作表名稱。 三、複製工作表 你可以使用copy_worksheet()方法來複製一張工作表。 四、列印所有工作表名稱 五、刪除工作表 你可以使用remove()方法來刪除工作表"物件"。 注意:
Thumbnail
一、套件安裝 在使用Python操控Excel前,需要先安裝openpyxl模組。 二、建立工作簿 不需要先建立Excel檔案,就可以開始使用openpyxl;只要引用Workbook類別,就可以開始工作。 一個工作簿被建立起來的時候,至少會含有一張工作表。你可以用active屬性來使用這張工作表。
Thumbnail
還記得之前介紹我的LINE Bot中第一個功能嗎?沒錯,就是部落格的選單,這選單看起來是不是比較華麗一點,但其實製作上相當簡單就可以完成,要如何製作就是我們今天要教的「客製化選單」