mieki256's diary



2024/05/22(水) [n年前の日記]

#1 [python] Pythonで色取得ツールを作れそうか試してみた

Python + tkinter を使って色取得ツール? カラーピッカー? を作れそうか試してみた。環境は、Windows10 x64 22H2 + Python 3.10.10 64bit。

OpenGL関係のプログラムを書いていた際に、RGB値を 0.0 - 1.0 で指定するのが面倒で…。一般的な色管理ツール/カラーピッカーは、RGB値を 0 - 255 で扱うので、変換が面倒で…。あらかじめ 0.0 - 1.0 で扱える色管理ツールがないかと探してみたけど、これが全然見つからない。自分で作るしかないのかなあ、と…。

PyAutoGUIをインストール :

デスクトップ上の一点を指定して色を取得できるライブラリ(モジュール)は何があるのかとググってみたところ、PyAutoGUI が使えるらしいと知った。

pyautogui.pixel(x, y)、もしくは、pyautogui.screenshot().getpixel((x, y)) で、指定座標の (r, g, b) を取得できるらしい。ちなみに、デスクトップ全体のスクリーンショットを取ってから、該当位置のドットを調べる仕組みらしい…。

また、この PyAutoGUI を使うと、マウスカーソル座標も常時取得できる。x, y = pyautogui.position() で得られる模様。

ひとまず、PyAutoGUI をインストール。
pip install PyAutoGUI

PyAutoGUI 0.9.54 がインストールされた。

pyperclipをインストール :

得られたRGB値を、OpenGL用のRGB値に ―― 各RGB値が 0.0 - 1.0 の値で示される状態に変換して、クリップボードに入れたい。Python からクリップボードを扱えるライブラリがないかググったところ、pyperclip というライブラリが使えると知った。

pyperclip.copy("hoge") で、クリップボードに "hoge" が入る。

このライブラリもインストール。
pip install pyperclip

pyperclip 1.8.2 がインストールされた。

※ 2024/05/28追記。もし、tkinter を使っているなら、tkinter自体にクリップボードを操作する機能があるので、pyperclip を別途インストールして使わなくても済む。

常時色を取得する版を書いてみた :

一定の時間間隔でtkinterが関数を呼んで、常時、マウスカーソル位置と、その位置の色を取得する版を書いてみた。以下のサンプルを参考にさせてもらいました。ありがたや。

_Pythonでマウス座標をリアルタイムで取得して表示するGUIアプリを作る

Windows10 x64 22H2 + Python 3.10.10 64bit + PyAutoGUI 0.9.54 + pyperclip 1.8.2 で動作確認。

以下のような感じになった。
  • Cキー、またはスペースキー、またはEnterキーを叩くと、そのタイミングでマウスカーソル位置の色をクリップボードにコピーする。
  • ESCキー、またはQキーで終了。

02_get_color1_py_ss.gif

下のほうに、クリップボードの内容をリアルタイム表示する GhostBoard というアプリのウインドウも出しているけれど、値を取得(クリップボードにコピー)するたびに、ちゃんとクリップボードの内容が変わってることも分かるかと。

ソースは以下。

_02_get_color1.py
import pyautogui as ag
import tkinter as tk
import pyperclip


DELAYMS = 100  # milliseconds


def display_pos_rgb(x, y, rgb):
    r = float(rgb[0]) / 255.0
    g = float(rgb[1]) / 255.0
    b = float(rgb[2]) / 255.0
    rgbftext = "%.4f, %.4f, %.4f" % (r, g, b)
    root.poslabel.config(text=f"Pos : {x}, {y}")
    root.rgblabel.config(text=f"RGB : {rgb}")
    root.rgbflabel.config(text=rgbftext)
    root.collabel.config(bg="#%02x%02x%02x" % rgb)
    return rgbftext


def set_clipboard(e):
    global cnt
    x, y = ag.position()
    rgb = ag.pixel(x, y)
    rgbftext = display_pos_rgb(x, y, rgb)

    # to clipboard
    pyperclip.copy(rgbftext)

    root.reslabel.config(text="copied!")
    root.reslabel.after(750, clear_msg)


def clear_msg():
    root.reslabel.config(text="")


def update():
    global cnt
    x, y = ag.position()  # get mouse position
    rgb = ag.pixel(x, y)  # get color
    display_pos_rgb(x, y, rgb)
    root.after(DELAYMS, update)


def close(e):
    root.destroy()


