Pythonで透過色を指定してデスクトップにマスコットを召喚する

# 2行で要約

Windows + Python ＋ tkinter 環境で wm_attributes("-transparentcolor") の使い方解説
画像の上にテキストを描画する部品の解説

## はじめに

ChatGPT流行っていますね。
チャットAIの進化を感じる度に[某デスクトップマスコット](http://usada.sakura.vg/)のようなものにチャットAIを搭載できないかなと思うのですが、似たようなことを考える方は多いのではないかと思います。

![sakura.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264524/0d322573-1e30-0f0a-710d-335649fac317.png "某デスクトップマスコット")


仕事が忙しすぎるので現実逃避としてPythonでデスクトップマスコットを作るためのネタ集めをしたので記事として残しておきます。



## tkinterの透過画面

Windows + Python ＋ tkinter の環境で作っていきます。

まずは矩形ではないウィンドウの作り方を確認してみました。

メイン画面に、canvasを表示して透過色を設定することで透過色の部分がくり抜かれます。
透明なウィンドウになるのではなく抜かれる動作です。

使いやすいように部品化してみました。


```python:transparent_window.py


import tkinter
from PIL import Image, ImageTk


class TransparentWindow():
    """透過画面"""

    FRAME_OFFSET = -2
    BG_COLOR = "white"

    def __init__(self, main, image, position=(0, 0), size=(0, 0)):
        """コンストラクタ"""

        self.main = main
        main.config(bg=self.BG_COLOR)
        self.image = image
        self.window_position = position
        self.window_size = size

        self.main.geometry(str(self.window_size[0]) + "x" +
                           str(self.window_size[1]) + "+" +
                           str(self.window_position[0]) + "+" +
                           str(self.window_position[1]))

        self.main.wm_overrideredirect(True)
        # 透過表示(くり抜き)色を指定
        self.main.wm_attributes("-transparentcolor",
                                self.BG_COLOR)
        # ウィンドウ全体が透過される
        # self.main.wm_attributes("-alpha", 0.7)

        self.init_canvas(self.main, self.image)

    def init_canvas(self, frame, image):
        """canvas初期化"""

        # canvas作成
        self.canvas = tkinter.Canvas(
            frame,
            width=image.width,
            height=image.height,
            bg=self.BG_COLOR
        )

        # 枠を消すためにマイナス値を指定
        self.canvas.place(x=self.FRAME_OFFSET,
                          y=self.FRAME_OFFSET)

        # クリックイベント
        self.canvas.bind('<Button-1>', self.click_canvas_event)

        # PIL.Image から PhotoImage 生成
        self.photo_image = ImageTk.PhotoImage(image=image)

        # # canvasに画像を表示
        self.canvas.create_image(
            self.photo_image.width() / 2,
            self.photo_image.height() / 2,
            image=self.photo_image)

    def click_canvas_event(self, event):
        """canvasクリックイベント"""

        print("click:(x:" + str(event.x) + ",y=" + str(event.y) + ")")

        self.main.quit()


if __name__ == '__main__':

    # 画像ファイルを開く
    pil_image = Image.open("./character.png")
    # サイズ調整 1/4
    pil_image = pil_image.resize(
        (int(pil_image.width / 4),
         int(pil_image.height / 4)),
        Image.HAMMING)

    # 透過画面表示
    root = tkinter.Tk()

    window_position = (root.winfo_screenwidth() - pil_image.width - 100,
                       root.winfo_screenheight() - pil_image.height - 50)

    TransparentWindow(main=root,
                      image=pil_image,
                      position=window_position,
                      size=(pil_image.width, pil_image.height))
    root.mainloop()




```

tkinterのメイン画面は枠を描画するので、何も考えずに透過色を設定するだけだと枠は消えずに残ってました。
canvasを配置する時にメイン画面の枠の上に配置してあげることで枠も消えるようなので、ちょっと変な感じですがこの方式を採用してます。

背景色が白い画像を使う必要があります。変更したい場合はBG_COLORを変更してください。

クリックすると画面閉じる実装にしてあります。

### 実行例

全世界で大人気のキャラクターを提供する[いらすとや](https://www.irasutoya.com/2020/12/blog-post_86.html)で画像を入手し、実行すると以下のような表示となります。
アルファ値が効かないので少しギザついてみえるので、画像の作り方を注意する必要がありそうです。

![スクリーンショット (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264524/fb63bf22-72e7-e8c5-d5fd-a30ecc6708f8.png)


製品や会社のロゴをスプラッシュ画面として表示するような場合は上記部品を使えばよいと思います。

スプラッシュ画面に利用する場合は自動で閉じるタイマーを追加する必要があると思います。
タイマーは次に記載した部品でも利用しているので参考になるかと思います。


## 複数の画像とテキスト表示

マスコットと会話するために会話用の吹き出しを表示したいので、複数の画像を表示とテキスト描画できるように拡張しました。


```python:talk_balloon.py

import tkinter
from PIL import Image, ImageTk
import random

# 辞書のキー定義
KEY_IMAGE = 'image'
KEY_POSITION = 'position'


class TransparentWindow():
    """透過画面"""

    FRAME_OFFSET = -2
    BG_COLOR = "white"

    def __init__(self, main, items, position=(0, 0), size=(0, 0),
                 text_messages=None, text_position=(0, 0)):
        """コンストラクタ"""

        self.main = main
        main.config(bg=self.BG_COLOR)
        self.items = items
        self.window_position = position
        self.window_size = size
        self.text_messages = text_messages
        self.text_position = text_position

        self.timre_id = None

        self.main.geometry(str(self.window_size[0]) + "x" +
                           str(self.window_size[1]) + "+" +
                           str(self.window_position[0]) + "+" +
                           str(self.window_position[1]))

        self.main.wm_overrideredirect(True)
        # 透過表示(くり抜き)色を指定
        self.main.wm_attributes("-transparentcolor",
                                self.BG_COLOR)
        # ウィンドウ全体が透過される
        # self.main.wm_attributes("-alpha", 0.1)

        self.init_canvas(self.main)

        self.update_canvas()

    def init_canvas(self, frame):
        """canvas初期化"""

        self.photo_images = []

        # canvas作成
        self.canvas = tkinter.Canvas(
            frame,
            width=self.window_size[0],
            height=self.window_size[1],
            bg=self.BG_COLOR
        )

        # 枠を消すためにマイナス値を指定
        self.canvas.place(x=self.FRAME_OFFSET,
                          y=self.FRAME_OFFSET)
        # クリックイベント
        self.canvas.bind('<Button-1>', self.click_canvas_event)

    def click_canvas_event(self, event):
        """canvasクリックイベント"""

        print("click:(x:" + str(event.x) + ",y=" + str(event.y) + ")")

        if self.timre_id:
            self.main.after_cancel(self.timre_id)
            self.timre_id = None

        self.main.quit()

    def update_canvas(self):
        """canvas描画メソッド"""

        # canvasに画像を描画
        for item in self.items:
            photo_image = ImageTk.PhotoImage(image=item[KEY_IMAGE])
            self.photo_images.append(photo_image)

            self.canvas.create_image(
                item[KEY_POSITION][0],
                item[KEY_POSITION][1],
                image=photo_image)

        # canvasにテキストを描画
        if self.text_messages and len(self.text_messages) >= 1:
            index = random.randint(0, len(self.text_messages)-1)
            self.canvas.create_text(
                self.text_position[0], self.text_position[1],
                text=self.text_messages[index])

            # テキストが複数ある場合はタイマーで更新する
            self.timre_id = self.main.after(5000, self.update_canvas)


if __name__ == '__main__':

    # 画像ファイルを開く
    character = Image.open("./character.png")
    # サイズ調整 1/4
    character = character.resize(
        (int(character.width / 4),
         int(character.height / 4)),
        Image.HAMMING)

    balloon = Image.open("./balloon.png")
    # サイズ調整 1/2
    balloon = balloon.resize(
        (int(balloon.width / 2),
         int(balloon.height / 2)),
        Image.HAMMING)

    image_items = []
    image_items.append({KEY_IMAGE: character,
                        KEY_POSITION: ((character.width / 2) + (balloon.width),
                                       character.height / 2)})
    image_items.append({KEY_IMAGE: balloon,
                        KEY_POSITION: (balloon.width / 2, character.height / 2)})

    root = tkinter.Tk()

    window_size = (character.width + balloon.width,
                   max(character.height, balloon.height))

    window_position = (root.winfo_screenwidth() - window_size[0] - 100,
                       root.winfo_screenheight() - window_size[1] - 50)

    # 透過画面表示
    TransparentWindow(main=root,
                      items=image_items,
                      position=window_position,
                      size=window_size,
                      text_messages=["Hellow world",
                                     "こんにちは世界",
                                     "hoge",
                                     "ほげ"],
                      text_position=(60, 75))
    root.mainloop()



```


画像やテキストを渡すためにパラメータを見直しました。
あとテキストを一定時間で更新するタイマーを実装しています。



### 実行例

吹き出しも[素材サイト](https://fukidesign.com/terms/ "フキダシデザイン")から探してきました。
フキダシデザインさんという色の指定してファイル保存できるので便利なサイトですね。
普通にプレゼン資料作るときに活用したい。

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264524/66106974-0497-1749-2bcc-03d98dfd1886.gif)



## おわりに

見た目だけで力尽きたので今回はここまで。
次回があればREST APIでチャットAIの呼び出しに対応する記事になるはず。