vscode-drawioのライブラリをPythonで作成して追加する

## Abstract

VSCodeでdrawio、つかってますか？

https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio

draw.ioというダイアグラム作成を行うツールの(非公式)VSCode拡張機能です。
VSCode上で図を作成できるので、非常に重宝しています。

本記事では、**png**画像で構成されたライブラリを作成・追加する方法を紹介します。

:::note warn
svg画像の場合はZennに記事がありましたが、png画像だとフォーマットが同じではないです。
https://zenn.dev/capybara_alt/articles/a3fc0f1eadeca1
:::

## 環境

- Windows 10
- Python 3.11
    - opencv-python==4.8.1.78

OpenCV, tqdmを利用します。

```shell
pip install opencv-python tqdm
```

## ライブラリファイルの形式

png画像のライブラリを表すファイルは、下記のような形式になっています。

```lib.xml
<mxlibrary>[
{
    "data": string,    // png画像のbase64エンコード。
//  "xml": string, svgの場合は data ではなくこっち。
    "w": number,       // width
    "h": number,       // height
    "title": string,   // 画像名
    "aspect": "fixed" | "variable"      // 縦横比を固定するか可変にするか
},
...
]</mxlibrary>
```

dataの値は、png画像の場合は`data:image/png;base64,...`となっていて、いわゆるbase64エンコードで保存しています。

従ってpng画像をライブラリするためには:

1. Base64エンコードして
1. 画像のサイズを取得して
1. 上記のライブラリ形式を作成
1. 最後にライブラリファイルを書き込む

という手順が必要なことになります。

本記事ではPythonで実装しようと思います。

## ライブラリファイルの生成

```main.py
"""
対象となるフォルダのpng画像を基にしたdrawioのライブラリファイルを作成する。
NOTE: pngファイルのみをライブラリに入れる。
"""
from typing import Literal
import pathlib
import base64
import json
import sys

import cv2
from tqdm import tqdm

__dirname = pathlib.Path(__file__).parent

def to_base64_value(image_path):
    img = open(image_path, 'rb').read()
    base64_str = base64.b64encode(img).decode('utf-8')
    return "data:image/png;base64," + base64_str

def measure_image_size(image_path: str):
    img = cv2.imread(image_path, cv2.IMREAD_COLOR)
    height, width, _ = img.shape[:3]
    return height, width

# xmlの形式
# class DataDict:
#     data: str
#     w: int
#     h: int
#     title: str
#     aspect: str     # "fixed" | "variable"

def create_data_dict(img: pathlib.Path, aspect: Literal["fixed", "variable"]):
    data_value = to_base64_value(img)
    h, w = measure_image_size(str(img.absolute()))
    return json.dumps({
        "data": data_value,
        "w": w,
        "h": h,
        "title": img.stem,
        "aspect": aspect
    }, indent = 2)

class XmlFileWriter:
    lines: list[str]
    
    def __init__(self, file_path, initial_str = None) -> None:
        self.file_path = file_path
        self.lines = [initial_str] if initial_str else []
    
    def append(self, value: str):
        self.lines.append(value)
    
    def flush(self):
        with open(self.file_path, 'w', encoding='utf-8') as f:
            f.write('\n'.join(self.lines))

def main(target: pathlib.Path):
    lib_path = __dirname / f'lib/{target.stem}.gen.xml'    # 出力先です。お好みの場所・名前にしてください。
    writer = XmlFileWriter(lib_path)
    writer.append('<mxlibrary>[')
    
    elements = []
    for texture in tqdm(target.glob('*.png')):
        elements.append(create_data_dict(texture, "variable"))    # 縦横比を固定にしたい時は"fixed"に変えてください。
    
    writer.append(
        ",\n".join(elements)
    )
    
    writer.append(']</mxlibrary>')
    writer.flush()

if __name__ == '__main__':
    args = sys.argv[1:]
    if not args:
        raise ValueError("Set the target directory as a first argument.")
    target = pathlib.Path(args[0])
    
    main(target)
```

### 使い方

main.pyと同階層にassetsフォルダがあり、そのフォルダの中にライブラリにしたいpngファイルが存在すると仮定します。

```shell
python main.py ./assets/
```

生成された lib/assets.gen.xml がライブラリファイルです。

## VSCode-drawio にライブラリファイルを追加する

ライブラリファイルの設定をsettings.jsonに追加します。
ライブラリファイルのパスは、絶対パスまたはワークスペースフォルダからのパスでないといけないようです。

> The file path to the library. Must be absolute. You can use ${workspaceFolder}.

そのため、基本的にはワークスペースの設定に追記して利用するのが基本でしょう。

```jsonc:settings.json
{
    "hediet.vscode-drawio.customLibraries": [
        {
            "entryId": "MyLibrary",      // 複数のライブラリファイルをまとめる際に使用するグループ名
            "libName": "MyLibrary - first",      // ライブラリ名
            "file": "${workspaceFolder}/lib/assets.gen.xml"    // ライブラリファイル本体へのパス
        },
        {
            "entryId": "MyLibrary",      // もし異なるライブラリファイルを、同じグループで使用するなら同じidを指定する。
            "libName": "MyLibrary - Second",
            "file": "${workspaceFolder}/lib/assets2.gen.xml"
        }
    ],
    ...
}
```

## まとめ

本記事では vscode-drawio で**png画像ライブラリファイルを作成・追加する方法**を紹介しました。
本家サービスのDraw.ioとは異なり、VSCode-drawioのライブラリについては日本語情報(どころか英語情報も)なかったので、備忘録的にまとめておきました。
誰かの役に立てば幸いです。

## 参考文献

- svgファイルをライブラリにする

https://zenn.dev/capybara_alt/articles/c810cb3dc9cba0

https://zenn.dev/capybara_alt/articles/a3fc0f1eadeca1

- Pythonによるdrawioライブラリ操作パッケージ
    - svgのみ想定っぽい。
    - pngだと型が異なる

https://github.com/capybara-alt/pydrawio

## 付録 - Mincraftのテキスチャーをdrawioライブラリにする

最近Minecraftを初めて、レッドストーン回路等の学習をしている時に、drawioで設計をまとめたりしたいと思い立ったので。

### テキスチャー画像の取得

まずお手元のjarファイルを探します。
私は`.minecraft\versions\1.20.2\1.20.2.jar`でした。
jarファイルを 7zip で展開します。
すると展開したディレクトリの中に、テキスチャーのフォルダがあります。
例: `.minecraft\versions\1.20.2\1.20.2\assets\minecraft\textures`
block, itemのフォルダにpng画像が大量に入っているので、こちらのディレクトリの絶対パスをコピーして、本記事のコードでライブラリファイルを作成します。

aspectは"fixed"がおススメです。
ライブラリファイルを追加して、drawioファイルを作成して開いて、**Gridのサイズを16pxにします**。(注: 画像は16px × 16pxなので)
あら不思議！快適にdrawioでMinecraftのアイコンが使えます！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483769/1926a0ae-d993-0a87-d5d2-cfbc87ac1e63.png)

↑ これは先日作ったトロッコ駅のロジックの整理ファイル

https://x.com/takeMe_py/status/1724340369503166591?s=20