# initialize tkinter
root = tk.Tk()
root.attributes("-topmost", True)

root.title("Color picker")
# root.geometry("320x240")

root.reslabel = tk.Label(text="", font=("Arial", 16))
root.poslabel = tk.Label(text="Pos : ")
root.rgblabel = tk.Label(text="RGB : ")
root.rgbflabel = tk.Label(text="")
root.msglabel = tk.Label(text="C / Enter / Space key : get color")

root.collabel = tk.Label(
    text="", width=15, height=4, bg="green", borderwidth=1, relief=tk.SOLID
)

root.reslabel.pack()
root.poslabel.pack()
root.rgblabel.pack()
root.rgbflabel.pack()
root.msglabel.pack(padx=10)
root.collabel.pack(padx=10, pady=10)

# bind key
root.bind("<Escape>", close)
root.bind("<q>", close)

root.bind("<c>", set_clipboard)
root.bind("<space>", set_clipboard)
root.bind("<Return>", set_clipboard)

root.after(DELAYMS, update)

root.mainloop()

本当はマウスクリックで色を取得するようにしたかったのだけど、tkinter のウインドウ外でマウスクリックされたかどうかを判別する方法が分からなくて…。仕方なく、tkinter のアプリウインドウがアクティブになってる時に、キーが押されたかどうかを判別して、色を取得するようにしてみた。

余談。tkinter でアプリウインドウを最前面にする時は、.attributes("-topmost", True) を指定すればいいらしい。
root.attributes("-topmost", True)

また、.after(ミリ秒, 関数名) を使うことで、指定時間後に関数を呼ぶことができる。呼ばれた関数の中で、再度 .after() を呼べば、一定の時間間隔で処理をすることができるようになる。

マウスクリックで取得する版を書いてみた :

tkinter のウインドウ外でマウスクリックを検出する方法が無いか Microsoft Copilot に尋ねてみたら、「pynputを使えばできらあ」と言ってきたのでググってみた。マウスカーソルが移動してるかどうかを検出したり、マウスボタンが押されたかどうかを検出できるらしい。とりあえずインストール。

pip install pynput

pynput 1.7.6 がインストールされた。

pynput を使って、マウスクリックで色を取得するようにしてみた。ボタンを押してから、デスクトップ上のどこかしらをクリックすると、その位置の色を取得して、クリップボードにRGB値が入る。

03_get_color2_py_ss.gif

_03_get_color2.py
import tkinter as tk
import pyautogui
from pynput import mouse
import pyperclip

DEF_MSG = "Get color"
listener = None
getting = False


def display_rgb(x, y):
    global rgblbl, rgbflbl, collbl
    r, g, b = pyautogui.screenshot().getpixel((x, y))
    rf = float(r) / 255.0
    gf = float(g) / 255.0
    bf = float(b) / 255.0
    rgbftext = "%.4f, %.4f, %.4f" % (rf, gf, bf)
    rgblbl.config(text="RGB : %d, %d, %d" % (r, g, b))
    rgbflbl.config(text=rgbftext)
    collbl.config(bg="#%02x%02x%02x" % (r, g, b))
    return rgbftext


def on_move(x, y):
    display_rgb(x, y)


def on_click(x, y, button, pressed):
    global listener, getting, btn, root
    if pressed:
        rgbftext = display_rgb(x, y)

        # to clipboard
        pyperclip.copy(rgbftext)

        btn.config(text="Copied !")
        listener.stop()
        getting = False
        root.after(1000, set_default_msg)


def set_default_msg():
    global btn
    btn.config(text=DEF_MSG)


def start_get_color():
    global listener, getting, btn, rgblbl
    listener = mouse.Listener(on_click=on_click, on_move=on_move)
    listener.start()
    btn.config(text="Please click desktop")
    getting = True


def on_close():
    global listener, getting
    if getting:
        listener.stop()
    root.destroy()


def close(e):
    global listener, getting
    if getting:
        listener.stop()
    root.destroy()


# ----------------------------------------
# main

# init tkinter
root = tk.Tk()
root.attributes("-topmost", True)
root.title("Color picker")
# root.geometry("260x200")

btn = tk.Button(text=DEF_MSG, command=start_get_color, font=("Arial", 13), width=25)
rgblbl = tk.Label(root, text="RGB : 0, 0, 0")
rgbflbl = tk.Label(root, text="0.0000, 0.0000, 0.0000")
collbl = tk.Label(
    root, text="", width=10, height=4, bg="black", borderwidth=1, relief=tk.SOLID
)

