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値を求めるあたりは事前にテーブルを作って処理してそうな気がするけど自信無し。

以上です。

過去ログ表示

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