Photoshop│PythonでPSDファイルを操作できるpsd-toolsの使い方

Image Magicの代替として使い始めたpsd-tools

PSDの編集はできないものの、情報の取得に特化していて便利です。

以下、詳しい内容です。

psd-toolsは、PythonでAdobe PhotoshopのPSDファイルを読み書きできるライブラリです。

PSDファイルの構造を解析し、レイヤー情報の取得や画像のエクスポート、さらにはファイルの編集まで可能にします。

主な特徴

  • PSD/PSBファイルの低レベル構造の読み書き
  • レイヤー画像のNumPy配列やPIL形式でのエクスポート
  • 基本的なピクセルベースレイヤーの合成
  • ベクターマスクのサポート
  • レイヤー属性(名前、可視性、不透明度など)の編集
  • レイヤーやグループの追加・削除
  • ブレンドモードのサポート(dissolveを除く)
  • コマンドラインツールの提供
目次

インストール

基本インストール

pip install psd-tools

高度な合成機能を使う場合

ベクターシェイプ、グラデーション、パターン塗り、レイヤーエフェクトなどの高度な合成機能を使用する場合は、compositeエクストラをインストールします。

pip install 'psd-tools[composite]'

これにより、aggdraw、scipy、scikit-imageといった追加の依存関係がインストールされます。

基本的な合成はNumPyのみで動作しますが、高度な機能にはこれらのライブラリが必要です。

32bitファイルのサポート

32bit PSDファイルから画像を抽出する場合、PIL/PillowがLITTLECMSまたはLITTLECMS2サポート付きでビルドされている必要があります。

# Ubuntu/Debian
apt-get install liblcms2-2

# macOS
brew install little-cms2

基本的な使い方

PSDファイルを開く

from psd_tools import PSDImage

# PSDファイルを開く
psd = PSDImage.open('example.psd')

# ファイル情報を表示
print(psd)  # PSDImage(mode=RGB size=101x55 depth=8 channels=3)

PSDファイル全体を画像としてエクスポート

# 全レイヤーを合成してPNG出力
image = psd.composite()
image.save('output.png')

レイヤーを反復処理

# 全レイヤーをループ
for layer in psd:
    print(f"レイヤー名: {layer.name}")
    print(f"レイヤータイプ: {layer.kind}")
    print(f"サイズ: {layer.width}x{layer.height}")
    print(f"可視性: {layer.visible}")
    print("---")

イテレーション順序は背景から前景への順です(バージョン1.7.x以前とは逆)。

前景から背景へ反復するにはreversed(psd)を使用します。

インデックスでレイヤーにアクセス

# 最初のレイヤーを取得
first_layer = psd[0]

# ネストしたレイヤーにアクセス
nested_layer = psd[0][0]

レイヤーの種類

PixelLayer(ピクセルレイヤー)

最も基本的なレイヤータイプで、ラスター画像を含みます。

# ピクセルレイヤーの確認
if layer.kind == 'pixel':
    # レイヤーを画像として出力
    layer_image = layer.composite()
    layer_image.save(f'{layer.name}.png')
    
    # NumPy配列として取得
    pixels = layer.numpy()
    print(pixels.shape)  # (height, width, channels)
    
    # PIL Imageとして取得
    pil_image = layer.topil()

Group(グループレイヤー)

複数のレイヤーをまとめるグループです。

# グループ内のレイヤーを反復処理
for layer in psd:
    if layer.is_group():
        print(f"グループ: {layer.name}")
        for child in layer:
            print(f"  - {child.name}")

TypeLayer(テキストレイヤー)

テキストとフォント情報を含むレイヤーです。

if layer.kind == 'type':
    # テキスト内容を取得
    print(f"テキスト: {layer.text}")
    
    # スタイル情報(読み取り専用)
    print(f"エンジン辞書: {layer.engine_dict}")
    print(f"リソース辞書: {layer.resource_dict}")

注意: テキスト情報は現在読み取り専用で、編集はサポートされていません。

ShapeLayer(シェイプレイヤー)

ベクターシェイプを描画するレイヤーです。

if layer.kind == 'shape':
    # ベクターマスク情報
    print(layer.vector_mask)
    
    # ライブシェイププロパティ
    for shape in layer.origination:
        print(shape)

