mieki256's diary



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

#1 [python] PySideを使ってGIMPの着色処理っぽいことを再現してみたり

GIMPには、着色(色 → 着色)という機能があって、画像を任意の一色で塗ったような状態に変換することができるのだけど。ソレを Python + PySide を使って再現できないかテストしてみたり。

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

RGB/HSLのQSlider + QSpinBoxを用意して値を連動させる。
かつ、ビットマップ画像をGIMPの「着色」風に変更してみる。

Windows10 x64 + Python 2.7.11 + PySide 1.2.4
"""


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

IMG_FILE = "temp.png"


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()

        # bitmap display label
        im = QImage(IMG_FILE)
        lbl = QLabel("", self)
        lbl.setPixmap(QPixmap.fromImage(im))
        l.addWidget(lbl)
        self.im = im
        self.lbl = lbl
        self.lum_im = self.make_lum_image(im)

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

        # 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.change_pixmap_color(self.lum_im)
            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.change_pixmap_color(self.lum_im)
            self.event_ignore = False

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

    def make_lum_image(self, im):
        """make luminance image."""
        w, h = im.size().width(), im.size().height()
        dst = []
        for y in range(h):
            d = []
            for x in range(w):
                c = im.pixel(x, y)
                r, g, b = qRed(c), qGreen(c), qBlue(c)
                # get luminance
                l = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
                l = max(0, min(l, 255))
                d.append((l, qAlpha(c)))
            dst.append(d)
        return dst

    def change_pixmap_color(self, src):
        """change QPixmap color."""
        b_h, b_s, b_l = self.get_sliders(3)
        w, h = len(src), len(src[0])
        dst = QImage(w, h, QImage.Format_ARGB32)
        c2 = QColor()
        for y in range(h):
            for x in range(w):
                l, a = src[y][x]
                if b_l > 128:
                    l *= (255.0 - b_l) / 127.0
                    l += (255.0 - ((255 - b_l) * 255.0 / 127.0))
                elif b_l < 128:
                    l *= b_l / 128.0
                l = int(l)
                if l < 0:
                    l = 0
                if l > 255:
                    l = 255
                c2.setHsl(b_h, b_s, l, a)
                dst.setPixel(x, y, c2.rgba())
        self.lbl.setPixmap(QPixmap.fromImage(dst))


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


if __name__ == '__main__':
    main()
動作に必要な画像: _temp.png
temp.png



こんな感じだろうか…。

ソースについて少し説明すると、make_lum_image() で、元画像の各ピクセルを調べて輝度を計算して、(輝度、アルファ値) のタプルとして配列に記録。また、change_pixmap_color() で、指定された HSL(色相、彩度、輝度)に従って着色処理をしてる。

ループ内はもう少し最適化できるけど、とりあえずサンプルなので、このぐらいで。

RGBから輝度への変換は、 _YUV - Wikipedia を参考にしてみたり。

PySide のアレコレをメモ。 :

QImage.pixel(x, y) で、指定座標のピクセル値を long値として取得できる。ソレを、qRed(long)、qGreen(long)、qBlue(long)、qAlpha(long) で、RGBA値に変換できる。

_python - How to get RGB values of QPixmap or QImage pixel - Qt, PyQt - Stack Overflow

QImage.setPixel(x, y, long値) で、QImage の指定座標のピクセル値を変更。

GIMPのソースの該当する部分。 :

GIMP 2.8.18 のソース、gimp-2.8.18.tar.bz2 を入手して中身を眺めてみた。おそらくだけど、gimp-2.8.18\app\base\colorize.c のあたりが着色処理、に似た処理なのではないかと。たぶん。自信無いけど。

関係ありそうな箇所だけ抜き出してみたり。
      while (w--)
        {
          lum = (colorize->lum_red_lookup[s[RED]] +
                 colorize->lum_green_lookup[s[GREEN]] +
                 colorize->lum_blue_lookup[s[BLUE]]); /* luminosity */

          if (colorize->lightness > 0)
            {
              lum = (gdouble) lum * (100.0 - colorize->lightness) / 100.0;

              lum += 255 - (100.0 - colorize->lightness) * 255.0 / 100.0;
            }
          else if (colorize->lightness < 0)
            {
              lum = (gdouble) lum * (colorize->lightness + 100.0) / 100.0;
            }

          d[RED]   = colorize->final_red_lookup[lum];
          d[GREEN] = colorize->final_green_lookup[lum];
          d[BLUE]  = colorize->final_blue_lookup[lum];

          if (alpha)
            d[ALPHA] = s[ALPHA];

          s += srcPR->bytes;
          d += destPR->bytes;
        }
lum が、画像から求めた輝度じゃないかなと。colorize->lightness は、ダイアログ上で指定した輝度値で、-100 から +100 の値を取るのだろうと。HSLからRGB値を求めるあたりは事前にテーブルを作って処理してそうな気がするけど自信無し。

#2 [prog] 色選択部分のUIでちょっと悩む

RGB値を指定する部分のUIはどうあるべきかでなんとなく悩み始めたり。

一番シンプルなのは、コレかな…。

color_sel_ui_01.png

でも、一々「,」も入力していくのが面倒臭そう。だったらRGBを分けて入力したほうがいいのかな。

color_sel_ui_02.png

だけど、必ずキーボードから数値を入力させるのもなんだか面倒だなと。マウス操作でも数値を変更できたほうが良さそうな。

color_sel_ui_03.png

しかしこれでは、値を大きく変化させたい時に困りそう。となると…。

color_sel_ui_04.png

でも、これではどの数値がどんな色になるのか分かりづらいかもしれない。色見本相当が並んでるほうがいいのかな。

てな感じで悩み始めて、OS標準の色指定ダイアログや、GIMPの色選択ダイアログを眺めてるけど。何かこのあたり、更にグッドなUIがありそうな気もしてきたけど気がするだけで思いつかず。

一般的には、色相を円にして、彩度と輝度、もしくは明度で三角形を作って、という形がメジャーなのかな…。でもソレ、正確にRGB値を指定できてるわけではないよな…。

#3 [web] IEやMicrosoft Edgeはwebmを再生できないのか

最近、この日記にwebm形式で動画を貼り付けていたけれど。確認してみたら IE や Microsoft Edge では webm が再生できなかったようで。webmに変換する前の元動画は削除しちゃったから、別フォーマットを作ってアップロードし直すのも、ちと厳しい…。

とりあえず mp4 なら対応ブラウザが多いらしいので、今回から mp4 で貼っておこうかなと。

以上、1 日分です。

過去ログ表示

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