Toma(とま)のゲーム日記

MHNOW、MHWIB、ELDEN RING、WILD HEARTSなどの役立ち情報をアップしていきます。ツイッターでの懸賞応募、自炊、家庭菜園といろいろ始めました。

記事内に商品プロモーションを含む場合があります。

PythonでLINEスタンプ制作を自動化!サイズ一括変換と透過を維持した白縁取り、ZIP圧縮まで完全ガイド

前回の記事では、3×3のタイル状に並んだアイコン画像をPythonでバラバラに分割し、スタンプの「素材」を作る方法をご紹介しました。

前回記事のリンク:めんどくさい画像分割をPythonで解決!3×3のアイコン画像をコード実行で透過&スタンプ素材にする

しかし、素材ができただけではLINEスタンプとして申請はできません。「サイズが合っていない」「背景が透過していない」「視認性を上げる白縁がない」など、申請用データに整えるまでには、意外と地味で面倒な作業が山積みです。

LINEスタンプ制作・申請用画像サイズ一覧のインフォグラフィック。スタンプ画像(最大370x320px、01.png〜40.png)、メイン画像(240x240px、main.png)、タブ画像(96x74px、tab.png)の3種類の規格を解説。透過背景の厳守、視認性を高める白縁(アウトライン)の活用、連番ルール、ZIP自動作成コードの利便性、および全画像で上下左右10px程度の余白推奨であることを説明しています。

 

特に、「リサイズしたら背景の透過が消えて白くなってしまった……」というのは、初心者の方が最もつまずきやすいポイントではないでしょうか?

そこで今回は、Pythonを使って以下の作業をコード一発で完結させる方法を解説します!

  • LINE公式仕様へのリサイズ: スタンプ用(370×320px)、メイン用(240×240px)、タブ用(96×74px)へ自動変換。
  • 透過を維持した白縁取り: 背景の透明度を保ったまま、キャラの周りに綺麗なアウトラインを追加。
  • アップロード用ZIPの自動生成: 1枚ずつ保存する手間を省き、そのまま申請できる状態に。

この記事を読み終える頃には、何十枚もの画像を一つずつ手作業で修正する苦労から解放されているはずです。それでは、さっそくコードの修正と実装を進めていきましょう!


 

LINEスタンプの画像サイズ・仕様をおさらい

まずは、今回作成するデータの「正解サイズ」を確認しておきましょう。LINEクリエイターズマーケットでは、以下の3種類の画像が必要になります。

LINEスタンプ制作・申請用画像サイズ一覧のインフォグラフィック。スタンプ画像(最大370x320px、01.png〜40.png)、メイン画像(240x240px、main.png)、タブ画像(96x74px、tab.png)の3種類の規格を解説。透過背景の厳守、視認性を高める白縁(アウトライン)の活用、連番ルール、ZIP自動作成コードの利便性、および全画像で上下左右10px程度の余白推奨であることを説明しています。

LINEスタンプ制作・申請用画像サイズ一覧
画像の種類 サイズ (幅 x 高さ) ファイル名
スタンプ画像 370 x 320 px (最大) 01.png ~ 40.png
メイン画像 240 x 240 px main.png
タブ画像 96 x 74 px tab.png

※すべての画像は「上下左右に10px程度の余白」を空けることが推奨されています。今回のコードでは、この余白も自動で計算して配置するようにしています。

 

透過を維持して「白縁」をつける:コードの核心部を解説

今回のスクリプトで最もこだわったのが、「背景の透過を維持したまま、キャラの周りにだけ白縁(アウトライン)をつける」処理です。

単純に背景を白く塗りつぶしてしまうと、LINEのトーク画面で見たときに四角い白枠が出てしまい、スタンプとしてのクオリティが下がってしまいます。これを防ぐための工夫が以下のステップです。

1. アルファチャンネル(透過情報)の抽出

まず、画像から「どの部分が不透明か」という情報(アルファチャンネル)だけを抜き出します。これが縁取りの「型紙」になります。

mask = base_img.getchannel('A')

2. フィルターによる「型紙」の膨張

抜き出した型紙を、MaxFilterを使って外側に数ピクセル分広げます。この「一回り大きくなった型紙」が、白縁を描く範囲になります。

edge_mask = mask.filter(ImageFilter.MaxFilter(5))

3. 透明なキャンバスでのレイヤー合成

ここが一番のポイントです。不透明な白を敷くのではなく、「完全に透明なキャンバス」の上に、以下の順序で重ねていきます。

  1. 下層:広げた型紙(edge_mask)の形に、白(outline_color)を塗る。
  2. 上層:その上に元のキャラ画像を重ねる。

これにより、キャラの周囲にだけ白が残り、それ以外の部分は「透明」なまま維持される仕組みです。

# 透明な土台を作る
outlined_img = Image.new("RGBA", base_img.size, (0, 0, 0, 0))
# 土台に「縁」を貼る
outlined_img.paste(outline_color, (0, 0), edge_mask)
# その上に「キャラ」を貼る
outlined_img.paste(base_img, (0, 0), mask)