SmartObjectLayer(スマートオブジェクトレイヤー)

外部ファイルを埋め込むまたはリンクするレイヤーです。

import io
from PIL import Image

if layer.kind == 'smartobject':
    # スマートオブジェクトデータにアクセス
    smart_obj = layer.smart_object
    
    if smart_obj.filetype in ('jpg', 'png'):
        image = Image.open(io.BytesIO(smart_obj.data))
        image.save(f'{layer.name}_embedded.{smart_obj.filetype}')

塗りつぶしレイヤー

塗りつぶしレイヤーは、マスクがない場合に領域全体を塗りつぶします。

# SolidColorFill(ベタ塗り)
# PatternFill(パターン)
# GradientFill(グラデーション)

AdjustmentLayer(調整レイヤー)

合成画像に適用される調整レイヤーです。

レイヤー属性の取得と編集

基本属性

# レイヤー情報の取得
print(f"名前: {layer.name}")
print(f"可視性: {layer.visible}")
print(f"不透明度: {layer.opacity}")  # 0-255
print(f"位置: ({layer.left}, {layer.top})")
print(f"サイズ: {layer.width}x{layer.height}")
print(f"バウンディングボックス: {layer.bbox}")  # (left, top, right, bottom)

# レイヤー属性の編集(書き込み可能)
layer.name = "新しい名前"
layer.visible = False
layer.opacity = 128
layer.left = 100
layer.top = 50

ブレンドモード

from psd_tools.constants import BlendMode

# ブレンドモードの確認
print(layer.blend_mode)

# ブレンドモードの変更
if layer.blend_mode == BlendMode.NORMAL:
    layer.blend_mode = BlendMode.SCREEN

サポートされているブレンドモード(dissolveを除く):

  • NORMAL, MULTIPLY, SCREEN, OVERLAY
  • SOFT_LIGHT, HARD_LIGHT, COLOR_DODGE, COLOR_BURN
  • DARKEN, LIGHTEN, DIFFERENCE, EXCLUSION
  • HUE, SATURATION, COLOR, LUMINOSITY など

レイヤーのロック

from psd_tools.constants import ProtectedFlags

# レイヤーのピクセルと位置をロック
layer.lock(ProtectedFlags.COMPOSITE | ProtectedFlags.POSITION)

マスク操作

# マスクの有無を確認
if layer.has_mask():
    # マスクを取得
    mask = layer.mask
    
    # マスクを画像として出力
    mask_image = mask.topil()
    mask_image.save('mask.png')

# ベクターマスクの確認
if layer.has_vector_mask():
    print(layer.vector_mask)

レイヤーの階層操作

レイヤーの検索

# 名前でレイヤーを検索
def find_layer_by_name(psd, name):
    for layer in psd.descendants():
        if layer.name == name:
            return layer
    return None

target_layer = find_layer_by_name(psd, "背景")

レイヤーの並び替え

# レイヤーを上に移動
layer.move_up()

# レイヤーを下に移動
layer.move_down()

レイヤーの追加と削除

# レイヤーをグループに追加
group = psd[0]
group.append(layer)

# レイヤーを削除
layer.remove()

# グループをクリア
group.clear()

新規PSDファイルの作成

ゼロから作成

from PIL import Image
from psd_tools import PSDImage

# 新しいPSDを作成
psd = PSDImage.new(mode='RGB', size=(640, 480), depth=8)

# PIL画像からレイヤーを作成
pil_image = Image.open('input.png')
layer = psd.create_pixel_layer(
    pil_image,
    name="Layer 1",
    top=0,
    left=0,
    opacity=255
)

# グループを作成
group = psd.create_group(name="Group 1")
group.append(layer)

# 保存
psd.save('new_image.psd')

レイヤーリストからグループを作成

# 複数のレイヤーを新しいグループにまとめる
layer1 = psd[0]
layer2 = psd[1]

group = psd.create_group(
    layer_list=[layer1, layer2],
    name="New Group"
)

画像エクスポート

全体を合成して出力

# PSD全体を合成
image = psd.composite()
image.save('output.png')

# 特定のビューポートで合成
viewport = (0, 0, 300, 300)  # (x1, y1, x2, y2)
image = psd.composite(viewport=viewport)

