mieki256's diary



2016/11/01(火) [n年前の日記]

#2 [python] PySideとQColorとHSL

PySideを使ってRGB指定もしくはHSL(色相、彩度、輝度)指定で色を選択できるかテスト。

こんな感じになった。

_convert_hsl.py
u"""
QSliderとQSpinBoxを1つのWidgetにまとめて並べてみるサンプル.

RGB/HSLのQSlider + QSpinBoxを用意して値を連動させる。結果を色で表示する。

Windows10 x64 + Python 2.7.11 + PySide 1.2.4
"""


import sys
from PySide.QtCore import *  # NOQA
from PySide.QtGui import *  # NOQA


class LabelSliderSpinBox(QWidget):

    """Slider and SpinBox."""

    # Signalを用意
    valueChanged = Signal(int)

    def __init__(self, text, parent=None):
        """init."""
        super(LabelSliderSpinBox, self).__init__(parent)
        # self.setContentsMargins(0, 0, 0, 0)
        hl = QHBoxLayout()
        hl.setContentsMargins(0, 0, 0, 0)
        # hl.setSpacing(0)
        self.lbl = QLabel(text, self)
        self.sld = QSlider(Qt.Horizontal, self)
        self.spb = QSpinBox(self)
        hl.addWidget(self.lbl)
        hl.addWidget(self.sld)
        hl.addWidget(self.spb)
        self.setLayout(hl)
        self.sld.valueChanged[int].connect(self.changed_slider_value)
        self.spb.valueChanged[int].connect(self.changed_spinbox_value)

        # 用意したシグナルとスロットを関連付ける
        self.valueChanged[int].connect(self.changedValue)

    def setText(self, text):
        """set QLabel text."""
        self.lbl.setText(text)

    def setRange(self, start_v, end_v):
        """set range QSlider and QSpinBox."""
        self.sld.setRange(start_v, end_v)
        self.spb.setRange(start_v, end_v)

    def setValue(self, value):
        """set value to QSlider and QSpinBox."""
        self.sld.setValue(value)
        self.spb.setValue(value)

    def value(self):
        """get value."""
        return self.spb.value()

    def changed_slider_value(self, n):
        """changed slider value."""
        self.spb.setValue(n)

    def changed_spinbox_value(self, n):
        """changed spinbox value."""
        self.sld.setValue(n)
        self.valueChanged.emit(n)  # 値が変わったのでシグナルを発行

    # スロットを用意する
    @Slot(int)
    def changedValue(self, value):
        """changed slider or spinbox value."""
        pass
        # print("value = %d" % value)