以前のコードで背景が白くなってしまったのは、透明な土台ではなく「真っ白な画像」を背景に敷いてしまっていたのが原因でした。この「透明から作り直す」工程が、クオリティの差を生むポイントです。

 

LINEスタンプのファイル名規則とZIP圧縮

画像をリサイズするだけではなく、「正しいファイル名で保存し、ZIPにまとめる」までが遠足……ではなく、スタンプ制作です。LINEのシステムが正しく画像を認識できるよう、以下の命名規則を厳守する必要があります。

ファイル名・構成の一覧表

ファイル名 役割 備考
01.png ~ 40.png スタンプ画像 必ず01からの連番にする(01, 02...)
main.png メイン画像 LINEストアで顔になる画像
tab.png タブ画像 トーク画面の切り替えタブ用

注意点:ファイル名は必ず半角小文字である必要があります。また、ZIPファイルの中に「フォルダ」が入っているとエラーになるため、画像だけを直下に圧縮するのがポイントです。

ZIPファイル化を自動化するコード

Pythonのzipfileライブラリを使えば、保存した画像を一つずつ選択して右クリック……という手間を省き、コード実行と同時にアップロード用ZIPを生成できます。

# zipfileライブラリを使用して、フォルダ内の画像を1つにまとめる
zip_path = os.path.join(input_dir, "line_stamps_upload.zip")
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
    for root, dirs, f_list in os.walk(save_dir):
        for file in f_list:
            # フォルダ構造を含めず、ファイルだけをZIP直下に書き込む
            zipf.write(os.path.join(root, file), file)

このコードを組み込むことで、line_stamps_upload.zipというファイルが自動で作成されます。あとはこれをLINEクリエイターズマーケットの管理画面からアップロードするだけで、全画像の登録が一瞬で完了します!

メリット:手作業によるファイル名の付け間違いや、圧縮時の階層エラーによる「リジェクト(再申請)」のリスクをゼロにできるのが、プログラム化する最大の利点ですね。

 

LINEスタンプのファイル名規則とZIP圧縮

画像をリサイズするだけではなく、「正しいファイル名で保存し、ZIPにまとめる」までが、スタンプ制作の仕上げです。LINEのシステムが正しく画像を認識できるよう、以下の命名規則を厳守する必要があります。

ファイル名・構成の一覧表

ファイル名 役割 備考
01.png ~ 40.png スタンプ画像 必ず01からの連番にする(01, 02...)
main.png メイン画像 240x240px。ストアの顔になる画像
tab.png タブ画像 96x74px。トーク画面の切り替え用

注意点:ZIPファイルの中に「フォルダ」が入っているとアップロードエラーになります。画像ファイルがZIPの直下(ルート)に来るように圧縮するのがポイントです。

ZIPファイル化を自動化するコード

Pythonのzipfileモジュールを使えば、保存した画像を一つずつ手作業で圧縮する手間を省き、コード実行と同時に申請用ZIPを生成できます。

# zipfileライブラリを使用して、フォルダ内の画像を1つにまとめる
zip_path = os.path.join(input_dir, "line_stamps_upload.zip")
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
    for root, dirs, f_list in os.walk(save_dir):
        for file in f_list:
            # フォルダ構造を含めず、ファイルだけをZIP直下に書き込む
            zipf.write(os.path.join(root, file), file)

メリット:手作業によるファイル名の付け間違いや、圧縮時の階層エラーによる「リジェクト(再申請)」のリスクをゼロにできるのが、プログラム化する最大の利点です。


【完成版】一括リサイズ&ZIP作成フルコード

透過の維持、白縁の追加、リサイズ、そしてZIP圧縮までをすべて盛り込んだ完成版のコードです。これを実行するだけで、outputフォルダ内に申請用のZIPファイルができあがります。

作業前後のファイル構成を確認しよう

プログラムを実行する前に、フォルダの中身がどう変わるかを確認しておきましょう。構成を把握しておくことで、生成されたファイルを見失う心配がなくなります。

作業前:分割済み素材が揃っている状態

前回の記事の手順で、outputフォルダ内に素材(tile_*.png)が書き出されている状態からスタートします。

my_project/                 (プロジェクトのルートフォルダ)
├── Line_size_6.py          (今回作成したリサイズ&ZIP化コード)
└── output/                 (前回の処理で生成された画像フォルダ)
    ├── tile_0_0.png        (分割された素材1)
    ├── tile_0_1.png        (分割された素材2)
    └── ...                 (以下、分割された素材が並ぶ)

作業後:申請用データ(ZIP)が完成した状態

コードを実行すると、Line_mainフォルダの中にリサイズ済みの画像が生成され、最終的にアップロード用のZIPファイルが書き出されます。

my_project/
├── Line_size.py
└── output/
    ├── tile_0_0.png
    ├── ...
    ├── line_stamps_upload.zip (★これをLINEにアップロード!)
    └── Line_main/             (ZIPの中身となる画像フォルダ)
        ├── 01.png             (370x320px:白縁・リサイズ済み)
        ├── 02.png             (370x320px:白縁・リサイズ済み)
        ├── ...                (最大40.pngまで)
        ├── main.png           (240x240px:ストア表示用)
        └── tab.png            (96x74px:トークルームタブ用)