レイヤーフィルターを使用

# テキストレイヤーを除外して合成
image = psd.composite(
    layer_filter=lambda layer: layer.is_visible() and layer.kind != 'type'
)

レイヤーごとに出力

for layer in psd:
    if layer.kind == 'pixel':
        # レイヤーを合成して出力(マスクとクリッピングレイヤーを含む)
        layer_image = layer.composite()
        layer_image.save(f'{layer.name}.png')

マスクなしで出力

# レイヤーとマスクを別々にエクスポート(合成なし)
layer_image = layer.topil()
layer_image.save('layer_no_mask.png')

if layer.has_mask():
    mask_image = layer.mask.topil()
    mask_image.save('mask.png')

特定のチャンネルを取得

from psd_tools.constants import ChannelID

# RGB個別チャンネル
red = layer.topil(ChannelID.CHANNEL_0)
green = layer.topil(ChannelID.CHANNEL_1)
blue = layer.topil(ChannelID.CHANNEL_2)

# アルファチャンネル
alpha = layer.topil(ChannelID.TRANSPARENCY_MASK)

NumPy配列として扱う

NumPy配列を使用することで、画像処理ライブラリ(OpenCV、scikit-imageなど)と組み合わせて高度な処理が可能になります。

import numpy as np

# PSD全体をNumPy配列として取得
array = psd.numpy()
print(array.shape)  # (height, width, channels)

# レイヤーをNumPy配列として取得
layer_array = layer.numpy()

# 特定のチャンネルのみ取得
color_only = layer.numpy(channel='color')
alpha_only = layer.numpy(channel='alpha')
mask_only = layer.numpy(channel='mask')
shape_only = layer.numpy(channel='shape')

コマンドラインツール

psd-toolsはコマンドラインからも使用できます。

PSDファイルの内容を表示

psd-tools show example.psd

PSDを画像として出力

# PSD全体をエクスポート
psd-tools export example.psd output.png

# 特定のレイヤーをエクスポート(インデックス指定)
psd-tools export example.psd[0] layer0.png

デバッグ情報の表示

psd-tools debug example.psd

高度な機能

レイヤーエフェクト

# レイヤーエフェクトの有無を確認
if layer.has_effects():
    # エフェクト情報にアクセス
    effects = layer.effects

注意: レイヤーエフェクトと調整レイヤーの多くは完全にはサポートされていません。合成結果がPhotoshopと異なる場合があります。

ベクターマスクとライブシェイプ

# ベクターマスク情報
if layer.has_vector_mask():
    vector_mask = layer.vector_mask
    print(vector_mask)

# ライブシェイププロパティ
if layer.has_origination():
    for shape in layer.origination:
        # Rectangle, RoundedRectangle, Ellipse, Line など
        print(type(shape).__name__)

クリッピングマスク

# クリッピングフラグ
print(layer.clipping)
layer.clipping = 1  # クリッピングマスクとして設定

タグ付きブロック

低レベルのPSD設定にアクセスできます。

from psd_tools.constants import Tag

# タグ付きブロックにアクセス
tagged_blocks = layer.tagged_blocks

# 特定のタグの情報を取得
if Tag.LAYER_ID in tagged_blocks:
    layer_id = tagged_blocks[Tag.LAYER_ID]

バッチ処理の例

PSDファイルからすべてのレイヤーをエクスポート

from pathlib import Path
from psd_tools import PSDImage

def export_all_layers(psd_path, output_dir):
    """PSDファイルの全レイヤーを個別の画像ファイルとして出力"""
    psd = PSDImage.open(psd_path)
    output_path = Path(output_dir)
    output_path.mkdir(exist_ok=True)
    
    for i, layer in enumerate(psd.descendants()):
        if layer.kind in ('pixel', 'shape'):
            try:
                image = layer.composite()
                # ファイル名をサニタイズ
                safe_name = "".join(c for c in layer.name if c.isalnum() or c in (' ', '-', '_'))
                image.save(output_path / f"{i:03d}_{safe_name}.png")
                print(f"Exported: {layer.name}")
            except Exception as e:
                print(f"Error exporting {layer.name}: {e}")

# 使用例
export_all_layers('example.psd', 'output_layers')