btn.pack(padx=8, pady=4)
rgblbl.pack()
rgbflbl.pack()
collbl.pack(padx=8, pady=8)

root.bind("<Escape>", close)
root.bind("<q>", close)

root.protocol("WM_DELETE_WINDOW", on_close)
root.mainloop()

一見上手くいったように思えたのだけど、マウスクリックで色を取得するのはよろしくないことに気づいた。マウスクリックした場所に別のアプリがあったりして、そのアプリのその位置をクリックすることで何かしらの処理が行われてしまう場合は、ちょっとマズいことになる。例えば、WebブラウザでWebページを開いていて、該当位置をクリックして色を取得したら、たまたまそこにリンクが貼ってあって、無意味にリンクを開いてしまったり等々…。

巷のカラーピッカーツールのように、マウスクリックではなく、マウスボタンのリリースで ―― マウスボタンを離したタイミングで色を取得したほうがいいのかもしれない。

マウスボタンを離したタイミングで色を取得する版を書いた :

マウスクリックで色取得するのはよろしくないことに気づいたので、マウスボタンを離したタイミングで色取得する版を書いてみた。アプリウインドウの中の任意の場所をマウスでドラッグして、色を取得したい場所でマウスボタンを離せば、その位置の色を取得できる。

04_get_color3_py_ss.gif

_04_get_color3.py
import tkinter as tk
import pyautogui
from pynput import mouse
import pyperclip

DEF_MSG = "Drag here. Get color"
listener = None
getting = False


def get_rgb(x, y):
    global rgblbl, rgbflbl, collbl
    r, g, b = pyautogui.screenshot().getpixel((x, y))
    rf = float(r) / 255.0
    gf = float(g) / 255.0
    bf = float(b) / 255.0
    rgbftext = "%.4f, %.4f, %.4f" % (rf, gf, bf)
    rgblbl.config(text="RGB : %d, %d, %d" % (r, g, b))
    rgbflbl.config(text=rgbftext)
    collbl.config(bg="#%02x%02x%02x" % (r, g, b))
    return rgbftext


def on_move(x, y):
    get_rgb(x, y)


def on_click(x, y, button, pressed):
    global listener, getting, btn, root
    if button == mouse.Button.left:
        if not pressed:
            # button release. to clipboard
            pyperclip.copy(get_rgb(x, y))

            btn.config(text="Copied !")
            listener.stop()
            getting = False
            root.after(1000, set_default_msg)


def set_default_msg():
    global btn
    btn.config(text=DEF_MSG)


def start_get_color(e):
    global listener, getting, btn, rgblbl
    listener = mouse.Listener(on_click=on_click, on_move=on_move)
    listener.start()
    btn.config(text="Please release on desktop")
    getting = True


def close_window(e):
    global getting
    if getting:
        return
    root.destroy()


# ----------------------------------------
# main

# init tkinter
root = tk.Tk()
root.title("Color picker")
# root.geometry("260x200")
root.attributes("-topmost", True)

btn = tk.Label(
    text=DEF_MSG, font=("Arial", 13), width=25, borderwidth=3, relief=tk.GROOVE
)
rgblbl = tk.Label(root, text="RGB : 0, 0, 0")
rgbflbl = tk.Label(root, text="0.0000, 0.0000, 0.0000")
collbl = tk.Label(
    root, text="", width=20, height=4, bg="black", borderwidth=1, relief=tk.SOLID
)

btn.pack(padx=8, pady=8)
rgblbl.pack()
rgbflbl.pack()
collbl.pack(padx=8, pady=8)

btn.bind("<ButtonPress-1>", start_get_color)

root.bind("<Escape>", close_window)
root.bind("q", close_window)

root.mainloop()

これで、かなりそれらしい処理になってくれた気がする。

この後は、以下のような処理を追加してみたいなと…。
  • 色を取得するたびにリストに追加して、取得した色の履歴を並べる。
  • 0 - 255 のRGB値と、0.0 - 1.0 のRGB値(OpenGL用)を相互に変換できるようにする。
  • 任意の色指定形式でクリップボードにコピーする。

でも、車輪の再発明のような気もする…。巷で公開配布されてる色管理ツールが、0.0 - 1.0 の値でRGB値をクリップボードにコピーする機能を持ってたらそれで済んでしまうのだけどな…。

以上です。

過去ログ表示

Prev - 2024/05 - Next
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project