class MyWidget(QWidget):

    """My Widget."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(MyWidget, self).__init__(*argv, **keywords)
        self.setWindowTitle("Color Slider")

        self.event_ignore = False

        l = QVBoxLayout()

        # color display button
        btn = QPushButton()
        btn.setFixedSize(64, 64)

        # set button background-color
        btn.setStyleSheet("QWidget { background-color: %s }"
                          % QColor(128, 128, 128).name())

        l.addWidget(btn)
        btn.clicked.connect(self.show_color_dialog)
        self.btn = btn

        # sliders
        data = [
            ("R", 0, 255, 128), ("G", 0, 255, 128), ("B", 0, 255, 128),
            ("H", 0, 360, 180), ("S", 0, 255, 128), ("L", 0, 255, 128)]
        self.sliders = []
        for (i, d) in enumerate(data):
            text, start_v, end_v, init_v = d
            w = LabelSliderSpinBox(text, self)
            w.setRange(start_v, end_v)
            w.setValue(init_v)
            l.addWidget(w)
            self.sliders.append(w)
            if i < 3:
                w.valueChanged.connect(self.changed_rgb)
            else:
                w.valueChanged.connect(self.changed_hsl)

        self.setLayout(l)

    def update_rgb_disp(self, col):
        """update RGB disp button."""
        # set background-color
        self.btn.setStyleSheet("QWidget { background-color: %s }"
                               % col.name())

    def set_sliders_rgb(self, col):
        """set RGB slider value."""
        r, g, b = col.red(), col.green(), col.blue()
        self.set_sliders(0, (r, g, b))

    def set_sliders_hsl(self, col):
        """set HSL slider value."""
        h, s, l = col.hslHue(), col.hslSaturation(), col.lightness()
        self.set_sliders(3, (h, s, l))

    def set_sliders(self, idx, v):
        """set 3 slider value."""
        for i in range(3):
            if self.sliders[idx + i].value() != v[i]:
                self.sliders[idx + i].setValue(v[i])

    def get_sliders(self, idx):
        """get 3 slider value."""
        v = []
        for i in range(3):
            v.append(self.sliders[idx + i].value())
        return tuple(v)

    def changed_rgb(self, _):
        """changed RGB value."""
        if not self.event_ignore:
            self.event_ignore = True
            r, g, b = self.get_sliders(0)
            col = QColor(r, g, b)
            self.set_sliders_hsl(col)
            self.update_rgb_disp(col)
            self.event_ignore = False

    def changed_hsl(self, _):
        """changed HSL value."""
        if not self.event_ignore:
            self.event_ignore = True
            h, s, l = self.get_sliders(3)
            col = QColor()
            col.setHsl(h, s, l)
            self.set_sliders_rgb(col)
            self.update_rgb_disp(col)
            self.event_ignore = False

    def show_color_dialog(self):
        """open QColorDialog."""
        col = QColorDialog.getColor()
        if col.isValid():
            self.set_sliders_rgb(col)


def main():
    """Main."""
    app = QApplication(sys.argv)
    w = MyWidget()
    w.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()



RGBとHSLの変換は、こんな感じでイケそうだなと。

RGBからHSLの変換について。 :

PySideには、 _QColor という、色情報を管理できるクラスがあるのだけど。この QColor には、RGB値とHSL値の変換機能も含まれているようで、わざわざ自分で変換処理を書かなくても済んでしまった。ありがたや。

例えば…。col = QColor(r, g, b) で生成した後、以下のような記述でHSLが取り出せる。
  • h = col.hslHue() で色相。
  • s = col.hslSaturation() で彩度。
  • l = col.lightness() で輝度。
  • あるいは、h, s, l, a = col.getHsl() でも取り出せるかもしれないが試してない。
色相は、0〜360の値。彩度と輝度は、0〜255の値が返ってくるようで。たぶん。おそらく。ちょっと自信無いけど。一応、得られた値を print で出力した感じではそのように見えた。

HSLからRGBに変換することもできる。col = QColor() で生成した後、col.setHsl(h, s, l) でHSLを設定してやれば、以下のような記述でRGB値が取り出せる。
  • r = col.red() で赤成分。
  • g = col.green() で緑成分。
  • b = col.blue() で青成分。
  • あるいは、r, g, b, a = col.getRgb() でも取り出せるかもしれないがどうなんだろう。
RGB値は、それぞれ 0〜255の値を取る。

整数ではなく、小数点で得ることもできる模様。その場合、getRgbF() や getHslF() のように、最後にFがついた関数を呼ぶ。返り値は、0.0〜1.0、とドキュメントには書いてあるように見える。

そもそも Python 自体に、 _colorsys なるその手の処理をしてくれるモジュールがあるらしい。今回は使ってないけれど。Python は便利だなあ…。

ラベルとスライダーとスピンボックスをまとめた。 :

今回、管理をしやすくするために、QLabel(ラベル)と、QSlider(スライダー)と、QSpinBox(スピンボックス)をまとめたウィジェットを作って配置してみた。

lbl_slider_spinbox.png

QWidget に _QHBoxLayout(横に並べるレイアウト) を設定して、中に _QLabel_QSlider_QSpinBox を並べて管理、みたいな。

更に、「QSlider の値が変わったら QSpinBox も同じ値に変更」「QSpinBox の値が変わったら QSlider も同じ値に変更」という処理も入れた。これで、QSlider と QSpinBox が連動して動く。QSlider、QSpinBox には valueChanged というシグナル?が用意されていて、値が変わったらそのシグナルが発生することになっているので、ソレを利用した。

しかし、このままだと、QSlider もしくは QSpinBox の値が変わったことを親Widgetに伝える術が無くなるので(valueChanged シグナルを既に使っているので)、別途シグナルを設定して、QSlider もしくは QSpinBox の値が変わったらそのシグナルを発生するようにしてみたり。

class LabelSliderSpinBox(QWidget):

    # Signalを用意
    valueChanged = Signal(int)

    def __init__(self, text, parent=None):

        (中略)

        # 用意したシグナルとスロットを関連付ける
        self.valueChanged[int].connect(self.changedValue)

    (中略)

    def changed_spinbox_value(self, n):
        self.sld.setValue(n)
        self.valueChanged.emit(n)  # 値が変わったのでシグナルを発行

    # スロットを用意する
    @Slot(int)
    def changedValue(self, value):
        print("value = %d" % value)
親Widget側では、「子Widget の持ってる値が変化したら valueChanged というシグナルを子Widgetが出してくれるはずだから、そのシグナルが来た時はこの関数を呼ぶ」的な設定をしてやればいい。
    w.valueChanged.connect(self.changed_rgb)

シグナルが出続ける問題にまた遭遇。 :

今回も、よく考えないでソースを書いてたら、無限ループのような状態になってしまった…。
  1. HSLのスライダーもしくはスピンボックスの値が、ユーザ操作で変更される。
  2. 「HSLの値が変わったぞ!」のイベント発生。
  3. HSLからRGBに変換して値を求め、RGBスライダーの値を変更。
  4. 「RGBの値が変わったぞ!」のイベント発生。
  5. RGBからHSLに変換して値を求め、HSLスライダーの値を変更。
  6. 2に戻る。無限ループ状態に陥る。
とりあえずフラグを設けて、他のイベントによる処理がまだ終わってないなら、その後イベントが発生しても処理をスキップするようにしてみたり。self.event_ignore がフラグ用の変数。

ボタンの背景色を設定。 :

QPushButton への背景色を任意の色にすることもできるらしい。
        btn.setStyleSheet("QWidget { background-color: %s }"
                          % QColor(128, 128, 128).name())
QWidget もスタイルシートを持ってるらしいので、そこを弄れば目的が果たせるようで。

ていうか。Qtってスタイルシートで見た目を設定できたとは。知らなかった…。

以上です。

過去ログ表示

Prev - 2016/11 - 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

カテゴリで表示

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


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

Powered by hns-2.19.6, HyperNikkiSystem Project