レイヤー情報を抽出してJSON化

import json
from psd_tools import PSDImage

def extract_layer_info(psd_path):
    """レイヤー情報を辞書形式で抽出"""
    psd = PSDImage.open(psd_path)
    
    def layer_to_dict(layer):
        info = {
            'name': layer.name,
            'kind': layer.kind,
            'visible': layer.visible,
            'opacity': layer.opacity,
            'blend_mode': str(layer.blend_mode),
            'bbox': layer.bbox,
            'has_mask': layer.has_mask(),
        }
        
        if layer.is_group():
            info['children'] = [layer_to_dict(child) for child in layer]
        
        return info
    
    result = {
        'mode': psd.mode,
        'size': (psd.width, psd.height),
        'depth': psd.depth,
        'layers': [layer_to_dict(layer) for layer in psd]
    }
    
    return result

# 使用例
info = extract_layer_info('example.psd')
print(json.dumps(info, indent=2, ensure_ascii=False))

PSDファイルの一括変換

from pathlib import Path
from psd_tools import PSDImage

def batch_convert_psd_to_png(input_dir, output_dir):
    """ディレクトリ内の全PSDファイルをPNGに変換"""
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(exist_ok=True)
    
    for psd_file in input_path.glob('*.psd'):
        try:
            psd = PSDImage.open(psd_file)
            image = psd.composite()
            
            output_file = output_path / f"{psd_file.stem}.png"
            image.save(output_file)
            print(f"Converted: {psd_file.name} -> {output_file.name}")
        except Exception as e:
            print(f"Error converting {psd_file.name}: {e}")

# 使用例
batch_convert_psd_to_png('input_psds', 'output_pngs')

制限事項と注意点

サポートされていない機能

以下の機能は現在サポートされていません:

  • テキストレイヤーのテキスト編集
  • タイプレイヤー、シェイプレイヤー、スマートオブジェクトなどの完全な編集
  • 調整レイヤーの合成
  • 多くのレイヤーエフェクトの合成
  • フォントのレンダリング

合成の精度

  • レイヤーの合成結果は、Photoshopでの表示と異なる場合があります
  • dissolveブレンドモードはサポートされていません
  • すべてのレイヤーエフェクトと調整レイヤーが完全にサポートされているわけではありません

PSD仕様の不完全性

Adobe Photoshop PSD file formatの仕様は完全ではなく、多くの未文書化された部分があります。

ドキュメントで必要な情報が見つからない場合は、低レベルのデータ構造を直接調査する必要があります。

# 低レベルデータ構造にアクセス
psd._record  # 低レベルPSDレコード

パフォーマンスの考慮事項

  • 大きなPSDファイルの処理にはメモリと時間がかかります
  • 高度な合成機能(composite extra)は処理が重い場合があります
  • NumPy配列を使用することで処理を高速化できる場合があります

トラブルシューティング

ImportError: Advanced compositing features require optional dependencies

高度な合成機能を使用しようとした際にこのエラーが出る場合は、compositeエクストラをインストールしてください。

pip install 'psd-tools[composite]'

32bit PSDファイルが読めない

PIL/PillowがLITTLECMSサポート付きでビルドされているか確認してください。

# Ubuntu/Debian
apt-get install liblcms2-2

# macOS
brew install little-cms2

# Pillowを再インストール
pip uninstall Pillow
pip install Pillow

レイヤーが正しく合成されない

高度な合成機能が必要な場合があります。

compositeエクストラをインストールしているか確認してください。

また、一部のレイヤーエフェクトや調整レイヤーは完全にサポートされていません。

まとめ

psd-toolsは、PythonでPSDファイルを操作するための強力なライブラリです。

基本的な読み込みとエクスポートから、レイヤー属性の編集、新規PSDの作成まで幅広い機能を提供します。

主な用途:

  • PSDファイルからの画像やレイヤーの一括エクスポート
  • レイヤー情報の自動抽出と分析
  • PSDファイルのバッチ処理
  • PSDテンプレートからの自動生成
  • 画像処理パイプラインへの統合

完全な機能はPhotoshopには及びませんが、自動化やバッチ処理には十分な機能を備えています。

参考リンク

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

WAZAの有料記事のサブスクリプションも開始しました。

目次