[python]複数の画像を連結、リサイズしてウディタのマップチップ画像にする

概要

この間買った大量の加工可能、ゲームエンジン自由のマップチップをウディタで扱いたいので書きました。

必要なチップを選択して一枚の画像にしたいだけならぴぽやさんの下記の記事がおすすめです。

Tiledでタイルセット画像を並び替えたりパーツ合成する方法 - ぴぽや倉庫
本記事は2017年3月に旧ブログに掲載したものをそのまま転載しております。Tiledは2019年10月現在も活発にアップデートが行われているため、記事内容と最新版との機能では若干違う部分もあると思いますので、ご了承ください。 WOLF

環境

Python3.7

更新履歴

2021-10-15 リストに文字列を追加するのがだるかったのでin以下のフォルダ名でウディタ用マップ画像を出力するように修正しました new
2021-07-17 Tiledが便利だったので概要に記事を追加
2021-07-05 以前のコードだと一部変換できない画像が出てきたので修正

参考

Installing scikit-image — skimage v0.18.0 docs
Python, Pillowで画像の一部をトリミング(切り出し/切り抜き) | note.nkmk.me
Pythonの画像処理ライブラリPillow(PIL)のImageモジュールに、画像の一部の領域を切り抜くメソッドcrop()が用意されている。Image Module — Pillow (PIL Fork) 4.2.1 documentation ここでは以下の4つの場合についてサンプルコードとともに説明する。通常の...

※ほか同サイトのいろいろな記事

内容

インストール

Pillowとscikit-imageとOpenCV使用するのでインストールします。
いろいろあってキメラにライブラリを使っています…。

pip install Pillow
pip install scikit-image
pip install opencv-python

コード

# coding: utf-8
import os
import cv2
from PIL import Image
import skimage.io
import glob

# 説明: inフォルダ内にある、ディレクトリ名以下の画像ファイルを連結してウディタのタイルサイズの横幅で切断します
# 具体的に in/[ウディタ]街/なんとか1.png in/[ウディタ]街/なんとか2.png とした場合
# ウディタのマップタイルとしてそのまま使える幅で out/[ウディタ]街.png が作られます

# ウディタマップのタイルサイズ、32x32の場合は32と書く
tile_size = 32
# 拡大倍率、16x16のマップタイルを32x32にしたい場合は2と指定する、小数点、縮小は非対応。整数でおねがいします。
resize_rate = 1

# 初期化
max_width = tile_size * 8
slice_width_from = 0
slice_width_to = 0
slice_count_list = {}
image_list = []


# list内でvalueのある配列が何番目かを返却
def getListObjectValueIndex(lists, label, value):
    target_index = -1
    now_index = 0
    for obj in lists:
        if obj[label] == value:
            target_index = now_index
            break
        now_index = now_index + 1

    return target_index


# inフォルダの中身を取得
files = glob.glob("./in/*/*")
for file in files:
    file_name_array = file.split('\\')
    folder_name = file_name_array[1]
    file_name = file_name_array[2]
    if folder_name in '_end':
        # _end のディレクトリは無視
        continue

    map_name_index = getListObjectValueIndex(image_list, "map_name", folder_name)
    if map_name_index > -1:
        # 同じフォルダ内にある画像を連結用リストに追加
        image_list[map_name_index]["map_list"].append(file_name)
    else:
        # フォルダ単位でマップチップをまとめる
        map_obj = {
            "map_name": folder_name,
            "resize": resize_rate,
            "map_list": [
                file_name,
            ],
        }
        image_list.append(map_obj)

