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値を求めるあたりは事前にテーブルを作って処理してそうな気がするけど自信無し。
[ ツッコむ ]
#2 [prog] 色選択部分のUIでちょっと悩む
RGB値を指定する部分のUIはどうあるべきかでなんとなく悩み始めたり。
一番シンプルなのは、コレかな…。

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

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

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

でも、これではどの数値がどんな色になるのか分かりづらいかもしれない。色見本相当が並んでるほうがいいのかな。
てな感じで悩み始めて、OS標準の色指定ダイアログや、GIMPの色選択ダイアログを眺めてるけど。何かこのあたり、更にグッドなUIがありそうな気もしてきたけど気がするだけで思いつかず。
一般的には、色相を円にして、彩度と輝度、もしくは明度で三角形を作って、という形がメジャーなのかな…。でもソレ、正確にRGB値を指定できてるわけではないよな…。
一番シンプルなのは、コレかな…。
でも、一々「,」も入力していくのが面倒臭そう。だったらRGBを分けて入力したほうがいいのかな。

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

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

でも、これではどの数値がどんな色になるのか分かりづらいかもしれない。色見本相当が並んでるほうがいいのかな。
てな感じで悩み始めて、OS標準の色指定ダイアログや、GIMPの色選択ダイアログを眺めてるけど。何かこのあたり、更にグッドなUIがありそうな気もしてきたけど気がするだけで思いつかず。
一般的には、色相を円にして、彩度と輝度、もしくは明度で三角形を作って、という形がメジャーなのかな…。でもソレ、正確にRGB値を指定できてるわけではないよな…。
[ ツッコむ ]
#3 [web] IEやMicrosoft Edgeはwebmを再生できないのか
最近、この日記にwebm形式で動画を貼り付けていたけれど。確認してみたら IE や Microsoft Edge では webm が再生できなかったようで。webmに変換する前の元動画は削除しちゃったから、別フォーマットを作ってアップロードし直すのも、ちと厳しい…。
とりあえず mp4 なら対応ブラウザが多いらしいので、今回から mp4 で貼っておこうかなと。
とりあえず mp4 なら対応ブラウザが多いらしいので、今回から mp4 で貼っておこうかなと。
[ ツッコむ ]
以上、1 日分です。