2016/11/06(日) [n年前の日記]
#1 [python] PySideを使ってGIMPの着色処理っぽいことを再現してみたり
GIMPには、着色(色 → 着色)という機能があって、画像を任意の一色で塗ったような状態に変換することができるのだけど。ソレを Python + PySide を使って再現できないかテストしてみたり。
_convert_hsl2.py
こんな感じだろうか…。
ソースについて少し説明すると、make_lum_image() で、元画像の各ピクセルを調べて輝度を計算して、(輝度、アルファ値) のタプルとして配列に記録。また、change_pixmap_color() で、指定された HSL(色相、彩度、輝度)に従って着色処理をしてる。
ループ内はもう少し最適化できるけど、とりあえずサンプルなので、このぐらいで。
RGBから輝度への変換は、 _YUV - Wikipedia を参考にしてみたり。
_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
こんな感じだろうか…。
ソースについて少し説明すると、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 の指定座標のピクセル値を変更。
_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値を求めるあたりは事前にテーブルを作って処理してそうな気がするけど自信無し。
[ ツッコむ ]
以上です。