Python
psdファイルをPythonで読み込みレイヤーの表示・非表示を切り替える
Posted on 2022年3月9日 (Last modified on 2023年1月14日) 2 min read • 413 words最近の動画で使用させていただいている春日部つむぎのpsdファイルを読み込み、動画で使用する口パク動画を作りたいです。 手元にWindowsPCがないためAviUtilが使えず、代替としてPythonで自作してみたいと思います。
※利用規約により画像の配布は禁止されているため、実行結果は規約を読んで素材を取得した上で確認してください
psd-tool
psd-tools — psd-tools 1.9.18 documentation
以下の環境で実行しています。
python = "^3.8"
numpy = "^1.22.3"
psd-tools = "^1.9.18"
matplotlib = "^3.5.1"
jupyter = "^1.0.0"
ipykernel = "^6.9.1"
ファイル読み込み+グループ・レイヤーの確認
import psd_tools
psd = psd_tools.PSDImage.open('./春日部つむぎ立ち絵_公式_v1.1.1/春日部つむぎ立ち絵_公式_v1.1.1.psd')
for group in psd:
print(group)
for layer in group:
print(f'---- {layer}')
Group('!体部分' size=1658x3868)
---- PixelLayer('髪\u3000サイドテール(反転用)' size=340x809 invisible)
---- PixelLayer('髪\u3000ロング' size=764x743)
---- PixelLayer('体' size=1422x3277)
---- PixelLayer('スカート' size=850x579)
---- PixelLayer('シャツ' size=931x1174)
---- PixelLayer('セーター' size=1504x1312)
---- PixelLayer('シュシュ' size=243x284)
---- PixelLayer('ネクタイ' size=275x753)
---- PixelLayer('ネックレス' size=121x123)
---- PixelLayer('缶バッチなど' size=803x746)
---- PixelLayer('腕章' size=158x242)
---- PixelLayer('髪\u3000サイドテール' size=340x857)
---- PixelLayer('髪\u3000もみあげ' size=587x680)
---- PixelLayer('顔' size=383x453)
---- PixelLayer('髪\u3000前髪' size=764x710)
Group('!口' size=76x11)
---- PixelLayer('*3' size=10x25 invisible)
---- PixelLayer('*ω\u3000わ' size=76x58 invisible)
---- PixelLayer('*ω' size=76x11)
---- PixelLayer('*べー' size=61x25 invisible)
...
画像にして確認
import psd_tools
psd = psd_tools.PSDImage.open('./春日部つむぎ立ち絵_公式_v1.1.1/春日部つむぎ立ち絵_公式_v1.1.1.psd')
psd.composite()
指定したレイヤーを非表示にする
- psd_tools.api.layers
- composite(viewport=None, force=False, color=1.0, alpha=0.0, layer_filter=None, ignore_preview=False)
visible
をFalseに設定することで非表示に。opacity
を0にすることでも非表示にしてもいいかもしれません。Layer/Groupにvisible
の設定が指定でき、ここでは口のレイヤーを非表示にしてみます。ただし、自分の環境ではforce=True
としないとレイヤーを非表示にできませんでした。
force – Boolean flag to force vector drawing.
ということですが、なぜ force=False
だと出力されないのかは不明です。
import psd_tools
psd = psd_tools.PSDImage.open('./春日部つむぎ立ち絵_公式_v1.1.1/春日部つむぎ立ち絵_公式_v1.1.1.psd')
for group in psd:
name = group.name
if name == '!口':
group.visible = False
psd_without_mouse = psd.composite(force=True)
psd_without_mouse
一部レイヤーのvisible/opacityを変更したpsdを画像として保存する
ここまで来れば、総当たりで全ての表情の組合せをpngにして出力することができます。口だけ変えた全パターンを出力してみます。
visible
を一枚ずつ変化させて画像を出力させると、なぜか出力される画像に欠損が発生してしまいました。そのため、全てのレイヤーでvisible=True
とした後で透明度を0 (opacity=0
) にした上で、一枚ずつ透明度を上げて画像を出力しています。
import os
import psd_tools
import time
output_dir = './output_image'
os.makedirs(output_dir, exist_ok=True)
psd = psd_tools.PSDImage.open('./春日部つむぎ立ち絵_公式_v1.1.1/春日部つむぎ立ち絵_公式_v1.1.1.psd')
for group in psd:
name = group.name
if name == '!口':
# 一旦すべてのレイヤーの透明度を0にする
for i, layer_i in enumerate(group):
layer_i.visible = True
layer_i.opacity = 0
# レイヤーを一つずつ表示して画像に出力
for i, layer_i in enumerate(group):
print(f'> {layer_i.name} の画像を出力中...')
layer_i.opacity = 255
img = psd.composite(force=True)
img.save(os.path.join(output_dir, f'口={layer_i.name}.png'))
layer_i.opacity = 0
del img
> *3 の画像を出力中...
> *ω わ の画像を出力中...
...
全ての表情パターンの画像を出力
itertools
を用いて全ての表情パターンを上と同じ要領で出力します。一枚出力するのに数秒かかるため、全て出力するのにかなり時間がかかるので注意してください。
import itertools
import os
import psd_tools
import time
output_dir = './output_image'
os.makedirs(output_dir, exist_ok=True)
psd = psd_tools.PSDImage.open('./春日部つむぎ立ち絵_公式_v1.1.1/春日部つむぎ立ち絵_公式_v1.1.1.psd')
mouses, eyes, eyebrows = None, None, None
for group in psd:
name = group.name
if name in {'!口', '!目', '!眉'}:
# 一旦group内のすべてのレイヤーを非表示にする
for i, layer_i in enumerate(group):
layer_i.visible = True
layer_i.opacity = 0
if name == '!口':
mouses = group
elif name == '!目':
eyes = group
elif name == '!眉':
eyebrows = group
# レイヤーを一つずつ表示して画像に出力
for mouse, eye, eyebrow in itertools.product(mouses, eyes, eyebrows):
if not eyebrow.name == '普通':
continue # 実行時間が長いため眉毛は普通のみ
print(f'> 口={mouse.name} 目={eye.name} 眉={eyebrow.name} の画像を出力中...')
mouse.opacity = 255
eye.opacity = 255
eyebrow.opacity = 255
img = psd.composite(force=True)
img.save(os.path.join(output_dir, f'口={mouse.name}_目={eye.name}_眉={eyebrow.name}.png'))
mouse.opacity = 0
eye.opacity = 0
eyebrow.opacity = 0
> 口=*3 目=*赤目 口=*ん? の画像を出力中...
> 口=*3 目=*赤目 口=*真面目 の画像を出力中...
> 口=*3 目=*赤目 口=*悲しい の画像を出力中...
> 口=*3 目=*赤目 口=*怒る の画像を出力中...
> 口=*3 目=*赤目 口=*困る の画像を出力中...
> 口=*3 目=*赤目 口=*普通 の画像を出力中...
> 口=*3 目=*白目 口=*ん? の画像を出力中...
> 口=*3 目=*白目 口=*真面目 の画像を出力中...
> 口=*3 目=*白目 口=*悲しい の画像を出力中...
> 口=*3 目=*白目 口=*怒る の画像を出力中...
> 口=*3 目=*白目 口=*困る の画像を出力中...
> ...
On this page
¯\_(ツ)_/¯
こんちゃす。