やりたいこと
- Pythonを使って、pngをgifに変換したい
- 透過に対応したい
- apng(アニメーションpng)ならgifアニメーションに変換したい
環境
- Windows11 64bit
- Python 3.10.5
Pythonのインストールは済んでいる状態とします。
pngとgifの違い
どちらも画像ファイルの拡張子です。
違いについて軽く確認しておきます。
-
gif
- 8ビットカラー(256色)
- 特定のピクセルを透過指定し、背景画像が透けて見えるようにできる
- 半透明は表現できない
-
png
- 3種類のタイプが存在する
- PNG-8:8ビットカラー(256色)
- PNG-24:24ビットカラー(約16,77万色)
- PNG-32:24ビットカラー+8ビットのアルファチャンネル
- 8ビットのアルファチャンネルによって半透明にできる
- 3種類のタイプが存在する
pngからgifへの変換
PythonのPillowというライブラリを使用します。
Pillowとは、画像ファイルの読み込み・操作・保存の機能を提供するフリーのライブラリです。
Python Imaging Library(略称PIL)の後継であり、PILは開発が停止していますが、Pillowは2022年8月現在も開発が進められています。
Pillowリファレンス:Pillow (PIL Fork) 9.2.0 documentation
PILのwiki:Python Imaging Library - Wikipedia
もしPillowをインストールしていない場合は、コマンドプロンプト上で以下のコマンドを実行し、インストールしておいてください。
pip install pillow
静止画pngの変換
静止画の透過png画像を、透過gif画像に変換するサンプルコードです。
# パッケージをインポートする
from PIL import Image
# pngを取得する
img_png = Image.open(r'C:\test\filename.png')
# RGBAに変換する
img_png = img_png.convert('RGBA')
# RGBAのalpha(透過)を取得する
alpha = img_png.getchannel('A')
# alphaのマスクを取得する
mask = Image.eval(alpha, lambda a: 255 if a <=128 else 0)
# gifへ変換するために減色する
img_gif = img_png.quantize(colors=256)
# gifにマスクを貼り付ける
img_gif.paste(im=255, mask=mask)
# 透過gifをエクスポートする
img_gif.save(r'C:\test\filename.gif', transparency=255)
処理内容
処理内容について説明します。
-
パッケージをインポートする
python# パッケージをインポートする from PIL import Image
PillowパッケージからImageモジュールをインポートします。
インポートするパッケージの名前はPillow
ではなくPIL
なので注意です。
Imageモジュールはファイルから画像を読み込んだり新しい画像を作成できるモジュールです。 -
pngを取得する
python# pngを取得する img_png = Image.open(r'C:\test\filename.png')
指定したパスのファイルを取得します。文字列の前に「r」をつけることによって、「\」のエスケープシーケンスが行われず、そのまま文字列として扱われるようになります。
-
RGBAに変換する
python# RGBAに変換する img_png = img_png.convert('RGBA')
img_png.convert('RGBA')
で、画像をRGBAに変換します。RGBAとは、透過マスク付きの色を表す形式です。R:赤(Red)
G:緑(Green)
B:青(Blue)
A:透明度(Alpha)上記の4つの組み合わせで色を表現します。
-
RGBAのalpha(透過)を取得する
python# RGBAのalpha(透過)を取得する alpha = img_png.getchannel('A')
.getchannel('A')
で、アルファチャンネルの画像を取得し、変数alpha
に代入します。 -
alphaのマスクを取得する
python# alphaのマスクを取得する mask = Image.eval(alpha, lambda a: 255 if a <=128 else 0)
8ビット(256色)のアルファチャンネルから、128以下をすべて255にそれ以外を0に変換し、変数
mask
に代入します。 -
gifへ変換するために減色する
python# gifへ変換するために減色する img_gif = img_png.quantize(colors=256)
png画像を256色に変換します。
-
gifにマスクを貼り付ける
python# gifにマスクを貼り付ける img_gif.paste(im=255, mask=mask)
256色に変換した画像の255ピクセルにマスクを貼り付けます。
-
透過gifをエクスポートする
python# 透過gifをエクスポートする img_gif.save(r'C:\test\filename.gif', transparency=255)
指定したパスにgifをエクスポートします。
transparency
は、255ピクセルを透過色に指定するという意味です。
アニメーションpngの変換
透過apng(アニメーションpng)を、透過gifアニメーションに変換するサンプルコードです。
# パッケージをインポートする
from PIL import Image, ImageSequence
# pngを取得する
img_png = Image.open(r'C:\test\filename.png')
# gifアニメーション格納用変数を定義する
imgs_gif = []
# フレームの数だけループする
for frame in ImageSequence.Iterator(img_png):
# RGBAに変換する
frame = frame.convert('RGBA')
# RGBAのalpha(透過)を取得する
alpha = frame.getchannel('A')
# alphaのマスクを取得する
mask = Image.eval(alpha, lambda a: 255 if a <=128 else 0)
# gifへ変換するために減色する
img_gif = frame.quantize(colors=256)
# gifにマスクを貼り付ける
img_gif.paste(im=255, mask=mask)
# 透過後のデータを格納する(参照渡しではなく値渡し)
imgs_gif.append(img_gif.copy())
# apngの再生回数の情報を取得する
loop = img_png.info['loop']
# 透過gifをエクスポートする
imgs_gif[0].save(r'C:\test\filename.gif', save_all=True, append_images=imgs_gif[1:], loop=loop, optimize=False, transparency=255, disposal=2)
処理内容
処理内容について説明します。
静止画pngの変換で説明した内容については省きます。
-
パッケージをインポートする
python# パッケージをインポートする from PIL import Image, ImageSequence
PillowパッケージからImageとImageSequenceモジュールをインポートします。
ImageSequenceモジュールはアニメーションのフレームを処理できるモジュールです。 -
gifアニメーション格納用変数を定義する
Python# gifアニメーション格納用変数を定義する imgs_gif = []
アニメーションから1枚1枚の画像をリストで格納するための変数を定義します。
-
フレームの数だけループする
Python# フレームの数だけループする for frame in ImageSequence.Iterator(img_png):
ImageSequence.Iterator(img_png)
で、アニメーションのフレームごとの画像をリストで取得します。
取得したフレームごとの画像を1枚ずつ変換するために、ループします。 -
透過後のデータを格納する(参照渡しではなく値渡し)
Python# 透過後のデータを格納する(参照渡しではなく値渡し) imgs_gif.append(img_gif.copy())
imgs_gif.append()
で、透過gifに変換した画像ファイルをリストの末尾に追加します。
リストへは値渡しします。.copy()
をつけないと参照渡しになります。
参照渡しは変数の値そのものではなく、変数のメモリ番地を渡します。ループで同じ変数を使いまわすため、次のループで値が書き換わった場合、すでに代入済みの変数の値も変化してしまうので、それを防ぐために値渡しで代入する必要があります。 -
apngの情報を取得する
Python# apngの再生回数の情報を取得する loop = img_png.info['loop']
アニメーションpngのループ回数を取得します。loop=0は無限ループです。
-
透過gifをエクスポートする
Python# 透過gifをエクスポートする imgs_gif[0].save(r'C:\test\filename.gif', save_all=True, append_images=imgs_gif[1:], loop=loop, optimize=False, transparency=255, disposal=2)
指定したパスにgifをエクスポートします。
それぞれの引数の内容は以下です。save_all
:Trueの場合、画像のすべてのフレームを保存します。Falseの場合、最初のフレームのみ保存します。
append_images
:追加のフレームとして追加する画像のリストを指定します。
loop
:gifアニメーションをループする回数を指定します。
optimize
:Trueの場合、未使用の色を削除してパレットの圧縮を試みます。
transparency
:透過色を適用するピクセルを指定します。
disposal
:フレームの処理方法を指定します。2を指定することで、フレームごとに背景色へ戻してから描画します。disposalを指定しないと、前フレームの画像が残り続けます。
実行ファイルにドロップしたpngの変換
コマンドライン引数に渡したpngファイルを、gifファイルに変換するサンプルコードです。
サンプルコードをテキストファイルに貼り付け、拡張子を「.py」にして保存すれば使用できます。
おおまかな処理内容は以下です。
- 「.py」にpngをドラッグ&ドロップするとpngファイルと同じディレクトリにgifファイルをエクスポートする
- 静止画とアニメーションの両方対応可能
# パッケージをインポートする
import sys, os
from tkinter import messagebox
from PIL import Image, ImageSequence
# 受け取ったコマンドライン引数の数だけループする
for fp in sys.argv[1:]:
# パス + 拡張子に分解して取得する
root, ext = os.path.splitext(fp)
# もし拡張子がpng以外なら処理を終えて次のループへ
if ext != '.png':
messagebox.showinfo('警告', '処理できません。対応する拡張子は png のみです。')
continue
# pngを取得する
img_png = Image.open(fp)
# pngがアニメかどうかで分岐する
if img_png.is_animated:
######## pngがアニメのとき ########
# gifアニメーション格納用変数を定義する
imgs_gif = []
# フレームの数だけループする
for frame in ImageSequence.Iterator(img_png):
# RGBAに変換する
frame = frame.convert('RGBA')
# RGBAのalpha(透過)を取得する
alpha = frame.getchannel('A')
# alphaのマスクを取得する
mask = Image.eval(alpha, lambda a: 255 if a <=128 else 0)
# gifへ変換するために減色する
img_gif = frame.quantize(colors=256)
# gifにマスクを貼り付ける
img_gif.paste(im=255, mask=mask)
# 透過後のデータを格納する(参照渡しではなく値渡し)
imgs_gif.append(img_gif.copy())
# apngの再生回数の情報を取得する
loop = img_png.info['loop']
# 透過gifをエクスポートする
imgs_gif[0].save(root + '.gif', save_all=True, append_images=imgs_gif[1:], loop=loop, optimize=False, transparency=255, disposal=2)
else:
######## pngが静止画の時 ########
# RGBAに変換する
img_png = img_png.convert('RGBA')
# RGBAのalpha(透過)を取得する
alpha = img_png.getchannel('A')
# alphaのマスクを取得する
mask = Image.eval(alpha, lambda a: 255 if a <=128 else 0)
# gifへ変換するために減色する
img_gif = img_png.quantize(colors=256)
# gifにマスクを貼り付ける
img_gif.paste(im=255, mask=mask)
# 透過gifをエクスポートする
img_gif.save(root + '.gif', transparency=255)
# 処理終了のメッセージを出す
messagebox.showinfo('メッセージ', 'gifをエクスポートしました。')
処理内容
処理内容について説明します。
静止画pngの変換とアニメーションpngの変換で説明した内容については省きます。
-
パッケージをインポートする
python# パッケージをインポートする import sys, os from tkinter import messagebox from PIL import Image, ImageSequence
パッケージをインポートします。それぞれのパッケージの特性は以下です。
sys
:コマンドライン引数をうけとるため使用します。
os
:パスから拡張子と拡張子以外へ分解するために使用します。
messagebox
:メッセージボックスを出すために使用します。
Image
:画像処理をするために使用します。
ImageSequence
:フレーム処理をするために使用します。 -
受け取ったコマンドライン引数の数だけループする
python# 受け取ったコマンドライン引数の数だけループする for fp in sys.argv[1:]:
受け取ったコマンドライン引数の数だけループします。
sys.argv[0]
は自分自身のパスを返すため、1以降をループします。 -
パス + 拡張子に分解して取得する
python# パス + 拡張子に分解して取得する root, ext = os.path.splitext(fp)
コマンドライン引数へ渡したファイルのパスを、拡張子とそれ以外に分解します。
C:\test\filename.png
の場合、C:\test\filename
と.png
に分解します。 -
もし拡張子がpng以外なら処理を終えて次のループを実行する
python# もし拡張子がpng以外なら処理を終えて次のループを実行する if ext != '.png': messagebox.showinfo('警告', '処理できません。対応する拡張子は png のみです。') continue
コマンドライン引数へ渡したファイルがpng以外の場合、処理を終了します。
-
pngがアニメーションかどうかで分岐する
python# pngがアニメーションかどうかで分岐する if img_png.is_animated:
img_png.is_animated
は、フレームが2枚以上(アニメーション)のとき、Trueを返します。静止画かアニメーションかで処理を分岐します。 -
処理終了のメッセージを出す
Python# 処理終了のメッセージを出す messagebox.showinfo('メッセージ', 'gifをエクスポートしました。')
メッセージボックスを表示します。
さいごに
apngからgifアニメーションに変換する方法がわからなくて苦労しました。
Pythonはあまり慣れていないので、書き方のお作法がよくわかっておらず、処理内容の説明も自信ないです。動くものは作れたので満足しています。
参考にしたサイト
Python で PNG から 透過 GIF を生成する
Image Module - Pillow (PIL Fork) 9.2.0 documentation
Python Tips: アニメーション GIF から静止画をまとめて抽出したい - Life with Python
Pythonで画像の減色をする
Python, Pillowで透過png画像を作成するputalpha | note.nkmk.me
Python, PillowでアニメーションGIFを作成、保存 | note.nkmk.me
python — プログラムでPythonでビデオまたはアニメーションGIFを生成しますか?
[備忘録] PythonのPILで透過GIFを作る時に後ろの画像を表示させない方法 - Qiita