ポイントは、Line_mainフォルダの中にある画像が「正しいファイル名(01.pngなど)」で保存されていることです。プログラムが自動でリネームしてくれるので、私たちはZIPをアップロードするだけでOKです!

 

import os
import glob
import zipfile
from PIL import Image, ImageFilter

def process_line_stamps_pro(input_dir="output", target_pattern="tile_*.PNG"):
    save_dir = os.path.join(input_dir, "Line_main")
    os.makedirs(save_dir, exist_ok=True)

    # 読み込む画像リストを取得
    files = sorted(glob.glob(os.path.join(input_dir, target_pattern)))
    if not files:
        print("画像が見つかりませんでした。")
        return

    print(f"{len(files)}枚の画像を処理中...")

    for i, file_path in enumerate(files):
        # 01, 02... という2桁の連番ファイル名を作成
        file_number = f"{i+1:02}" 
        
        with Image.open(file_path).convert("RGBA") as base_img:
            
            # --- 共通処理:透過を維持した白縁(アウトライン)の追加 ---
            mask = base_img.getchannel('A')
            edge_mask = mask.filter(ImageFilter.MaxFilter(5)) 
            outline_color = Image.new("RGBA", base_img.size, (255, 255, 255, 255))
            
            # 透明なキャンバスに縁とキャラを合成
            outlined_img = Image.new("RGBA", base_img.size, (0, 0, 0, 0))
            outlined_img.paste(outline_color, (0, 0), edge_mask)
            outlined_img.paste(base_img, (0, 0), mask)
            
            processed_img = outlined_img

            # --- A. 個別スタンプ画像 (370 x 320) ---
            stamp_img = processed_img.copy()
            # 10px程度の余白を考慮してリサイズ
            stamp_img.thumbnail((350, 300), Image.Resampling.LANCZOS)
            canvas_stamp = Image.new("RGBA", (370, 320), (0, 0, 0, 0))
            canvas_stamp.paste(stamp_img, ((370 - stamp_img.width)//2, (320 - stamp_img.height)//2))
            canvas_stamp.save(os.path.join(save_dir, f"{file_number}.png"))

            # --- B. 最初の1回だけ 申請用セット (main / tab) 作成 ---
            if i == 0:
                # メイン画像 (240 x 240)
                m = processed_img.copy()
                m.thumbnail((220, 220), Image.Resampling.LANCZOS)
                mc = Image.new("RGBA", (240, 240), (0, 0, 0, 0))
                mc.paste(m, ((240 - m.width)//2, (240 - m.height)//2))
                mc.save(os.path.join(save_dir, "main.png"))

                # トークルームタブ画像 (96 x 74)
                t = processed_img.copy()
                t.thumbnail((80, 60), Image.Resampling.LANCZOS)
                tc = Image.new("RGBA", (96, 74), (0, 0, 0, 0))
                tc.paste(t, ((96 - t.width)//2, (74 - t.height)//2))
                tc.save(os.path.join(save_dir, "tab.png"))

    # --- ZIP圧縮:フォルダ構造を含めず直下に固める ---
    zip_path = os.path.join(input_dir, "line_stamps_upload.zip")
    with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for root, dirs, f_list in os.walk(save_dir):
            for file in f_list:
                zipf.write(os.path.join(root, file), file)
    
    print(f"完了! {zip_path} が作成されました。")

if __name__ == "__main__":
    process_line_stamps_pro()

 

まとめ:自動化でイラスト制作にもっと集中しよう!

今回は、LINEスタンプ申請における最大の難所「サイズ調整」と「透過・白縁処理」をPythonで自動化する方法をご紹介しました。

手作業で1枚ずつリサイズや縁取りをしていると、どうしても数時間かかってしまいますが、このスクリプトを使えば数十枚の画像もわずか数秒で処理が完了します。

もし、まだ画像処理ライブラリの「Pillow」をインストールしていない方は、ターミナルやコマンドプロンプトで以下のコマンドを実行しておいてくださいね。

pip install Pillow

今回のポイントを振り返ると:

  • 透過を壊さないImage.pasteを活用して透明なキャンバスに重ねる。
  • 視認性を上げるMaxFilterでキャラの形に合わせた白縁を自動生成する。
  • 手間を省く:メイン・タブ画像も含めて一括でZIPにまとめる。

これで、技術的な「作業」の時間は大幅に短縮されました。余った時間は、ぜひ次のスタンプのアイデア出しや、魅力的なキャラクターイラストを描く時間に充ててください!

「Toma(とま)のゲーム日記」では、今後もこうした「ゲーム制作やクリエイティブ活動をちょっと楽にする技術」を発信していく予定です。不明点があれば、ぜひコメントで教えてくださいね。

それでは、楽しいクリエイティブライフを!