# マップ単位のループ
for img_obj in image_list:
    total_slice_count = 0
    # マップリスト単位のループ
    for img_name in img_obj["map_list"]:
        map_chip_name = img_obj["map_name"]
        image = Image.open('./in/' + map_chip_name + '/' + img_name)

        # 画像サイズ
        w = image.size[0]
        h = image.size[1]
        slice_count = 0

        # 必要な場合画像リサイズ
        if img_obj["resize"] > 1:
            # あまり正確ではないので整数以外入れないほうがいい
            w = int(w * img_obj["resize"])
            h = int(h * img_obj["resize"])
            image = image.resize((w, h), Image.NEAREST)
        while slice_count < (w / max_width):
            # 切り出せる横幅の計算
            slice_width_from = slice_count * max_width
            slice_width_to = slice_width_from + max_width

            # max_width幅で切り出し(slice_width_from: h, 0: slice_width_to)
            img1 = image.crop((slice_width_from, 0, slice_width_to, h))

            # 画像の書き出し
            out_file_name = "./out/" + img_obj["map_name"] + '_' + str(total_slice_count) + '.png'
            img1.save(out_file_name, 'PNG')
            print(out_file_name)

            # 処理回数記録
            slice_count = slice_count + 1
            total_slice_count = total_slice_count + 1
    # 最終的な処理回数
    slice_count_list[img_obj["map_name"]] = total_slice_count

print(slice_count_list)
# 画像結合
for img_obj in image_list:
    print(img_obj)
    cnt = 0
    max_cnt = int(slice_count_list[img_obj["map_name"]])
    out_img_list = []
    while cnt < max_cnt:
        filepath = './out/' + img_obj["map_name"] + '_' + str(cnt) + ".png"

        # cv2はアスキー文字以外の読み込みができないのでskimageで行う
        img_item = skimage.io.imread(filepath)
        out_img_list.append(img_item)

        # 結合用に作ったファイルを削除
        os.remove(filepath)
        cnt = cnt + 1

    # 画像結合して保存
    # skimageで画像結合するとndimが4以外になって落ちることが頻発するので結合はOpenCvを使う
    im_v = cv2.vconcat(out_img_list)
    out_file_name = "./out/" + img_obj["map_name"] + '.png'

    # skimageで読み込んだものはskimageで保存しないとRBが入れ替わって悲惨なことになる
    skimage.io.imsave(out_file_name, im_v)

使い方

ファイルと同じ階層に「in」フォルダと「out」フォルダを作って、inフォルダの方に結合したいマップを入れます。
コード内の「結合対象のマップチップリスト」を編集してください。項目の詳細は下記の通りです。
map_name: 最終的な保存ファイル名を書く
resize: リサイズ時の拡大率、2倍にして切り取りたい場合は2、そのままでいいなら1。整数で入力してください。
map_list: 切り取りしたいマップチップのリストを拡張子付きで並べる
pythonを実行後は、resizeの拡大率でリサイズされたmap_listの画像が、ウディタ規格のマップチップの幅(タイルサイズ*8倍)にカットされて、map_nameの名前で縦に結合されます。

使用方法を修正しました。

1.pythonファイルの下記の部分を、使用するタイルサイズと拡大率に変更してください。

# ウディタマップのタイルサイズ、32x32の場合は32と書く
tile_size = 32
# 拡大倍率、16x16のマップタイルを32x32にしたい場合は2と指定する、小数点や縮小は非対応
resize_rate = 1

2.pythonファイルと同じ階層に「in」フォルダと「out」フォルダを作ります。
inフォルダの中を下記とします。

[ウディタ]始まりの村/なんとか.png
[ウディタ]始まりの村/なんとかw.png
[ウディタ]始まりの村/なっs.png
[ウディタ]始まりの村/なんとかああああ.png
[ウディタ]最終ダンジョン/ほげほげ.png

仮に上記のコードのまま、実行すると、「out」フォルダに下記のファイルが現れるはずです。
32*32ピクセル*8列分の幅で切られて縦に結合されているので、そのままウディタのマップチップフォルダに入れて使えます。

[ウディタ]始まりの村.png
[ウディタ]最終ダンジョン.png

おまけ

このpythonで切断したマップチップで作ったマップの動画。
いまのところも透過処理もされているようです。

おわり。