mieki256's diary



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

#1 [cg_tools] Fotografixを試用してみたり

最近、GIMPの起動時間の遅さにちと嫌気が差していて。比較的軽量で、起動時間もそれほどかからない画像編集ソフトを探していて、今のところ以下のソフトをちょこちょこ試用していたり。ちなみに環境は Windows10 x64。

_Vector 新着ソフトレビュー 「PixBuilder Studio」 - レイヤ/マスク機能を搭載し、アイコン編集にも対応した、軽快な画像編集ソフト
_JTrim - 窓の杜ライブラリ
_PhotoFiltre 7 - k本的に無料ソフト・フリーソフト
_高機能で軽量な画像編集ソフト「Hornil StylePix」

更に、ググっていたら Fotografix なるソフトもあると知り。それもまた軽量らしいので試用してみたり。バージョンは 1.5。

_Fotografix - Madhavan Lakshminarayanan
_Fotografix Portable | PortableApps.com - Portable software for USB, portable and cloud drives
_インストール不要!持ち運び可能な画像編集ツール「Fotografix」*二十歳街道まっしぐら(FC2ブログ時代)

とりあえず、少し触ってみた感想だけど…。トリミング作業に関しては、ちと難があるようで。

例えば、大きな画像を640x480にしたいと思った際は、以下のような流れになるだろうけど。
  1. 縦横比を4:3に設定して矩形で選択。
  2. 切り抜き(トリミング)。
  3. イメージサイズを640x480にリサイズ(再サンプリング)。

Fotografix 1.5 には、以下のような問題が。 後者は PixBuilder Studio でも起きているので、どうやらこの手のソフトを作る際にはそのあたり何か罠でもあるのかなと思えてきたり。イイ感じの仕様にするためには、何か一工夫が必要になるのかもしれない。

ちなみに起動はかなり早かった。一瞬で起動する。その分、なんだか惜しい。

#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ってスタイルシートで見た目を設定できたとは。知らなかった…。

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

#1 [windows] Windows 10 Anniversary Updateを適用してみたり

メインPC上ではWindows10 x64が動いてるのだけど。なんとなく Windows 10 Anniversary Update(以下アニバと表記)を適用してみたり。というのも、親父さんのPCが数日前にアニバ適用状態になって、さほど問題なく動いてることを確認できたので、せっかくだから自分の環境もアップデートしてみたいと思えてきたというか。

もっとも、自分も一応、少し前に一度適用してみたことはあって。しかしその際は、Illusion製の古い3Dゲームが ―― Unityを使わずに独自エンジンで動いてた時期のゲームタイトルが、めちゃくちゃ低いフレームレートになって紙芝居状態になったので、なんだか怖くなって元に戻してしまったわけで。

ただ、その後、「各ゲームのフォルダに、古い d3d9.dll をコピーしてやれば状況が改善する」という話を見かけて。だったら試してみようかなと。

アップデートの仕方は…。巷の解説ページによると、設定 → Windows Update、等を辿っていけばアップデートに辿り着けるようだけど、自分の環境は一度アップデートして元に戻したせいか該当項目が見当たらず。なので、 _Windows 10 の更新履歴 - Windows Help のページで、「Anniversary Updateを今すぐ入手」をクリックして、おそらくは「Windows10アップグレードアシスタント」をDL・実行して作業を進めたり。

時間は…。3〜4時間ぐらいかかったような気がする。途中で何回か自動で再起動していた。ちなみに、ハードウェアは、Core i5-2500(3.0GHz、4コア)、RAM 8GB、HDD 7200rpm Cache 32MBの環境。

アップデート終了。 :

アップデート終了後は、前回同様、すんなり起動してくれた。助かった。

もっとも、いくつか設定が初期化されていたようで。自分はキーボードのCapsLockをCtrlに設定してたので、そのあたりの設定がクリアされてちょっと戸惑った。 _Change Key で設定し直したら反映してくれたけど。

また、「高速スタートアップ」も有効になっていたので、無効に設定し直し。

Illusion製ゲームは、やっぱり紙芝居状態になった。Windows 10 Anniversary Update は、前回アップデートを試した時と、何も変わってないっぽい。ただ、Win10_SysWOW64_20160907_10.0.10586.589 なるバージョンの d3d9.dll を、ゲームが入ってるフォルダにコピーしたら、フレームレートは改善された。このあたり、どうやら環境によって相性のいい d3d9.dll が違ってくる、と、 _ジンコウガクエンシリーズ part225 [無断転載禁止]@bbspink.com では言われてるようで。

_【GPU】 GeForce 372.90 ドライバ 不具合情報 : ニッチなPCゲーマーの環境構築 では、
この件に関してNVIDIAは「Win10アニバで仕様が変わってしまいました。我々は解決策を見つけるために、マイクロソフトと協力しています」と、フォーラム上で語っております。

【GPU】 GeForce 372.90 ドライバ 不具合情報 : ニッチなPCゲーマーの環境構築 より

という話があるので…。ゲーム関係は、アニバにすることで不具合が起きてしまう時もある、てのは間違いないらしい。

休止状態から復帰するとロック画面が出てきてしまう。 :

休止状態から復帰すると、ロック画面が出てきてしまう。ロック画面を出さないように設定していたと思ったけど、設定がクリアされちゃったのだな。

さて、コレってどこで設定したんだっけか…。

既定のブラウザが設定できない。 :

設定 → システム → 既定のアプリ、で、既定のWebブラウザーを Firefox もしくは Google Chrome にしたいのだけど、該当画面で設定を変えてみても反映されない不具合に遭遇。

コレって Microsoft の嫌がらせというか、姑息さを感じるレベルの Microsoft Edge の普及活動、なのだろうか。それとも何か技術的な問題が絡んでるのだろうか。

ちなみに、既定のメーラー(メールソフト)については、Thunderbird にすんなり切り替えられた。

既定ブラウザに変更する方法は…。
  1. 該当画面の下のほうに「アプリによって既定値を設定する」という項目があるのでソレを選ぶ。
  2. 「コントロール パネル\プログラム\既定のプログラム\既定のプログラムを設定する」が開く。
  3. 左側で Firefox もしくは Google Chrome を選ぶ。
  4. 下のほうにある、「既定でこのプログラムで開く項目を選択する」を選ぶ。
  5. HTTPとか HTTPS とか .html とかそのあたりにチェックを入れて、右下の「保存」をクリック。
要するに、Firefox (or Google chrome) で扱える拡張子のファイルは、全部 Firefox (or Google Chrome)に割り当てる、てな設定にする。

これで、設定 → システム → 既定のアプリ、の画面上でも設定が変わってくれた。ような気がする。たぶん。

Cドライブの容量が少なくなってしまった。 :

残りの空き容量が心許ない。仕方ないので、Cドライブ上の「ドキュメント」の中に入ってる、「ピクチャ」「ミュージック」「ビデオ」その他のフォルダをDドライブに引っ越すことに。

  1. 一旦、それらフォルダの中身を、Dドライブの任意のフォルダに移動しておく。
  2. Dドライブ内の管理しやすい場所に、Pictures、Music、Videos フォルダを作成。
  3. スタートボタン → 左側の「個人用フォルダ」アイコンをクリック。C:\Users\ユーザアカウント名\ が開かれる、はず。
  4. 「ピクチャ」を右クリック → プロパティ → 場所。
  5. 「標準に戻す」をクリック。一旦デフォルト設定にする。
  6. 「移動」をクリックして、先ほどDドライブに作った Pictures を選ぶ。これで、「ピクチャ」がDドライブに移動された。
  7. 最初にDドライブに移動していたフォルダやファイルを、Dドライブの Pictures内に移動。
  8. ミュージック、ビデオ、に対しても同様の作業をする。

このままだと、自分が把握し切れてないアプリ等で「○○フォルダが無くなってるよ!」とか文句を言われるかもしれない、ので、シンボリックリンクを作成して、Dドライブ内の各フォルダが、Cドライブ内にもあるように見せかけておいた。


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

#1 [windows] Windows10 Anniversary Update後の設定

ちょこちょこ設定。

休止状態から復帰するとロック画面が出てきてしまう問題。 :

休止状態から復帰した際はロック画面を出さないようにしたい。そのように設定しておけば…。
  • スリープや休止状態から正常に復帰できた時は、デスクトップ画面が表示される。
  • 復帰に失敗して再起動した時は、ロック画面(サインイン画面)が表示される。
てな状態になるので、正常復帰できたのか、それとも失敗したのかが、見た目で分かりやすいわけで。

でも、どこで設定すればよかったのだったか…。

_Windows 10 anniversary Updateを適用後ロック画面を非表示にできない【Ver1607】 - マイクロソフト コミュニティ
_Windows10でスリープからの復帰時にロック画面にせず、すぐにデスクトップを表示する方法

アニバで、今までとは違う場所に設定項目が移動されたらしい?

設定 → アカウント → サインインオプション → サインインを求める、を、「表示しない」にしてみた。

この状態で再起動してみたところ、OS起動時にはサインイン画面が出てくるし、休止状態からの復帰時はロック画面が出てこないようになった。と思う。たぶん。

デフラグをかけた。 :

_Auslogics Disk Defrag を起動してみたら、システムドライブが断片化しまくって真っ赤になっていたので、一応デフラグを。断片化したファイルだけを最適化するデフラグをかけてから、時間がかかるとされてるデフラグもかけてみたり。5時間ほどかかった…。

MFTもあちこちに散らばってたようなので、 _UltraDefrag を使って最適化してみたけど。Windows10はサポートしてなかったことに今頃気が付いた。大丈夫かな…。やっちまったかな…。一応動いてるけど…。

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

#1 [nitijyou] HDDの中を整理中

ピクチャ、ミュージック、ビデオ、その他のフォルダをDドライブに移動した関係で、バックアップツールの SyncToy の設定をし直すことになったので、バックアップ先の2.5インチHDDをUSB接続して、中身を整理したり等その他の作業をゴチャゴチャと。

SyncToyについて。 :

SyncToy てのは、Microsoft が公開してる、フォルダの中身を同期してくれるツール。コレを使って、ローカルHDDの中身と、外付けHDD(あるいはUSBメモリ等)の中身を同期すればバックアップが取れるわけで。変更があったファイルだけを上書きコピーしたり、ローカルで削除したファイルは外付けHDD側でも削除してくれるので、バックアップ作業の時間がちょっとは短くて済む、みたいな。

_Windows 10 データのバックアップはSyncToyが便利 | Tanweb.net
_Microsoftのツール「SyncToy 2.1」でフォルダを手軽に同期してみる (1) SyncToy 2.1のダウンロードとセットアップ | マイナビニュース
_Microsoft製フリー同期ツール「SyncToy」には、コマンドラインから使える SyncToyCmd.exe もあるのが便利! - Sakura scope

もっとも今なら、クラウド上の仮想ストレージを活用してバックグラウンドで同期、とかできたりもするので、そういうサービスを使う手もあるわけだけど。とはいえ、利用できる容量、転送速度、万が一重要ファイルが流出する可能性などを考えると、ローカルで、HDD同士を使って、というのもアリじゃないかなと。

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

#1 [nitijyou] 部屋の中にカメムシが

ブラタモリを見ながらPCで作業してたら、首のあたりに何かがバシッとぶつかってきて。何だろう。蛾でも入ってきてぶつかったのだろうか。鏡で確認したら、胸のあたりに10円玉ぐらいの巨大なカメムシが。ギャアアア。コイツどこから入ってきやがった。

なんとか窓の外に投げ捨てたけど、指も、服も、例の悪臭が…。着替えてもまだ臭うと思ったら、普段部屋の中で着てるジャンパーにまで臭いが染みついていて。勘弁して…。仕方なく、ジャンパーも洗濯物として出して、着心地がしっくりこないのでリアルに埃を被ってた別のジャンパーを出してきて無理矢理着ていたり。

こっちは危害を加えるつもりが一切ないのに、勝手に入ってきて、勝手にぶつかって、勝手に例の臭いを出しやがって…。いいかげんにしろよ…カメムシ…。

それにしても、カメムシの、あの臭い…。兵器として使えないだろうか…。

例えば、ビニール袋の中にギッシリ詰めて、電車の中で離したら、これはもうある種のテロ…。想像するだに恐ろしい。というか、そんなに集めたくない。実行犯のほうがおそらく大ダメージを受けてしまうのではないか。

もしかすると、拷問の一つとして「カメムシ集め」を設定することも可能だったりしないか。

などとくだらない妄想をして現実逃避してしまうぐらいに、臭い…。

#2 [anime] BS朝日はアニメを放送しないでほしい

どうしてBS朝日は、「ユーリ!!! on ICE」を「レガリア」と「ブブキブランキ」にぶつけてくるかな…。ていうか福島では地上波の「タイガーマスクW」ともぶつかってんだけど…。普段滅多にアニメなんか放送しないくせして、どうして集中して同じ時間帯に放送するのか…。

BS朝日は、一体何なの? 他局のアニメを殺しに来てるつもりなの? 今現在アニメ業界がどこも悲鳴を上げながら必死に作ってるのだから、せめて一応その気になればどのアニメも最低限視聴ぐらいはできる、的状況ぐらいは実現してやらないとあまりに可哀想過ぎるではないかと思うのですけれど。それを何なの。ぶつけて潰し合うとか…。これじゃその気になっても視聴できないじゃん。今のアニメ業界は物理的(?)に見てもらえないアニメを作るために血反吐を吐いてるようなものじゃないか。BS朝日は鬼の集まりか。血も涙も無いのか。人の情けを持ってないのか。それとも、あの局だけ頭の中が昭和なのか。子供だった頃の団塊Jr相手に視聴率競争してたあの時代の感覚でスケジュール決めてるのか。いいかげんにしろ。ぷんすかぷんぷん。ぷぷぷん。

いやまあ、アニメの本数が多過ぎるのが、そもそも問題なんでしょうけど…。しかし、その割に、BS放送にしろ田舎の地上波にしろ、普段はアニメなんか放送しないのだよなあ…。なのに、特定の曜日、特定の時間帯に集中させるというのが…訳が分からない…。

とりあえず、BS朝日はどうもビミョーに放送時間がキッチリしてなくて、前後にずれたり休止したり、一挙にまとめて放送したりと振舞いが珍妙なので、いっそあの局だけはアニメなんか放送しないでほしいものだなあ、などと大変失礼なことを思っております。

というか、もっとガツンと時間帯ずらして放送すれば皆で幸せになれる話だろうに、なんでぶつけて潰し合うんだ…。

全然関係ないけど、「血反吐を吐く」って日本語として合ってるのだろうか。 _感感俺俺 っぽくて、ちと不安になってきた。「血を吐きながら」とでも書くほうがいいのだろうか。どうなんだろう。

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 で貼っておこうかなと。

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

#1 [python] お絵かきアプリを修正中

昨日書いたPythonスクリプトで、GIMPの着色処理っぽいことが実現できたので、先日書いたPySideを使ったお絵かきアプリモドキに反映させているところ。せっかくだからクラスの構成を変えようとして動かなくなってハマっていたり。

PySideのQImageについてメモ。 :

PySideには画像を保持できる QImage というクラスがあるのだけど、反転や回転の仕方が分かったのでメモ。

水平反転、垂直反転は、 _QImage.mirrored() を使えばできる。以下は、水平反転、垂直反転した画像を取得する例。
    def get_hflip_image(self, img):
        u"""水平反転したQImageを返す."""
        nimg = img.mirrored(True, False)
        return nimg
    
    def get_vflip_image(self, img):
        u"""垂直反転したQImageを返す."""
        nimg = img.mirrored(False, True)
        return nimg

回転は、 _QTransform_QImage.transformed() を使えばできる。以下は、0〜3 の指定値から、0度、+90度、+180度、+270度(-90度)の画像を取得する例。
    def get_rot_image(self, img, id):
        u"""回転したQImageを返す."""
        deg = id * 90
        rot = QTransform()
        rot = rot.rotate(deg)
        nimg = img.transformed(rot)
        return nimg

ちなみに、画像内のピクセル値を取得したり、反転や回転をする際は、QImage を利用するけれど。キャンバスに描画等をする際は _QPixmap を使ったほうが高速らしいのでそちらを使うようで。

_QImageとQPixmapの速度比較 - Sukalog

QImage から QPixmap への変換は、 _QPixmap.fromImage() を使えばできる。
        pm = QPixmap.fromImage(qimg)

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

#1 [python] お絵かきアプリモドキにUndoをつけた

PySideを使って書いてるお絵かきアプリモドキにUndoをつけた。とりあえず、キャンバス相当の QPixmap を配列? リスト? に記録していくだけの処理を入れてみたけど、一応それらしい動きになってくれた。
    def save_undo(self):
        u"""キャンバスをUndoバッファに記憶."""
        if len(self.undo_buf) > UNDO_MAX:
            del(self.undo_buf[0])
        self.undo_buf.append(self.canvas_pixmap.copy())

    def undo(self):
        u"""キャンバスをUndo."""
        if len(self.undo_buf) > 0:
            pm = self.undo_buf.pop()
            self.canvas_pixmap = pm.copy()
            self.canvas_item.setPixmap(self.canvas_pixmap)
            return True
        return False

ただ、ちょっと気になる点が。Python で、配列の要素を削除した時、中身はちゃんと解放してくれているのだろうか…?

#2 [zatta] 日本人は土地について無知になっているのではあるまいか

博多駅前の道路が、地下鉄工事の影響で大きく陥没、というニュースを目にして。元々件の土地は川が流れていた場所で、てな話も一緒に見かけたわけで。

そういう場所に地下鉄を作るってのは、どうなんだろうと。いやまあ、今までも何回か事故が起きていたらしいから、もちろん分かった上でやってるのだろうけど。

津波が来る場所なのに、高台をわざわざ削って低くして原発を建てて事故を起こしたソレもそうだし。「蛇落地悪谷」という地名を変えてしまって、危険な場所としての情報を隠してしまったらしい件もそうだろうし。 *1

もしかすると日本人は、自分達がどんな性質を持った土地に住んでいるのか、さっぱり気にしない無知な状態になっているのでは、と思ったりもしたけどどうなんだろう。

先人達がせっかく色々と記録を残しても、全然活かせない現代の日本人。みたいな。

ウチの近所も色々とアレだったり。 :

ウチの近所でも、似たようなアレコレがあって。

阿武隈川が氾濫したら水没しそうな場所に、わざわざ郊外型のスーパーを作ってしまったりとか。あの店は、いつか完全に水没すると思うんだけど…。今でも激しい雨が降ると駐車場が池になるし…。

自分が住んでいる家も、近隣の湧き水の通り道とは知らずに建ててしまったトホホなソレだったり。工事前には、水のあるところに生える系の草がポツポツ生えていたそうで、少し下のほうの村に昔から住んでた人達は、「あそこは湿地みたいなもんだ」と知ってたらしい。けど、当時の両親は若かったから気にせず土地の値段だけで買ってしまって…。結果、床下がアレなことに…。

自宅の裏にあった山を削って作られた宅地も、山を削って作った部分と、谷を埋めて作った部分があって。後者の上に建てた家は、既に傾いてきてるという話が…。

てな感じで、昔から近隣に住んでいる人達は知っていたことが、新規層(?)には伝えられず、みたいなことがチラホラと。

*1: もっとも、「蛇落地悪谷」の件は、 _地名変更したのが江戸時代 という話も見かけたのでアレだけど。

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

#1 [python] PySideでツールパネルっぽいソレを作成できないかテスト

Photoshop や GIMP等の、ツールパネル?っぽいソレをPySideで作れるかどうかテスト。…ツールパネルって呼び方でいいのかな? ツールパレット? なんて呼ぶんだろアレ。要は、どれか一つ、ツールを選ぶと ―― どれか一つボタンを押すと、他のボタンがOFFになるアレ。

_Photoshop のツールギャラリー

こんな感じだろうか。

_button_test.py
u"""押したままの状態になるボタンを並べてツールパネル相当を作るテスト."""


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


class ToolPanel(QWidget):

    u"""ツールパネル用Widget."""

    button_clicked = Signal(int)  # シグナルを用意

    def __init__(self, *argv, **keywords):
        u"""初期化."""
        super(ToolPanel, self).__init__(*argv, **keywords)
        self.grp = QButtonGroup()
        gb = QGridLayout()
        dt = [
            (0, 0, 0, "Brush"),
            (1, 0, 1, "Erase"),
            (0, 1, 2, "Box"),
            (1, 1, 3, "Box Fill"),
            (0, 2, 4, "Select"),
            (1, 2, 5, "Fill"),
        ]
        for x, y, id, s in dt:
            btn = QPushButton(s, self)
            btn.setCheckable(True)  # トグルボタンにする
            self.grp.addButton(btn, id)  # グループ登録してラジオボタン風に使う
            gb.addWidget(btn, y, x)
        self.setLayout(gb)
        self.grp.setExclusive(True)  # 排他的なボタン処理にする
        self.grp.button(0).setChecked(True)  # 最初のボタンを押しておく
        self.grp.buttonClicked.connect(self.changed_button)

    def changed_button(self):
        u"""ボタンが押された時に呼ばれる処理."""
        self.button_clicked.emit(self.grp.checkedId())  # シグナルを発生

    def get_id(self):
        u"""選択状態(ID)を返す."""
        return self.grp.checkedId()


class MyMainWidget(QWidget):

    u"""メインウインドウ."""

    def __init__(self, *argv, **keywords):
        u"""初期化."""
        super(MyMainWidget, self).__init__(*argv, **keywords)
        self.tb = ToolPanel(self)
        self.tb.button_clicked.connect(self.changed_toolbox)
        self.lbl = QLabel("----", self)
        self.lbl.setText("ID=%d" % self.tb.get_id())
        l = QVBoxLayout()
        l.addWidget(self.tb)
        l.addWidget(self.lbl)
        self.setLayout(l)

    def changed_toolbox(self, id):
        u"""ツールの選択状態が切り替わった時に呼ばれる処理."""
        self.lbl.setText("ID=%d" % id)


def main():
    u"""メイン処理."""
    app = QApplication(sys.argv)
    w = MyMainWidget()
    w.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

button_test_ss01.gif


どれか一つボタンを押すと、他のボタンがOFFになってる。更に、IDも変わってる。目的は果たせそう。

こういう動作にする際は、 _QButtonGroup を使う。 _QPushButton (フツーのボタン)でも、 _QRadioButton (ラジオボタン)でも、とにかく QButtonGroup に追加登録してやれば、こういう動作にすることができる模様。また、各ボタンを登録する際にIDを設定しておいてやると、後で変化があった際、押されたボタンのIDを取得することもできる。

QPushButtonは、そのままだとクリックした時しかONにならないけど。クリックするたびにON/OFFを切り替えるトグルボタン(?)にしたい時は、 _setCheckable(True) を呼んで設定してやる。

それと、最初に QPushButton のどれかしらが既に押されている状態にしたいわけだけど。その場合は、 _setChecked(True) を呼んでやれば、ON状態に設定できる。

QButtonGroup に登録されたボタンの状態が変わったら、QButtonGroup は _buttonClicked というシグナルを出すので、そのシグナルにメソッドを割り当ててやれば、どのIDのボタンが押されたのかが分かる。

アイコン画像を表示してみた。 :

各ボタンにテキストが表示されてる状態ではツールパネルっぽくないので、試しにアイコン画像を表示してみたり。

_button_test_with_icon.py.txt
u"""
押したままの状態になるボタンを並べてツールパネル相当を作るテスト.

アイコン画像をボタン上に表示してみる版。
"""


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


class ToolPanel(QWidget):

    u"""ツールパネル用Widget."""

    button_clicked = Signal(int)  # シグナルを用意

    def __init__(self, *argv, **keywords):
        u"""初期化."""
        super(ToolPanel, self).__init__(*argv, **keywords)

        # 画像を読み込んで分割
        im = QImage("./toolbar_32x32x8.png")
        h = im.size().height()
        imgs = []
        for i in range(8):
            imgs.append(im.copy(i * h, 0, h, h))

        self.grp = QButtonGroup()

        gb = QGridLayout()
        gb.setHorizontalSpacing(0)  # GridLayoutの水平余白を設定
        gb.setVerticalSpacing(0)    # GridLayoutの垂直余白を設定
        dt = [
            (0, 0, 0, "Brush"),
            (1, 0, 1, "Erase"),
            (2, 0, 2, "Line"),
            (3, 0, 3, "Box Line"),
            (4, 0, 4, "Box Fill"),
            (0, 1, 5, "Fill"),
            (1, 1, 6, "Text"),
            (2, 1, 7, "Select"),
        ]
        for x, y, id, s in dt:
            icon = QIcon(QPixmap.fromImage(imgs[id]))  # アイコン画像を生成
            btn = QPushButton(icon, "", self)

            # アイコンサイズを画像の元サイズにする
            btn.setIconSize(imgs[id].size())

            btn.setToolTip(s)  # ツールチップを指定

            btn.setCheckable(True)  # トグルボタンにする
            self.grp.addButton(btn, id)  # グループ登録してラジオボタン風に使う
            gb.addWidget(btn, y, x)
        self.setLayout(gb)
        self.grp.setExclusive(True)  # 排他的なボタン処理にする
        self.grp.button(0).setChecked(True)  # 最初のボタンを押しておく
        self.grp.buttonClicked.connect(self.changed_button)

    def changed_button(self):
        u"""ボタンが押された時に呼ばれる処理."""
        self.button_clicked.emit(self.grp.checkedId())  # シグナルを発生

    def get_id(self):
        u"""選択状態(ID)を返す."""
        return self.grp.checkedId()


class MyMainWidget(QWidget):

    u"""メインウインドウ."""

    def __init__(self, *argv, **keywords):
        u"""初期化."""
        super(MyMainWidget, self).__init__(*argv, **keywords)
        self.tb = ToolPanel(self)
        self.tb.button_clicked.connect(self.changed_toolbox)
        self.lbl = QLabel("----", self)
        self.lbl.setText("ID=%d" % self.tb.get_id())
        l = QVBoxLayout()
        l.addWidget(self.tb)
        l.addWidget(self.lbl)
        self.setLayout(l)

    def changed_toolbox(self, id):
        u"""ツールの選択状態が切り替わった時に呼ばれる処理."""
        self.lbl.setText("ID=%d" % id)


def main():
    u"""メイン処理."""
    app = QApplication(sys.argv)
    app.setStyle(QStyleFactory.create("cleanlooks"))
    # app.setStyle(QStyleFactory.create("plastique"))
    # app.setStyle(QStyleFactory.create("windows"))
    # app.setStyle(QStyleFactory.create("windowsvista"))
    w = MyMainWidget()
    w.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
アイコン画像として、以下を自作した。スクリプトソースも含めて、CC0 / Public Domain ってことで。

toolbar_32x32x8.png
_toolbar_32x32x8.png

こんな見た目になった。

button_test_with_icon_ss01.gif

  • アイコンの指定には、QIcon を使う。QPixmap を渡すと作れるっぽいので、元画像が QImage なら、QIcon(QPixmap.fromImage(qimg)) てな記述になる。
  • QIcon は、自動で画像を縮小して適切なサイズにしてしまうので、元画像サイズで表示したい場合は setIconSize() を呼んで元画像サイズを指定してやる。
  • QGridLayout の水平余白、垂直余白は、setHorizontalSpacing()、setVerticalSpacing() で指定できる。
  • ボタン上にマウスカーソルを合わせた際にピョコッと表示されるツールチップは、setToolTip() で指定できる。
  • 各Widgetの全体的な見た目は、QApplication.setStyle() で変更できる。QStyleFactory.create("cleanlooks") や QStyleFactory.create("plastique") を渡してやると見た目が変わる。指定できる文字列としては、windows、motif、cde、plastique、cleanlooks があるらしい。

_QIcon - PySide v1.0.7 documentation
_QGridLayout - PySide v1.0.7 documentation
_QWidget - PySide v1.0.7 documentation
_QApplication - PySide v1.0.7 documentation
_QStyleFactory - PySide v1.0.7 documentation

#2 [python] PySideのQKeySequenceについてメモ

PySideでメインウインドウにメニューバーをつけた際、各メニューに、setShortcut() を使って、ショートカットキーを割り当てたりすると思うのだけど。
    zoomactual_action = view_menu.addAction("Zoom &1:1")
    zoomactual_action.setShortcut("Ctrl+1")
    zoomactual_action.triggered.connect(self.zoom_actual_pixels)

今頃になって、 _QKeySequence なるものがあると知ったのでメモ。

QKeySequence には、あらかじめ決まったショートカットキーが定義されてたりするようで、ソレを使うと分かりやすく…なるのかな? どうかな? どうなんだろう。かえって分かりづらくなったりしないか。どうなんだ。そのへん判断できないけど、とりあえずそういう指定もできるようですね、とメモ。
    open_action = QAction("&Open", self)
    open_action.setShortcut(QKeySequence(QKeySequence.Open))
    open_action.setStatusTip("Open Image File")
    open_action.triggered.connect(self.show_open_dialog)
    file_menu.addAction(open_action)

    ...

    zoomin_action = view_menu.addAction("Zoom &In")
    zoomin_action.setShortcut(QKeySequence(QKeySequence.ZoomIn))
    zoomin_action.triggered.connect(self.zoom_in)

    zoomout_action = view_menu.addAction("Zoom &Out")
    zoomout_action.setShortcut(QKeySequence(QKeySequence.ZoomOut))
    zoomout_action.triggered.connect(self.zoom_out)

ただ、一部、シンボルはあるけど中身が定義されてない組み合わせもあるようで。例えば、QKeySequence.SaveAs には何も割り当てられていなかった。

_QKeySequence - PySide v1.0.7 documentation を眺めた感じでは、SaveAs は、Mac OS X と GNOME なら Ctrl+Shift+S が割り当てられてるけど、Windows では何も割り当てられてないっぽい。このキーの組み合わせは、OS側で何かしらに使ってたりするのだろうか? ちょっとよく分からない。

他にも、"Ctrl+S" といった記述の他に、Qt.CTRL + Qt.Key_S、といった記述も可能らしい。
    save_as_action = QAction("Save &As...", self)
    save_as_action.setShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_S))
    save_as_action.setStatusTip("Save Canvas Image")
    save_as_action.triggered.connect(self.show_save_dialog)
    file_menu.addAction(save_as_action)

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

#1 [cg_tools][prog] CGツールに使えそうなフリーのアイコンが見つからない

昨日、CGツールのツールパネルだかツールボックスだか、とにかくそれっぽいGUIを作れないかと模索した際、ツールボタン上に表示しても良さそうなアイコン画像が入手できなくて少々困ったわけで。

自分が書いたソースは、極力、CC0 / Public Domain で公開したいと思っているので、そこで使うアイコン画像も CC0 や Public Domain だとありがたいのだけど、さすがにそこまで緩いライセンスのアイコン素材には遭遇できなくて。

一応ググってる最中、見た目はピッタリなアイコン画像群を見つけたけれど。

_ArtLibreSet - Tango Desktop Project

しかし、このアイコン群、「GPL」って書いてある…。ということは、このアイコン画像を使ったプログラムも、GPLにしなきゃいけないってことになるのだろうなと。不自由だ…。GPLは、不自由だ…。

これほどCGツールが溢れているのに。 :

世の中にはこれほどCGツールが溢れているのに、そのCGツールの類を作りたい、と思った時に使えそうな、自由なライセンスのアイコン画像が全く見当たらないというのは、なんとなくちょっぴり厳しいものがあるなと。

それぞれ毎回、自分でアイコンを描いているのだろうか。その作業リソースを、本体機能の増強に使えたら…。でも、ライセンスが厳しいアイコンしか出回ってないのだから仕方ないのだろうけど。

もっとも、その手のツールってパッと見の印象も大事だったりするのだろうから、アイコン画像も自作したほうがより良い状態、ではあるのだろうか。アイコンのデザインまで含めてそのツールの仕様の一つ、みたいな。

でも、実験用のスクリプトをちょこっと書く時に、気楽に利用できるアイコン画像が欲しいよなあ…。まあ、いつもそのあたりヨサゲなソレが見当たらなくて、結局自分でドット打ちする展開になってるわけですけど。

一般的なアプリなら Tango Icon Library がヨサゲ。 :

ちなみに、一般的なアプリなら、以下のアイコンが使えそう。

_Tango Icon Library - Tango Desktop Project
_Tango icons - Wikimedia Commons

DLして解凍したら、COPYING というファイルに「The icons in this repository are herefore released into the Public Domain.」と書いてあったので、おそらくこの Tango Icon Library に関しては Public Domain だろうと。自由だ…。

#2 [cg_tools] Paintgraphic 3 を購入

SOURCENEXTのサイトで、Paintgraphic 3 ダウンロード版を購入。前々からなんとくなく気になってたソフトだけど、税抜き1,780円でセール、かつ、割引券があったので、試しに買ってみたり。

もっとも、以前購入した Paintgraphic 2 は使い道が無くて、今現在はインストールすらしてない状態なので、おそらくコレも放置しそうな予感が…。

ちなみに環境は、Windows10 x64。

インストール時のアレコレ。 :

インストールにはネット接続環境が必要。

セットアップファイルを実行すると、デスクトップの真ん中に入力ダイアログが表示されるけど。こちらの好きな場所に表示位置を変更できなくてモヤモヤしたり。何故に、頑として位置を固定してしまうのか…。TV画面をディスプレイに表示しながら作業してたから入力欄が隠れちゃって、わざわざTVを消す羽目に。フツーこの手のソレは、ウインドウの位置を変更できるようにしておくものじゃないのか。

SOURCENEXTのアカウント(メールアドレス)とパスワード、ソフトのシリアル番号を入力すると、SOURCENEXT側に自動でユーザ登録される。

ダウンロード版を購入した自分が言うのもなんだけど、オフライン環境で導入できないのはいかがなものか。ネット接続環境を要求する必然性が、このソフトのジャンルからして無いだろうに。一体何を考えているのか…。

インストール先を指定するダイアログ等は一切出てこなくて、問答無用で C:\Program Files\SOURCENEXT\Paintgraphic3\ にインストールされてしまった。酷い。Cドライブの容量が厳しくて別ドライブにインストールしたいと思った時は一体どうすればいいのか。

まあ、どれもこれも初心者が悩まないように、と考えての仕様かもしれないけど。最初に「標準インストール」と「カスタムインストール」をラジオボタンで選ばせて、ではいかんのだろうか。

起動時間。 :

さすがに古いソフトだけあって、起動はかなり早かった。自分の環境ではGIMPの起動に数分かかるので、この起動時間の早さはありがたいなと。

メモリの使い方について。 :

たしか Paintgraphic 2 を試用した際に、親父さんのデジカメ写真を読み込んで何か処理をしようとしたらメモリが足りないと怒られて作業ができなかった記憶があって。RAMを8GB積んでるのに…。このバージョンはそのあたり改善されているのだろうか。後で確認してみないと…。

キャンバスの拡大表示について。 :

キャンバスを拡大表示したら妙なボケかたをして、「なんじゃこりゃ」「コレは使えないんじゃないか」と思ったけれど。環境設定で、拡大表示時に補完する設定が有効になっていたようで。該当機能を無効にしたら拡大表示してもドットがキッチリと表示された。

ブラシがボケる。 :

少し触っていて気が付いたけど、もしかしてこのソフト、ドット絵の類が作れないのでは…。1ドット単位でキッチリしたドットを打てるブラシ種類がそもそも存在していないような。どれもボケてるブラシばかりなのだけど。それとも何か見落としているのだろうか。

む。各ブラシは濃度設定ってのがあるのか。デフォルトでは濃度が50%になってるけど、100%にしたらキッチリとドットを打てる模様。

まあ、ドット絵を作りたかったら、コレを使わずに EDGE2 を使ったほうが、という気もするけど。

カスタムツールエリアは便利。 :

上部ツールバー上のカスタムツールエリアに、自分のお気に入りのコマンドを登録できるのは、かなり便利。一々メニューを辿ったり、ショートカットキーを割り当てて、かつ、その割り当てを覚えたりしなくても済む…。

ただ、登録したコマンドが「F01」「F02」等のアイコンで表示されるのは今一つ。パッと見で、どれが何のコマンドなのか分からない。ユーザが「ココに○○を割り当てたはず」と覚えておく必要が。結局はユーザにその手の負担を強いているのはよろしくないよなと。

ユーザが任意のアイコン画像を割り当てられる仕様にしておくとか、せめて色ぐらいは選べるようにしておくとか、何かもうちょっと一工夫が必要な気も。

パレットの表示個数が少ない。 :

カラーツールの色見本一覧? パレット? に表示される色の個数が少な過ぎて使いづらいなと。ウインドウサイズを変えても面積が増えないので表示数も増えないし。更に、マウスホイール回転でスクロールする機能も無いので、スクロールバーを一々操作しないと下のほうのパレットを見れない点が非常に不便。

例えば、色1つあたりのサイズを変更して一覧の表示個数を増やせたらいいのに。もっともこのあたり、例えば CLIP STUDIO PAINT も似たような不便さがあった記憶も。パレット機能は登録個数が増えるとこの手の問題が、ということなのだろうか。

Photoshopプラグインが使えなかった。 :

「PaintgraphicシリーズはPhotoshopプラグインも使える」という話を見かけたけど、環境設定を眺めてもプラグインフォルダを指定する設定タブが存在しなくて。

ヘルプを眺めたら、「Photoshopプラグインは、Paintgraphic 32bit版でしか使えない」と書いてあった。すると自分の環境では、問答無用で64bit版をインストールされてしまったということだろうか。

どうせなら32bit版をインストールしたいのだけど、果たして方法はあるのだろうか。無さそうだな…。

しかし、環境設定ダイアログ上では、使用可能メモリが2GBで頭打ちになってるのだよなあ。コレって本当に64bit版なのか? タスクマネージャで確認すると「64」の文字が見えるから、どうやらソレっぽいけど…。

2016/11/11追記。 :

親父さんのデジカメ写真を開いてみた。5616x3744ドットのjpeg。全選択してコピー、かつ、貼り付けをしてもメモリが足りないと怒られなかったし、10枚ぐらいレイヤーを複製してもエラーは出なかった。

#3 [pc] 外付けHDDを購入

手持ちの外付けHDDの空き容量が厳しくなってきたので、ネット通販で外付けHDDを購入。 _Logitec LHD-ENA030U3WS 。USB3.0接続、3TB。ファンレス。税込み9,470円。

BUFFALO や IO-DATA の外付けHDDは、その時々で一番安く入手できるHDDを入れて売ってるので、どのメーカのHDDが入ってるのか買ってみるまで分からない博打っぽいところがあるのだけど。Logitecの件の製品は、「必ず Western Digital(WD)製HDDを入れて売りますよ!」というのが売りらしいので、今回試しに選んでみたり。

到着後、箱を開けてみたけど、内部は結構複雑なパッケージングというか。段ボール1枚で、よくまあここまで立体化を…。そのせいで取り出しづらいけど、その分輸送中の衝撃に対してはちょっと安心、なのかもしれず。取り出しやすいけど届いたら壊れてた、では意味が無いし。

メインPC(Windows10 x64)と繋いでみたら、すんなり認識してくれた。良かった。最低限、認識ぐらいはしてくれないと。

中身は WD Blue (WD30EZRZ-00Z5HB0) だった。Amazonのページでは「WD Blueを入れるよ」と書いてあったけど、公式サイトでは「WD製HDDを入れるよ」としか書いてなかったので、もしかすると WD Green を入れてくるのではと不安だったけどそんなことはなかった。もっとも、 _今の WD Blueは昔の WD Green という話も見かけたし、そもそも WD Greenって今はもう販売終了してたような。違ったっけ。

とりあえず _HDD-Scan 2.0 を使って全セクタの読み取りチェックを開始。残り時間は5時間と表示されてる。

2016/11/11追記。 :

HDD-Scanでのチェックは7時間かかった。読み取りできないセクタは無かった模様。今のところエラーは無いっぽい。

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

#1 [cg_tools][gimp] Paintgraphic3の起動時間をメモ

せっかくだから、手持ちのCGツールの起動時間をメモ。

環境は、Intel Core i5-2500(3.3GHz) + RAM 8GB + HDD WD10EALX-009BA0 (1TB,7200rpm,Cache 32MB)。
CGソフト起動時間
ソフト名起動時間
1回目2回目
Paintgraphic 35秒1秒
JTrim 1.53c1秒1秒
PixBuilder Studio 2.22秒1秒
PhotoFiltre 7.2.15秒1秒
Paint.NET 4.0.1212秒2秒
GIMP 2.8.18 Portable4分25秒40秒
Inkscape 0.91 Portable2分01秒8秒
Paintgraphic 3 の起動時間、そこそこ早い気がする。

ちなみに、2回目の起動は各ファイルがメモリ上にキャッシュされるので早くなるのだろうと。

GIMPの起動時間がヤバイ。 :

GIMPの起動時間が、やっぱりヤバイなと。1回目の起動は、フォントの検索で3分、Script-fuの検索で30秒ほどかかってた。2回目の起動はフォントがキャッシュされたようで短縮されたけど、Script-fuの検索時間は相変わらずのようで。

OSを再起動して、フォントキャッシュがされた状態で GIMPを起動してみた。1分28秒かかった。2回目の起動で1分10秒。…なんだかどんどん遅くなってるような。

SSDにしたら早くなるのだろうか。でも、追加した Script-fu はおそらくデータドライブ = HDDに置くことになるだろうから、それでは早くならないような…。かといって、~/ を丸々全部 SSD に置いたら容量食いそうだし…。

細かいファイルをまとめることはできないのかな。 :

細かいファイルを1つにまとめてメモリ上に載せておくことはできないのかな。…それがファイルキャッシュかもしれないけど。

デフラグをかけてるときに思うのだけど、フォルダ単位で1つのファイルにしてドンと置いといたら断片化しにくくならないか、ファイルアクセスも速くならないか、みたいなことを。

まあ、「SSD積めばええやん」と言われて終わりそうだけど。でも、ソフトウェア的な工夫で改善できる余地がありそうな気もするわけで。さすがにまだ、SSDにアクセスするより、メモリ上に乗ってる仮想ファイルにアクセスするほうが速くないか。そうでもないのかな。どうなんだろ。

圧縮フォルダってあったような。 :

Windowsには圧縮フォルダってのがあったなと思い出したので、GIMP の ~/.gimp-2.x/scripts と ~/.gimp-2.x/brushes フォルダを試しに圧縮フォルダにしてみたり。フォルダを右クリックしてプロパティを選んで詳細設定で圧縮するにチェックを入れてOKボタン。サブフォルダにも適用を選択。

起動時間が1分10秒から1分3秒になった。おお…。ちょっとは速くなるのか…。でもまあ、誤差の範囲かな、という気も。

圧縮を解除してみた。…アレ? 起動時間が1分3秒のまま。もしかしてファイルがキャッシュされていて結果が変わってなかったのだろうか。これじゃ意味があるのかないのか判断できない…。

ちなみに、scriptsフォルダ内のファイル数は 570ファイル。brushesフォルダ内のファイル数は 457ファイル。多過ぎというか入れ過ぎ。

Windows版GIMPは起動が遅いという話も。 :

_Idea/Request: Fast start mode for GIMP (2016) - Help & Support - gimpusers.com によると、「Linux版GIMPはファイルのタイムスタンプを見て新しくなってなければ再登録しないけど、Windowsはそのあたりの動作がぶっ壊れてる(ので毎回起動時に再検索・再登録して起動が遅くなる)」と読めそうな話が。英語分からんので自信無いけど。

SSDを積んでる環境でも起動に3分かかってる、という話も出ているな…。

Script-fuよりPython-fuのほうがいいのかな。 :

_python-fu vs script-fu (2016) - Help & Support - gimpusers.com によると、Python-fu は実際に実行するタイミングでスクリプトファイルの中身を読むので実行するまでワンテンポ遅いけど、Script-fu はGIMP起動時に全部メモリに読み込むので実行するまでが速い、という話が。

逆に考えると…。Script-fuの数が増えると、GIMP起動時にメモリ上に読み込む量が増えるので起動時間が増えてしまうけど、Python-fuで書いてあれば実行する直前までファイルを読まないから起動時間についてはそれほど増えない、てな可能性があったりするのだろうか。

だけど、作業中に頻繁に使う機能なら、最初からメモリ上にあったほうがいいのだろうな…。たまに使いそうな機能なら Python で書いて、よく使いそうな機能は Script-fu で書く、てな方針がいいのだろうか。分からんけど。

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

#1 [windows] CortanaのWeb検索キーワードを無効にしたいのだが

親父さんは Windows10 を使っているのだけど、ちょっと問題が発生して。

経緯。 :

親父さんが、Windows10 のタスクバーに登録してあった Canon ZoomBrowser EX のショートカットを、うっかりドラッグして消してしまったそうで。そこで親父さんは、「ZoomBrowser EX そのものを消してしまった」と思い込んでしまって。

で、親父さんは、Windows10のデスクトップの左下で「何でも聞いてください」と表示されてる Cortana さんに「zoombrowser」と打ち込んでみたそうで。

検索結果の中に、アプリとしての ZoomBrowser EX は出てこなかったけど、Web検索キーワードの一覧を表示する「ウェブ」のあたりに「zoombrowser ex」という文字列が出てきた。親父さんはソレをクリック。

Microsoft Edgeが立ち上がり、Bingの検索結果が表示されて。そのBing検索結果の上位のほうに、Canon公式サイトではない、かつてマルウェアをくっつけて配布してたサイト(updatestar.com)が出てきてしまって。

親父さんはよく分からないまま、その怪しいサイトのリンクをクリック。怪しいサイトから exe をダウンロードして、実行してインストールしてしまいましたとさ。

おかげで自分は、親父さんPCにマルウェアが入ってしまったかどうかを夜中まで作業してチェックする羽目に。

問題点。 :

さて、この件に関して問題はどこにあるのだろうと。

  • Bing検索が怪しいサイトを検索結果の上位に平気で表示してしまうのが悪い。
  • Cortanaが必ずBing検索を使うのが悪い。
  • Cortanaが必ずMicrosoft Edge を立ち上げるのが悪い。
  • Microsoft Edgeに「制限付きサイト」設定機能が無いのが悪い。
  • CortanaがWeb検索キーワードまで表示してしまうのが悪い。

そもそも怪しいURLをクリックしてダウンロードして実行までしちゃうユーザがアウトではあるのだけけど、技術で解決できるならそっちで解決したほうがいいわけで。

Cortanaが必ずBing検索を使うのではなく、好きな検索エンジンを選べたら…。例えばこれがもしGoogle検索を選べていたら、違った結果が出てくるのでひとまず今回の問題は回避できたはずで。

あるいは、Cortanaが Microsoft Edge 以外のブラウザを立ち上げる設定ができれば…。仮にIE11が起動するだけでも、(事前に準備は必要ではあるけど)問題を回避できた。 _IE11には「制限付きサイト」を登録する機能 があって。怪しいサイトのURLを事前に登録しておけば、怪しいURLからファイルをダウンロードしそうになった時にブロックしてくれる。

しかし、Microsoft Edgeにはソレが無い。防御策が無い。

Cortanaをもう少し改善してほしい。 :

ということで、「Windows10 + Cortana + Microsoft Edge + Bing検索はウイルス・アドウェア・マルウェア配布サービスか!」「Microsoft Edge + Bing という最悪の組み合わせを強制しやがって…どちらか変更できればまだなんとかなるのに!」と怒鳴りたい気分になってしまったわけだけど。

せめて、Cortana の検索結果に「ウェブ」を表示しないようにしたいなと…。それができたら、親父さんが検索したいと思った時は既定ブラウザを起動して検索する流れになるので、少しはこういう状況を回避できるはず。

でも、昔の Cortana にはそういう設定があったけど、今は無くなってしまった、という話を見かけて。残っているのは、 _Cortanaの全機能を無効にする設定 ぐらい、らしい…。

Cortanaのアプリ検索 *1 は便利だし、親父さんも時々使っているので、できれば Cortana は残したい・有効にしておきたいわけですよ。Web検索キーワードの一覧表示だけが要らない。そこだけ無効にしたい。

Cortanaそのものを殺したいとは思ってないのに、現状では殺すしか選択肢が無いのは厳しいので、このへん改善してほしいのだけど。しかしわざわざ設定を削除したということは、改善は望み薄なのかなと…。親父さんPC上では、Cortana を殺すしかないのか…。今まで散々、親父さんに、「とにかくここにキーワード打ち込めば探してくれるよ。便利な機能だよ」と教えてきたのに。

Microsoftは愚かな農夫、なのだろうか。 :

Microsoft は、Edge と Bing を Windows10ユーザに売り込もうとして Cortana の仕様を変えた結果、Cortanaそのものを殺してしまった、そんな気がしてきたり。

そういえば…たしか似たような昔話があったような…。 _ガチョウと黄金の卵 でしたっけ…。Microsoftは愚かな農夫のままで終わってしまうのだろうか…。

関連情報をメモ。 :

_Windows 10(Anniversary Update)で、Cortanaを恒久的に無効化する方法 - OTTAN.XYZ
_Windowsスマートチューニング (403) Win 10編: Cortanaを無効にしてローカルファイルのみ検索する | マイナビニュース
_【Windows 10】Windowsサーチをローカルに限定する方法 - Pseudo Hacks

Cortanaを殺してもローカルのアプリやファイルは検索できる、という話があるらしいので、この際だから殺してしまってもいいのかな、という気もするけど、せっかくの新機能なのだから、使えるなら使える状態にしておきたいわけで。「ウェブ」を表示しない設定ができれば、それで助かるんだけどな…。

まあ、本当の問題は、Bing検索が危ない検索サービスになってるあたり、なのでしょうけど。

*1: アプリを表示してくれるのは Cortanaの機能ではなくてファイル検索機能、らしいけど。

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

#1 [python] 消しゴムツールってどうやって実装するのだろう

Python + PySide を使ってお絵描きアプリモドキを作ろうとしているのだけど、消しゴムツールの実装で悩んでしまったり。ブラシ描画なら、QPainter を使えばQPixmap を QPixmap に描画することができるけど。消す時はどうするんだろうなと。

一応、もしかすると QPainter.setCompositionMode() てのが使えるのかも、と思えてきたけど…。

#2 [anime] 「NHKスペシャル 終わらない人 宮崎駿」を視聴

_NHKドキュメンタリー - NHKスペシャル「終わらない人 宮崎駿」 という番組がNHKで流れてたので見てみたり。宮崎駿監督は3DCGをやってる若い人達と一緒に、CGアニメ「毛虫のボロ」を作ってますよ、と紹介していく内容で。

気になるシーンがチラホラあって面白い番組だったなと…。それに、こんなこと言ったらアレだけど、そろそろ宮崎駿監督も年齢的にアレなので、どんどん取材して、晩年の様子を出来る限り映像記録として残しておいてほしいと思ったりもするわけで。どれもこれも、貴重な記録になるはず。

_宮崎駿に再び火がついた! 最新作のきっかけはゴミ拾い?? - NHK_PR - NHKオンライン

番組ディレクターさんへのインタビュー記事も面白かった。大変だろうけど、これからも取材を続けてほしいなと。

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

#1 [python] PySideで消しゴムツール

Python + PySide で書いたお絵かきアプリモドキで消しゴムツールを実装できそうか実験。

なんとかできそうな感じになってきた。

_PySideを使って消しゴムツールを実現できそうかテスト - Gist

実行結果。

eraser_tool_test_ss01.png

ちょっと分かりづらいけど、十字っぽいブラシ画像で、キャンバス相当の背景画像の一部を消去できていることが分かる、かなと。かつ、ブラシ画像のアルファチャンネルもそれっぽく反映されてる。

関係ありそうなところだけ抜き出してみると…。
    def erase_brush_pixmap(self):
        u"""ブラシ画像(QPixmap)を消しゴムとして使う."""
        qp = QPainter()
        qp.begin(self.canvas)
        qp.setCompositionMode(QPainter.CompositionMode_DestinationOut)
        x, y = self.brush.width() + 16, 0
        qp.drawPixmap(x, y, self.brush)
        qp.end()
        del qp
        self.canvas_item.setPixmap(self.canvas)

QPainter.setCompositionMode() を使って、描画する時の合成モードを指定できる。

_QPainter - PySide v1.0.7 documentation
_Composition Modes

それぞれどういう効果があるのか、どっちを Source にして、どっちを Destination にすればいいのか、ちょっとよく分からんけど…。色々試してたら、上記のような記述で消しゴムツールっぽい描画になってくれた。

ドキュメントには「QImageじゃないと働かないよ」と書いてあるようにも思えたけど、Windows10 x64 + Python 2.7.12 + PySide 1.2.4 では、QPixmapを指定しても動いてしまったように見える。けど、これでいいのだろうか。自信無し。

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

#1 [zatta][neta] Webを街にできないものか

ふとなんとなく。Webを3Dで街にできたりしないものかなと馬鹿妄想を。昔、GeoCitiesが、ユーザのWebスペースのURLを、通りの名前と番地で表現してたけど、あんな感じのノリで。まあ、今の GeoCities はフツーにユーザアカウント名でURLを作ってるけど…。

今はWebブラウザもWebGLという3D表示機能を持ってるし。各サイト間のリンク状態から道路を作って、Webサイト内のファイルサイズやファイル数や配色で建物の大きさやデザインを決定して、そうやって生成された街の中をウロウロ、みたいな。

もっとも、実用性は皆無だろうけど。今までは1クリックで目的のページに飛んでいたのに、WASDキーとマウス操作で数分かけて辿り着く、みたいな感じになっちゃうだろうから、情報の閲覧という点では面倒臭くてやってられないはず。ではあるけれど、例えばこれから普及するであろうVRのソレと絡めれば専用のコンテンツやモデルデータを用意しなくても大量の仮想3D空間が得られるだろうから、いつまでも際限なくウロウロできそうでもあり。

と思ったけど、セカンドライフモドキになるだけだろうか。 _BOOMTOWN っぽいソレになってくれないかな。

ていうか既にどこかでそういう実験をやってそう。絶対にやってる予感。と思ってググってみたら、 _インターネット・アドベンチャー なるソレを見かけて、やっぱり既にあったのかと。恥ずかしながら今頃知りました。たぶんコレ、問題点も色々と明らかになったんだろうな。それらの問題点は今の技術で解決できたりしないのだろうか。それとも根本的なところで問題があって上手くいかなかったのだろうか。

_メタバース に近いとされるサービスのアレコレを眺めてみたけど…。専用の空間を作ってたものばかりだな…。「やることがない」とユーザが思って離れてしまうあたりもネック、なのだろうか。分からんけど。

#2 [nitijyou] 某サイトを更新

某所の某サイトを更新。

pdfをキャプチャしてレタッチしてロゴのサムネイル画像を作るのが地味に面倒。たかだか十数ファイルなのに、3時間ぐらいかかった…。なんとか効率化できないものか…。

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

#1 [cg_tools] Inkscape 0.91 x64をインストール

今まで、Windows10 x64上で Inkscape Portable 0.91 x86版を使ってたのだけど、どうも起動が遅くてツライわけで。ググってたら、「フツーにインストールする版は少しは起動が早い」という話を見かけたので、試しに入れてみたり。

入れた版は、Inkscape 0.91 x64版。 _Windows 版 | Inkscape から、inkscape-0.91-x64.msi をDLしてインストール。

起動時間は…。あまり変わらんような…。いや、たしかに少しは早くなってるけど。
ソフト名1回目2回目
Inkscape 0.91 x6410秒5秒
Inkscape 0.91 x86 Portable14秒5秒
インストール直後の、一番最初の起動時に、おそらくはフォントを探してどこかにフォント種類をキャッシュするのだろうけど、そこは体感で数分かかった。その後は上記の起動時間になるっぽい。

不具合メモ。 :

ちなみに、自分の環境では、Inkscape 0.91 x64 で「印刷」を選ぶとエラーが出たはず。 _2016/01/07の日記 にメモしてあった。
ソフト名数値入力欄印刷
Inkscape 0.91 x64OKNG
Inkscape 0.91 x86NGOK
Inkscape 0.91 x86 PortableOKOK
Inkscape 0.48.5 x86 PortableOKOK

アイコンバグを修正。 :

Inkscape 0.91 は、Undo や Redo のアイコンがバグってて「×」のアイコンになってしまう。

_萌える?インクスケープ!(Inkscape) 【0.91】現在判明している不具合(バグ) で、暫定処置扱いだけど修正方法が紹介されてた。 _Inkscape 0.91 - Missing icons. - InkscapeForum.com から、icon.svg を入手して、Inkscapeインストールフォルダ\share\icons\icons.svg と交換すればいいらしい。

試してみたところ、たしかに Undo や Redo のアイコンが表示された。

メニューフォントを変更。 :

以下の記事を参考に、メニューフォントを変更。

_0.91.1リリース - Inkscape@JP
_既知の不具合 - Inkscape@JP
_Inkscape Portable の日本語化とフォントを変更する - @mekemoke
_GTKを使用しているアプリ(GIMP・Inkscape)のUIのフォントを変更する - tk4168の日記

etc\pango\pango.aliases を作成して上記ページで紹介されてる内容をコピペして保存。かつ、etc\gtk-2.0\gtkrc の最後に、
style "my-font"
{
  font_name="Tahoma 9"
}
widget "*" style "my-font"
を追加してみたり。

#2 [cg_tools] svgのサムネイル一覧を綺麗に表示したい

Windows10 x64上で、svgファイルのサムネイル一覧を閲覧したい。

今までは _XnView を使って見ていたけど、大変失礼ながら XnView のソレは品質が酷過ぎて。以下の画像を見れば分かるけど…とにかく汚い。正確なレンダリング(?)ができていない。

xnview_ss.png

もっと綺麗に、というか正確に、svgのサムネイル一覧を表示できるビューワはないものか…。

ググってみたけど、そもそもsvgをサポートしてるビューワ自体が少ないようで。svgは、相変わらず人気が無いなあ…。

しつこくググっていたら、以下のユーティリティに遭遇。

_SVG Explorer Extension - Home

Windowsのエクスプローラに、svgのサムネイルを表示する機能を追加してくれるユーティリティらしい。

試しに導入。Windows10 x64 を使っているので、 _SVG Explorer Extension - Download: SVG Viewer Extension for Windows Explorer v0.1.1 から、64bit版 dssee_setup_x64_v011_signed.exe をDLしてインストールしてみた。

インストール直後は、今までキャッシュされたサムネイル(ファイルアイコン)がそのまま表示されてしまったけど、 _CCleaner でエクスプローラのサムネイルキャッシュを削除してから表示してみたら、以下のような見た目に。

explorer_ss.png

綺麗な表示が得られた。これならなんとかなりそう。

Susieプラグインを使う手もアリ。 :

_Bay-side で、SVG形式Susieプラグインが公開されてる。ありがたや。

_ViX +SVG形式Susieプラグインで表示してみた。

vix_and_susie_svg_plugin_ss.png

ここで表示しているsvg群は、それぞれ48x48ドットのページサイズで作られているけど、そのサイズが反映された状態で表示された。見た目も綺麗。

#3 [prog][cg_tools] アイコン作成中

自作アプリのツールバーに表示できそうなアイコンを作成中。今回は Inkscape で svg を作成してからビットマップにしていこうかと。

アイコン名、ファイル名、アイコン種類は、以下を参考に。

_ArtLibreSet - Tango Desktop Project
_Tango icons - Wikimedia Commons
_TangoifiedIcons - Inkscape Wiki
_New Icons for Inkscape | Logos By Nick Saporito
_Icon Metaphors - Tango Desktop Project

ArtLibreSetの入手先。 :

ArtLibreSet の svg を眺めたら参考になりそうだなと思ったけれど、どこで入手できるのかが分からず。 _Talk:ArtLibreSet - Tango Desktop Project によると、tango-art-libre という名前で、CVS なるものを使って公開されてるらしいが…。

ひょっとすると、コレだろうか。

_Index of /static/cvs/tango-art-libre

試しに wget でごっそりDL。

_Web上にフォルダがそのまま公開されているフォルダをwgetでまとめて取得する方法 - 強火で進め
_wget の使い方

wget -r -np http://hoge、みたいな感じで試してみたら、おそらくは Apache(Webサーバ) が生成したindex.html までファイル保存してしまった…。けど、ひとまず svg は入手できた。

svgからpngに変換する方法で悩む。 :

48x48ドットのページサイズで、複数のsvgを作ったはいいけれど、pngに変換するあたりで悩んでしまったり。数がちょっと多いので、Inkscape を手で操作して変換していくのはツライ。将来、数が増えてきた時も対応が大変だろうし。

Inkscape をコマンドラインで呼び出して、pngでエクスポートしていく手があるらしいけど、自分の手元の環境では Inkscape の起動時間が遅いので、そのやり方はちと厳しい気がする。

ImageMagick を使って svg から png に変換する手もあるけれど、ググってみたところ、どうも ImageMagick の svg → png変換は画質が悪いというかクオリティが低いともっぱらの噂で。

更にググってみたら、どうやら rsvg なるツールが存在していて、ソレを使うとイイ感じの変換ができるらしいと知ったけど。その rsvg なるツールの正体が分からない。ググっても、Perlのモジュールが出てきたり、Rubyの記事が出てきたり、GTKをインストールして云々の記事が出てきたり、R言語がどうとかの記事が出てきたり。どうも Windows上では使えないツールなのかな、という気がしてきた…。

Linuxなら、rsvg-convert なるツールが存在するようで。Linux上で作業したほうがいいのかな。

Windows版のrsvg-convertがあった。 :

_Open Source Software and Windows 32-bit: RSVG-Convert SVG image conversion tool で、Windows上で使える rsvg-convert が公開されてた。ありがたや。

rsvg-convert-2.40.10.7z が、exeファイル一つで変換できるタイプらしい。その変わり、おそらくフォント関係の変換ができないのではないかなと。他の2つは、fc-cache.exe が同梱されてたり、dllがたくさん入ってた。

まあ、自分の場合、svg上のテキストは、念のためにパスに変換しながら作ってるから、rsvg-convert-2.40.10.7z で済みそうかなと。

XnViewもrsvg-convertに対応してるらしいのだが。 :

XnViewも、2.36の時点で rsvg-convert.exeに対応したらしいのだけど。

_XnView Software - View topic - XnView 2.36
_XnView Software - View topic - SVG support via rsvg-convert.exe
_XnView Software - View topic - How do I open SVG files and convert to PNG

Pluginsフォルダに、dll版を丸々コピーしてみたけど、それらしい動きにならない…。ヘルプメニューから辿れるプラグイン情報にもリストアップされてないし…。どうやって導入するんだろう? libpng16-16.dll がダブってるあたりも気になるし。

一旦 XnView をアンインストール・再インストールしてみたら分かってきた。CAD plugin (shareware) をインストールしないでおいて、その後 rsvg-convert.exe 一式をコピーしてやれば動作するのだな…。libpng16-16.dll も、一度アンインストール・再インストールした状態なら入ってなかった。今までの XnView アップデート作業中に、どこかのタイミングで入ってしまったのだろう。

ただ、rsvg-convert を導入した状態だと、svgのサムネイルを作成する際にDOS窓が開いて閉じてを繰り返すのがちょっとアレだなと。

もう一つ。オプションを弄っていて気付いたけど、ツール → オプション → サムネイル、で、シャープサムネイルにチェックが入ってるとサムネイルが汚くなる時がある模様。

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

#1 [python] PySideのツールバー関係を勉強中

QToolBar なるクラスを使うとツールバーが表示できるらしいので、その手のサンプルを探して眺めていたり。

_Menus and toolbars in PySide

どうやら、QAction を使って、メニューに表示する各項目を用意してから、メニューに追加、かつ、ツールバーにも追加していく模様。

QActionの書き方。 :

今までは以下のような書き方をしてたけど。
    eraser_action = QAction("Eraser", self)
    eraser_icon = QIcon(self.icons[2])
    eraser_action.setIcon(eraser_icon)
    eraser_action.setCheckable(True)
    eraser_action.setShortcut("E")
    eraser_action.setStatusTip("Eraser tool")
    eraser_action.triggered.connect(self.eraser_tool)

_Examples/menus.py at master - PySide/Examples で、こういう書き方もできるらしいと知った。
    line_action = QAction(QIcon(self.icons[3]), "Line", self,
                          checkable=True, shortcut="L",
                          statusTip="Line tool",
                          triggered=self.line_tool)

どっちが分かりやすいのかは、ちょっとよく分からない。

Qtが持っているアイコン画像を使う書き方。 :

自分で用意したアイコン画像を指定する際は以下のように書くけれど。
    new_action = QAction("&New", self)
    new_icon = QIcon(QPixmap("document-new.png"))
    new_action.setIcon(new_icon)

Qtが持ってるアイコン画像を使う場合は以下のように書けばいいらしい。
    new_action = QAction("&New", self)
    new_icon = self.style().standardIcon(QStyle.SP_FileIcon)
    new_action.setIcon(new_icon)

どういう種類のアイコン画像があるかは、以下のページが参考になった。

_Qt デフォルトで使用可能な組み込みアイコン QStyle::StandardPixmaps - unstable diary
_QStyle - PySide v1.0.7 documentation (PySide.QtGui.QStyle.StandardPixmap のあたりを参照)
_List of Qt Icons | Joe Kuan Defunct Code

qApp.setStyle(QStyleFactory.create('Cleanlooks')) 等を指定して全体の見た目を変えるとアイコンも変わるかもと思ったけれど。試してみたら一部のアイコンは変わったものの、大半は同じデザインのアイコンが表示された。

QToolBar上のアイコンサイズ。 :

QToolBar上のアイコンサイズを指定する方法は、以下が参考になった。

_How to change Icon size in QMenu?

    self.view_tb = QToolBar("View")
    self.view_tb.setIconSize(QSize(32, 32))
    self.addToolBar(self.view_tb)
QToolBarに対し、setIconSize() を使えば、表示したいアイコンサイズを指定することができる。

アクションのグループ化。 :

例えば、CGツールでよく見かける、ブラシツール、消しゴムツール、選択ツール等は、一つを選ぶと他の項目が非選択になる。要は、ラジオボタンっぽい感じだけど。

そういう感じのメニューやツールバーにしたい時は、QActionGroup を使う。
    # ツール群。checkableをTrueにしてることに注意
    pen_action = QAction("Pen", self)
    pen_action.setIcon(QIcon(self.icons[0]))
    pen_action.setCheckable(True)
    pen_action.setShortcut("P")
    pen_action.triggered.connect(self.pen_tool)

    eraser_action = QAction("Eraser", self)
    eraser_action.setIcon(QIcon(self.icons[2]))
    eraser_action.setCheckable(True)
    eraser_action.setShortcut("E")
    eraser_action.triggered.connect(self.eraser_tool)

    # ツール群はグループ化する。どれか一つだけが選ばれる状態になる。
    self.tools_grp = QActionGroup(self)
    self.tools_grp.addAction(pen_action)
    self.tools_grp.addAction(eraser_action)
    pen_action.setChecked(True)
該当する QAction に対し、setCheckable(True) を設定してから、QActionGroup に追加登録してやる。また、初期状態では、setChecked(True) を使って、どれか一つを選択状態にしておく。

メニューバーやツールバーへの登録は、いつもと同じように行えばいい。QActionGroup 自体をメニューバーやツールバーに追加登録する必要はない。

#2 [pc] Markdownを分かってないヤツが多過ぎる

Atomエディタの拡張の、 _markdown-toc てのが気になったのでインストールしてみたのだけど、toc = 目次が作れなくておかしいなと。

色々試していて気が付いた。この拡張は、行頭に「#」「##」を書くタイプの見出しにしか対応してないのだな。

この拡張の作者は、Markdownの売りが分かってないな…。よく見かける光景だけど、なんだかガックリ。

てなあたりから、どうでもいいことをもやもや考え始めたり。

Markdownの売り。 :

前にも書いた記憶があるけど…。Markdownの売りって、テキストファイルで眺めても視認性が高い、ってところで。

記述仕様/記述ルールの豊富さや、解析処理における厳格さで比較したら、他の記法のほうがはるかに充実していて。スペック(?)だけを見たらMarkdownを選ぶ理由なんて無い。

しかし他の記法は、ソースが呪文だらけ・謎記号だらけになってしまう。一般人お断りの文書ファイルになりがち。

そうはならないように「見た目」を重視してみたのが Markdown だし、その代わり記述仕様が今一つ充実してないのが Markdown。

「打ち込みやすさ」「できることの多さ」を重視したのが他の記法。「見栄え」「パッと見でわかるか」を意識したのが Markdown。そういう違いがあるわけで。

それを考えたら、二行で書くタイプの見出しには未対応で、行頭に「#」「##」を書くタイプの見出しにしか対応してないってのはNGだよなと。何のために Markdown を考え出したのか、そこからして分かってないからそうなっちゃうんだろう。いやまあ、行頭だけチェックする処理と、二行分をチェックする処理では、後者のほうが面倒だから対応したくないのは分かるのだけど、そこがいかにもプログラマー的なダメ思考というか。

個人的に、「Markdown とはなんぞや」が分かってるかどうかは、どっちの見出しの書き方をその人が選んでるかでおおよそ判断できそうな気がしていて。「#をつけると見出しになりますよ」しか説明しないヤツは、十中八九、分かってない。

というのも、「『#』が見出し」というルールを知らない人がソースを眺めてしまったら、その行が見出しとは分からないからで。つまり、「#」「##」の見出しを使いまくるというのは、謎記号を推奨するスタンスと言える。

だったら他の記法と何ら変わらないではないか。むしろ他の記法を選んだほうが良いではないか。他の記法なら謎記号を好きなだけ散りばめ放題。知らない人がソース(?)見て「ギャッ」となるファイルを書き放題。その代わり、Markdown よりも多種多様なコマンドを呼び出せて便利に使えるはず。謎記号カモーンなスタンスで書きたいなら、あえて Markdown なんぞ選ぶ必要は無いわけで。

というか、改めてGFM(GitHub Flavored Markdown)の仕様を眺めてみたら、謎記号がますます増えていて呆れたり。やっぱりプログラマーはダメだな。どんだけ謎記号が好きなんだ。いやまあ、打ち込む文字数が減るから、だろうけど。分かるけど。

そもそもLispのような括弧だらけの言語も平気で使いこなすのが優秀なプログラマーとされていたりするので、プログラマーという人種は謎記号に対する感覚が完全に麻痺しているのだろうなと。謎記号フェチ。それがプログラマー。だけどその割には、「Perlのソースは謎記号だらけ」とdisったりしてるし。Markdown すら謎記号だらけにしちゃう人種がソレを言うか…などと思ったり思わなかったり。

まあ、このあたり、「書きやすさ」と「読みやすさ」のどのへんでバランスをとるのか、という話でしかないのかしらん。

余談。 :

Visual Studio Code にも、 _Markdown Toc という拡張があるらしいのでインストールしてみたけど、こっちも「#」タイプにしか対応してなかった。どいつもこいつも分かってねえ。

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

#1 [python] PySideでツールバー表示のテスト

それっぽくなってきた。

_PySideを使ってツールバーの表示をテスト - gist

qtoolbar_test_ss01.png

もっと短く書けないものかと思わないでもないけど…。まあいいか。テストだし。表示できることは分かったし。

ツールバー用アイコン画像も置いときます。 :

自作したツールバー用のアイコン画像 ( svg + png (48x48, 32x32, 22x22, 16x16) ) もzipにして置いときます。ライセンスは CC0 / Public Domain のフリー素材ってことで。

artlibreset_substitute_20161118_ss.png

_artlibreset_substitute_20161118.zip (104KB)

中に mkpng.rb てのが入ってますが…。これは _Windows版のrsvg-convert.exe を使って、scalable\*.svg を png に変換するバッチファイル代わりのRubyスクリプト、なので気にしないでください。

ちなみに、svg 作成には、 _Inkscape を使いました、とメモ。

QMainWindowは最初からメニューバー等を持ってるらしい。 :

今まで知らなかったけど、QMainWindow は、最初からメニューバーやステータスバーを持っているらしい。いや、持っているというか、存在することが前提っぽく処理をすると自動的に作られる、てな便利な実装になってるだけかもしれないけれど。

いきなり self.statusBar().showMessage("Ready.") とでもやればステータスバーが出現するし。いきなり mb = self.menuBar() とやればメニューバーにアクセスできてしまった。ツールバーも同様で、いきなり tb = self.addToolBar("File") とかやると、ツールバーが出現するようで。

てなあたり、 _Menus and toolbars in PySide を眺めていて気付きました。今まで知らなかったから、メニューバーやステータスバーを生成してから、QMainWindow に setMenuBar() や setStatusBar() で設定してました。そんなことをする必要は無かったのだな…。

#2 [python] Windows10x 64上のPySideでsvgが表示できなくて悩んだり

PySideを使ったGUIアプリで、メニューやツールバー上にアイコン画像を表示したいなと思った場合。メニューの各項目(QAction)に対して、 _QIcon というクラスを使いながらアイコン画像を指定してやるのだけど。

ドキュメントを眺めていたら、「Qt4.2 からsvgをサポートしているぜ」と書いてあって。

であれば…。事前に svg を png に変換しておいてから使うのではなく、直接 svg をアイコン画像として指定できそうだなと。

しかし。以下のように実際に試してみても、表示される気配がない。何故。Windows上で動かしてるから?
    new_action = QAction("&New", self)
    new_icon = QIcon("./document-new.svg")
    new_action.setIcon(new_icon)
    new_action.setShortcut(QKeySequence(QKeySequence.New))
    new_action.setToolTip("New Canvas")
    new_action.triggered.connect(self.new_canvas)

Pythonインストールフォルダ\Lib\site-packages\PySide\plugins\imageformats\ の中を眺めると、qsvg4.dll なるファイルがあるし、Lib\site-packages\PySide\plugins\iconengines\ の中に qsvgicon4.dll なるファイルもある。であれば、なんだか svg も扱えそうな気がするのだけど…。何故に表示されないのか…。

importの書き方で使えたり使えなかったりするらしい。 :

_python - PySide SVG image formats not found? - Stack Overflow で、気になる書き込みが。「from PySide import QtSvg を書いてみたか?」と書いてある、ような。

試してみたら、たしかに svg が表示された。例えば以下のように書いておくと、画像ファイルとして svg を指定してもちゃんと表示される。
import sys
from PySide.QtCore import *  # NOQA
from PySide.QtGui import *  # NOQA
from PySide import QtSvg, QtXml  # NOQA

今まではこう書いてた。
import sys
from PySide.QtCore import *  # NOQA
from PySide.QtGui import *  # NOQA

ということで、「from PySide import QtSvg, QtXml」を書くと、svg が読み込めるようになるっぽい。が、何故だろう…。Python のそのあたり、よく分からない…。

ちなみに、Pythonスクリプトソース内の問題点を教えてくれる flake8 は、「QtSvg なんて使ってねえぞ? 無駄な行じゃねえのか?」と文句を言ってくる。どういうことだろう。

サポートしてる画像フォーマットを確認。 :

PySideを使ってるソースのどこかで、例えば以下を書けば、サポートしている画像フォーマットが確認できるらしい。
        for s in QImageReader.supportedImageFormats():
            print(s)

「from PySide import QtSvg, QtXml」を書いた場合は、以下の結果に。
bmp
gif
ico
jpeg
jpg
mng
pbm
pgm
png
ppm
svg
svgz
tga
tif
tiff
xbm
xpm
svg や svgz が入ってる。

「from PySide import QtSvg, QtXml」を書かない場合は、以下の結果に。
bmp
gif
ico
jpeg
jpg
mng
pbm
pgm
png
ppm
tga
tif
tiff
xbm
xpm
svg や svgz は入ってない。

#3 [zatta] 仕事で殺されそうになったことがない人が言ってるんじゃなかろうか

某企業の「取り組んだら『放すな』、殺されても放すな」というソレが、なんだか気になってて。

前々から、たまに言ってるけど…。本当に死にそうな目に遭ったことがない人ほど、不用意に「死ぬ気でやれ」とか口走ってしまうもんで。本当に死にそうな目にあった人なら、「いや…そこまでやらんでええよ…ていうかそういう精神状態になったらダメだろ…それもう負け戦確定だから…」とモニョモニョしちゃうはずで。

例えば、太平洋戦争中に戦地に行って大変な思いをした人達は、「もう1回戦争やりてえなあ」とか言ったりしないわけでね。戦地で片腕無くした水木しげる先生だって「戦争はいかんですよ。腹が減る」と言ってるし。 *1 「戦争しようぜ!」と口走るのは、戦地で地獄を見た経験が全然無い人だったり、どこか壊れちゃった人だったりするわけで。

本当に酷い目に遭った人は、そういうことを言わんのですよ。そのように考えると、「殺されても放すな」という文を書いた人は、これはほぼ間違いなく仕事で殺されそうになったことが無い人だよなと…。そういう人だから、そういう文を書けてしまう。ていうか実際に殺されてたらその一文は書けないし。<オイ。

もし、本当に仕事で殺されそうな目に遭った人なら、「いやいやいや、殺されそうな状態なら仕事なんかさっさと捨てなきゃダメでしょ。仕事どころじゃねえよ。全ては命あっての物種だよ? 生きてるから仕事もできるんだよ? 死んだら仕事もへったくれもないだろ。只の馬鹿だろ」ぐらいのことを言ってくれるんじゃないかと。

まあ、「アレは、物の喩えというか、あくまで仕事に取り組む意識やスタンスの話で…」「君は空気も読めないのか…」とか言い出すのだろうけど。スタンス云々でそういうアレな文を書いちゃう時点で、この人たぶん本当の地獄は見てないな、自分では見てたつもりになってただけの人だろうな、と思えてくることに変わりはないわけで。

てなわけで、「死ぬ気で」とか「殺されても」とか平気で口走る人達の言に耳を傾ける必要は一切無いよな、真に受けちゃダメ、ていうかそういう人種とはちょっと距離を置いたほうがいい、下手するとマジにこっちが殺されるし、てなことを自分は思ってしまうのでした。
*1: 実際には水木しげる先生自身が言ったわけじゃなくて、先生が描いた漫画の中でソレに近い台詞が書いてある、という話らしい。

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

#1 [python] PySideの「:/images/hoge.jpg」がよく分からない

PySideのサンプルを眺めていたら、QIcon(":/images/hoge.jpg") みたいな記述を見かけた。

何だろうコレ。「:/」は何を表してるんだ?

ググってたら、以下のページに遭遇。

_Qt how to access resources - Stack Overflow
_The Qt Resource System | Qt Core 5.7

リソースとして画像を登録しておくと、「:/images/hoge.jpg」みたいな記述でアクセスできるようになるらしい。

どうやら qrc なるファイルが関係してるようだけど。ググってたら、この日記も検索結果に出てきて。

_2012/09/22の日記 で、PySide じゃなくて PyQt の話だけど、リソースの指定の仕方がメモしてあった。qrcファイルを作成して、ツールを使って 〜_rc.py というファイルに変換して、スクリプト内に import 〜_rc と書いて使うようだなと。

そのあたりググっていたら、どうやら昔はアイコン画像等を表示したい場合、リソースファイルを作ってアレコレしないと表示できなかったようで。

ただ、今現在の Python 2.7.12 + PySide 1.2.4 では、いきなり QIcon("./images/hoge.jpg") と書いても画像が読め込めて、アイコンが表示されてるんだよな…。もしかするとこのあたり、機能追加された、ということだろうか。分からんけど。

QIconについてメモ。 :

数日前までは、QIcon(QPixmap("hoge.png")) とか書かないと生成できないものと思い込んでたけど。

その後あちこちのサンプルを眺めていたら、いきなり QIcon("hoge.png") と書いてもOKなことに気が付いた。とメモ。

_QIcon - PySide v1.0.7 documentation にも、class PySide.QtGui.QIcon(fileName) と書いてあった。見逃してた。

QKeySequence についてメモ。 :

数日前までは、new_action.setShortcut(QKeySequence(QKeySequence.New)) とか書かないと設定できないものと思い込んでたけど。

どうやらいきなり、new_action.setShortcut(QKeySequence.New) と書いてもOKなことに気が付いた。とメモ。

#2 [python] PySideでカスタムダイアログを作って入力値を取得

お絵かきアプリモドキで、キャンバスの新規作成時、サイズを入力したいなと。ダイアログを開いて、数値を入力させて、みたいな。

何を表示すればいいんだろう。ダイアログを開きたいのだから、QDialog かな。

ググってたら、以下のページに遭遇。

_python - How can I show a PyQt modal dialog and get data out of its controls once its closed? - Stack Overflow

なるほど…。こういう風に書けばいいのか。

こんな感じかな。

_qdialog_test.py
u"""
PySide + QDialogのテスト.

カスタムダイアログを表示して入力値を取得する例.

動作確認環境 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4
"""
import sys
from PySide.QtCore import *  # NOQA
from PySide.QtGui import *   # NOQA


class CanvasSizeInputDialog(QDialog):

    u"""キャンバスサイズ入力ダイアログ."""

    DEF_W = 256
    DEF_H = 256

    def __init__(self, *argv, **keywords):
        """init."""
        super(CanvasSizeInputDialog, self).__init__(*argv, **keywords)
        self.setWindowTitle("Input new canvas size")

        # スピンボックスを用意
        self.input_w = QSpinBox(self)
        self.input_h = QSpinBox(self)
        self.input_w.setRange(1, 8192)  # 値の範囲
        self.input_h.setRange(1, 8192)
        self.input_w.setFixedWidth(80)  # 表示する横幅を指定
        self.input_h.setFixedWidth(80)
        self.input_w.setValue(CanvasSizeInputDialog.DEF_W)  # 初期値を設定
        self.input_h.setValue(CanvasSizeInputDialog.DEF_H)

        # ダイアログのOK/キャンセルボタンを用意
        btns = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
            Qt.Horizontal, self)
        btns.accepted.connect(self.accept)
        btns.rejected.connect(self.reject)

        # 各ウィジェットをレイアウト
        gl = QGridLayout()
        gl.addWidget(QLabel("Input new canvas size", self), 0, 0, 1, 4)
        gl.addWidget(self.input_w, 1, 0)
        gl.addWidget(QLabel("x", self), 1, 1)
        gl.addWidget(self.input_h, 1, 2)
        gl.addWidget(btns, 2, 3)
        self.setLayout(gl)

    def canvas_size(self):
        u"""キャンバスサイズを取得。(w, h)で返す."""
        w = int(self.input_w.value())
        h = int(self.input_h.value())
        return (w, h)

    @staticmethod
    def get_canvas_size(parent=None):
        u"""ダイアログを開いてキャンバスサイズとOKキャンセルを返す."""
        dialog = CanvasSizeInputDialog(parent)
        result = dialog.exec_()  # ダイアログを開く
        w, h = dialog.canvas_size()  # キャンバスサイズを取得
        return (w, h, result == QDialog.Accepted)


class MyWidget(QWidget):

    u"""メインウインドウ相当."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(MyWidget, self).__init__(*argv, **keywords)
        self.btn = QPushButton("Open Dialog", self)
        self.lbl = QLabel("Ready", self)

        # レイアウト
        l = QVBoxLayout()
        l.addWidget(self.btn)
        l.addWidget(self.lbl)
        self.setLayout(l)

        # ボタンを押したらキャンバスサイズ入力ダイアログを開く
        self.btn.clicked.connect(self.open_input_dialog)

    def open_input_dialog(self):
        u"""キャンバスサイズ入力ダイアログを開いて値を取得."""
        w, h, result = CanvasSizeInputDialog.get_canvas_size(self)
        if result:
            self.lbl.setText("Ok. %d x %d" % (w, h))
        else:
            self.lbl.setText("Cancel.")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyWidget()
    w.show()
    sys.exit(app.exec_())
qdialog_test_ss01.gif

それらしく動いてる、ように見える。

QDialog を使って自作したダイアログの、OK/キャンセルボタンは、以下のように書けば用意できるみたいだなと。 _QDialogButtonBox を使うっぽい。
        # ダイアログのOK/キャンセルボタンを用意
        btns = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
            Qt.Horizontal, self)
        btns.accepted.connect(self.accept)
        btns.rejected.connect(self.reject)
accepted とか rejected とか、そのあたりがOKだのキャンセルだのと関係してるのだろうなと。

ダイアログを開く時は、exec_() を呼ぶようで。
        dialog = CanvasSizeInputDialog(parent)
        result = dialog.exec_()  # ダイアログを開く
OK/キャンセルボタンを用意した場合は、結果として、QDialog.Accepted か QDialog.Rejected が返ってくる模様。 _QDialog - PySide v1.0.7 documentation に、それらしいシンボルも見えるし。

前述のスクリプトの中では return (w, h, result == QDialog.Accepted) と書いてるけど、OKボタンが押されたら結果として QDialog.Accepted が返ってくるから、ソレと比較することで True / False に変換して結果を返しているのだろう。呼び出した側は、3番目の返り値が True か False かで、OKが押されたか、キャンセルが押されたかを判別すればいい。

#3 [prog] Open Icon Libraryってどうなってるんだろ

_Open Icon Library なる、CC0、Public Domain、GPLのアイコンを大量に集めたソレを見つけたのだけど。

ライセンスがCC0のアイコンのみをまとめたのかなと思われる、open_icon_library-CC-0.11.tar.bz2 をダウンロードして中身を確認してみたら、 _Oxygen Icon が混ざっていて。例えば draw-freehand.svg のあたりとか。コレ、ライセンスが LGPL だと思うのだけど…。CC0じゃないんだけど…。

もしかすると、他にも違うライセンスのアイコンが混ざってそう。怪しいから使わないことにして、HDDから全部削除した。

Open Icon Library って、そのあたりの分類はどうなってるんだろう…。まあ、2010年から更新されてないようだし、おそらく放置状態なのだろうけど。

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

#1 [python] GIMPのパレットファイルをプレビューするPySideのスクリプト

PySideを使って、GIMPのパレットファイル(.gpl)を読み込んで表示できるスクリプトを書いてたり。

_palette_select.py
u"""
GIMPのgplファイルを読み込んでパレットを表示してみるテスト.

動作確認環境 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4

"""

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


class PalettePreview(QGraphicsView):

    u"""パレットプレビュー用QGraphicsView."""

    valueGetted = Signal(QColor)

    def __init__(self, *argv, **keywords):
        """init."""
        super(PalettePreview, self).__init__(*argv, **keywords)
        self.rgb_data = []
        self.w = 256
        self.h = 128
        self.columns = 16
        self.rows = 8
        self.cw = self.w / self.columns
        self.ch = self.h / self.rows
        self.select_color = QColor(Qt.black)

        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setFixedSize(self.w, self.h)
        self.setBackgroundBrush(QBrush(self.get_bg_chip_image()))

        self.setScene(QGraphicsScene(self))
        pm = QPixmap(self.w, self.h)
        pm.fill(Qt.black)
        self.pal_canvas_item = QGraphicsPixmapItem(pm)
        self.scene().addItem(self.pal_canvas_item)
        self.pal_canvas_item.setOffset(0, 0)

    def make_palette_preview(self, columns, rgb_data):
        u"""パレットプレビュー画像を生成."""
        self.rgb_data = rgb_data
        self.columns = columns
        n = len(rgb_data)
        self.rows = n / columns
        if n % columns != 0 or n < columns:
            self.rows += 1
        self.cw = self.w / self.columns
        self.ch = self.h / self.rows

        # QPixmapに描画
        pm = QPixmap(self.w, self. h)
        pm.fill(QColor(0, 0, 0, 0))
        x, y = 0, 0
        for d in self.rgb_data:
            r, g, b, _ = d
            self.draw_one_palette(pm, x, y, self.cw, self.ch, (r, g, b))
            x += self.cw
            if x >= self.w or x + self.cw > self.w:
                x = 0
                y += self.ch

        self.pal_canvas_item.setPixmap(pm)
        self.scene().setSceneRect(QRectF(0, 0, self.w, self.h))

    def draw_one_palette(self, pm, x, y, w, h, rgb):
        r, g, b = rgb
        brush = QPixmap(w, h)
        brush.fill(QColor(r, g, b, 255))
        qp = QPainter()
        qp.begin(pm)
        qp.drawPixmap(x, y, brush)
        qp.end()
        del qp
        del brush

    def get_bg_chip_image(self):
        u"""背景塗りつぶし用のパターンを作成して返す."""
        c0, c1 = 128, 80
        im = QImage(16, 16, QImage.Format_ARGB32)
        qp = QPainter()
        qp.begin(im)
        qp.fillRect(0, 0, 16, 16, QColor(c0, c0, c0, 255))
        qp.fillRect(0, 0, 8, 8, QColor(c1, c1, c1, 255))
        qp.fillRect(8, 8, 8, 8, QColor(c1, c1, c1, 255))
        qp.end()
        del qp
        return im

    def mousePressEvent(self, event):
        u"""マウスボタンが押された時の処理."""
        if event.button() == Qt.LeftButton:
            scene_pos = self.mapToScene(event.pos())
            x = int(scene_pos.x())
            y = int(scene_pos.y())
            if x < 0 or x >= self.w or y < 0 or y >= self.h:
                return
            idx = (y / self.ch) * self.columns + (x / self.cw)
            if idx < len(self.rgb_data):
                r, g, b, _ = self.rgb_data[idx]
                self.select_color.setRgb(r, g, b, 255)
                self.valueGetted.emit(self.select_color)

    @Slot(QColor)
    def changedValue(self, value):
        """get color."""
        pass


class PaletteSelect(QWidget):

    u"""パレット選択ウィジェット."""

    PAL_DIR = "./palettes"
    DEF_PAL_NAME = "pet2015"

    valueGetted = Signal(QColor)

    def __init__(self, *argv, **keywords):
        """init."""
        super(PaletteSelect, self).__init__(*argv, **keywords)
        self.gpl_data = self.get_gpl_data()

        self.gview = PalettePreview(self)

        idx = 0
        self.combo = QComboBox(self)
        lst = sorted(self.gpl_data.keys())
        for (i, key) in enumerate(lst):
            self.combo.addItem(key)
            if key == PaletteSelect.DEF_PAL_NAME:
                idx = i

        vl = QVBoxLayout()
        vl.addWidget(self.combo)
        vl.addWidget(self.gview)
        self.setLayout(vl)

        self.combo.activated[str].connect(self.change_combo)
        self.gview.valueGetted[QColor].connect(self.clicked_palette_view)

        self.combo.setCurrentIndex(idx)
        self.update_preview(PaletteSelect.DEF_PAL_NAME)

    def change_combo(self, str):
        u"""comboboxが変更された時に呼ばれる処理."""
        self.update_preview(str)

    def clicked_palette_view(self, col):
        u"""パレットプレビューがクリックされた時に呼ばれる処理."""
        self.valueGetted.emit(col)

    def update_preview(self, pal_name):
        u"""パレットプレビューを更新."""
        _, columns, rgb_data = self.gpl_data[pal_name]
        self.gview.make_palette_preview(columns, rgb_data)

    def get_gpl_data(self):
        u"""パレットファイル(.gpl)を読み込み."""
        dir = PaletteSelect.PAL_DIR
        dt = {}
        for fname in os.listdir(dir):
            path, ext = os.path.splitext(fname)
            if ext.lower() == ".gpl":
                d = self.read_gpl_file(dir, fname)
                if d is not None:
                    fname, name, columns, rgb_data = d
                    name = name.lower()
                    dt[name] = (fname, columns, rgb_data)
        return dt

    def read_gpl_file(self, dir, fname):
        u"""gplファイルを1つ読み込んで内容をタブルで返す."""
        name = fname
        columns = 16
        rgb_data = []
        fpath = os.path.join(dir, fname).replace(os.path.sep, '/')

        r1 = re.compile(r"Name:\s+(.+)$")
        r2 = re.compile(r"Columns:\s+(\d+)$")
        r4 = re.compile(r"^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.+)$")
        r5 = re.compile(r"^\s*$")

        f = open(fpath, 'r')
        for (i, s) in enumerate(f):
            s = s.rstrip()
            if i == 0:
                if s != "GIMP Palette":
                    return None
            elif i == 1:
                m = r1.match(s)
                if m:
                    name = m.group(1)
                else:
                    print("Error : %s" & s)
                    return None
            elif i == 2:
                m = r2.match(s)
                if m:
                    columns = int(m.group(1))
                else:
                    print("Error : %s" & s)
                    return None
            elif s.find("#") == 0:
                continue
            elif r5.match(s):
                continue
            else:
                m = r4.match(s)
                if m:
                    r, g, b, colname = m.groups()
                    rgb_data.append([int(r), int(g), int(b), colname])
                else:
                    print("Error : %s" & s)
                    return None
        f.close()
        return (fname, name, columns, rgb_data)


class MyWidget(QWidget):

    u"""メインウインドウ相当."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(MyWidget, self).__init__(*argv, **keywords)
        self.palview = PaletteSelect(self)
        self.lbl = QLabel("Ready.")
        l = QVBoxLayout()
        l.addWidget(self.palview)
        l.addWidget(self.lbl)
        self.setLayout(l)

        self.palview.valueGetted[QColor].connect(self.clicked_palette)

    def clicked_palette(self, col):
        u"""パレットプレビューがクリックされた時の処理."""
        r, g, b = col.red(), col.green(), col.blue()
        self.lbl.setText("RGB=(%d, %d, %d)" % (r, g, b))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyWidget()
    w.show()
    sys.exit(app.exec_())

palette_select_ss01.gif


スクリプト + パレットファイル群(*.gpl)を、zipでまとめて置いておくです。スクリプトは CC0 / Public Domain ってことで。

_paletteselect_20161121.zip

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

#1 [python] PySideのQTabWidgetについてテストしてたり

パレットウインドウを表示するとアプリウインドウの面積が結構大きくなりそうなので、タブを表示して色指定の表示部分を切り替えられないかなと。ググってみたら、どうやら QTabWidget てのがあるようで。

_QTabWidget - PySide v1.0.7 documentation
_[PyQt] QTabWidget を使ってタブレイアウトを作成する - Python/PyQt

使い方としては、QTabWidget を生成して、そこに登録したいウィジェットを addTab() で追加していけばいいらしい。

てなわけで手元で動作確認していたのだけど。パレット上でクリックした時にRGBのスライダーも変化したほうが良さそうだな、ということはタブで切り替える表示の仕方はちょっとよろしくないかもな、と思えてきて。

クリックするとその部分だけ最小化される、みたいな何かは無いものか…。もしかして、 _QToolBox てのがソレだったりするのかな。と思ったけど、使用例が見当たらないな…。これはまた違うウィジェットなのかな…。どうなんだろ…。試してみないと分からんか…。

#2 [anime][neta] アニメの中の男性アイドル達は気楽でいいな

おそらくは女性向けなのであろう、男性アイドルキャラがわらわら出てくる某アニメを見ていたら、そのアイドル達が不良から金属バットで殴られたりパンチで殴り返したりしていて、しかしその後ステージに立ってキャーキャー言われる、という展開があって。

現実世界のアイドル(?)は、 _実際にはしてなくてもそういう話が出てきた だけで _「彼がいつまたみんなの前に姿を見せることができるようになるのか、私にもわからない。」 とかなっちゃうのに…。アニメの中のアイドル達って気楽でいいよなあ、と。いやまあ、リアルな脚本にすれば面白くなるわけでもないのでアレだけど。

どうせファンタジーなら、いっそ変形合体しながら世界の平和を守る男性アイドルグループアニメ、とかでもいいのかもってソレ昔どこかで見たような…。アイドルグループが歌うと敵兵がどんどん大人しくなる、てのもアリかもってソレもどこかで見たぞ…。

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

#1 [nitijyou] 朝方、大きな地震

今朝の06:00頃、福島県沖を震源とした大きな地震が。震度5弱の揺れだったらしいけど、体感で1分ぐらい揺れ続けてただろうか…。

その後も何度も余震が。「また揺れた」「また揺れた」って感じで。

震災時に家の耐震性は落ちてるだろうから、下手すると今度はとうとう崩れるかも、と思ったりもしたけど。今回も一応崩れずに済んだようで。とはいえ、これからしばらく、また震度5クラスの地震が来るかも、と気象庁が発表してるので、ちと気をつけないと…。

#2 [zatta] 津波観測網 S-netはどうなったんだろう

以前、NHK教育で放送されてる科学番組「サイエンスZERO」の中で、 _日本海溝海底地震津波観測網(略称、S-net) てのが紹介されてて。

震災が起きた当時、実験的に設置してた、新しい仕組みの津波・地震の観測装置が、比較的正確な津波の高さを「実測」で返していたらしいのだけど。当時はそのデータを受け取って活用する仕組みが官公庁側に一切用意されてなかったため、それらのデータは全部スルーされて津波の被害拡大を防ぐことに全く何一つ繋がらなかった、という極めて残念無念な話があって。

で、その時の教訓を活かして、320億円かけて東北一帯の太平洋側に、その観測網を広げて配備しますよ(しましたよ)、てのが件の観測網、なのだそうで。

_NIED|日本海溝海底地震津波観測網整備事業|整備計画
_津波の姿を20分早く、汚名返上へ東北沖で海底観測 :日本経済新聞

しかし今回、どうも津波の予測に関して、今までと同様にアバウトな予測値しか報道されなかったなと…。

NHKの夜7時のニュースで少し紹介されてたけど、どうやら気象庁の中では、これまで発生した地震と津波のデータベースが作ってあって、そのデータベースを元に「このくらいの津波が来るかもしれん」と発表してるそうで。

それも変な話だなと。おぼろげに「たぶんこのくらいじゃね?」と予想する仕組みじゃなくて、早い段階で津波の高さを「実測」できちゃう観測網が新たに配備されたはずなのに…。どうして今までと同じノリで発表してるんだろうと…。

実は、実測データは得られているけど相変わらず官公庁がスルーしてるのではあるまいか。もしかして、いわゆる縦割り行政云々で、システムが無視されている現状があるのでは…。下手すると気象庁の人達が「え? そんなシステムあるの? 知らなかった…」とか言い出すのでは…。

これがもし、「震源が海岸に近過ぎたので今回は活用できなかったのじゃ」とか「いやいやちゃんと反映させて予測値を出してるんよ? 以前よりは精度上がってるのよ?」てな話だったらそれはそれでってことになりそうだけど。万が一、320億円もかけたのに、気象庁内部の権力争いだか縄張り争いだかのせいで、システムがガン無視されてたら、さすがに国民側としてはここはブチキレておかないといかん場面ではなかろうかと。

ということで、件の観測網は今現在どうなってるんだろう、どういう扱いを受けているのだろうと気になったのでした。320億円はドブに、いや、太平洋に捨てられてしまったのだろうか。それともこれから活躍してくれる見込みがあるのだろうか。さて、どうなんだろう…。

ちょっとこのへん、マスコミさんに取材・検証してもらいたいところだよなと…。使われてないことが分かったら気象庁をフルボッコするネタが得られるし、ちゃんと使われてたら「日本の技術スゲー」「日本スゲー」的番組を作れるしで、どっちに転んでもマスコミさんにとっては美味しそうですけど。どうですかねえ…。

余談。 :

福島第一原発は震災時に津波で潮位計が壊れたままで今も直してないから、あのへんに津波が来てもどのくらいの津波が来たのか測りようもないし当然分からないまま、という話を見かけたのだけど、ホントなのだろうか…。ググってみたら、少なくとも2014年の時点では相変わらず壊れたままだったらしいですが。

今現在、そこらへん、どうなってるのだろう…。

アレかな。下手に津波の高さが観測できちゃうと何かと騒がれるからわざと直してない、みたいなソレかな。いかにも東電らしい、とにかく記録を残しちゃいかん、隠蔽すべし、みたいなソレが…。

いや、でも、さすがに今は直してあるのでは…。と思ったけど、朝の段階では福一にこのくらいの津波が来た、みたいな報道は無かったような…。

ググってみたら「福一に津波到達」「高さは不明」てなニュースを見かけた。不明、か…。やっぱり壊れたまま放置状態なのか…。

と思ったけど「1mの津波が来た」というニュースも見かけた。アバウトだな…。「たぶん1mぐらいじゃね?」「1mってことにしておこうか」とかやってるのかな…。どうも怪しいよなあ…。

む。「1mの津波が来た」のは福島第二原発、だったらしい。するとやっぱり福島第一原発は、今尚、津波が来ても高さが分からない、ということだろうか…。

一体どうなっているのやら。

#3 [pc] PS Vitaが無線LAN接続できず

妹が PS Vita を買ったらしいのだけど、ウチの無線LANルータ NEC Aterm WR8300N に接続できない、との相談が。

PS Vita のMACアドレスを無線LANルータ側に登録して、PS Vita がIPアドレスを取得するところまでは進むのだけど、DNSの取得でエラーが。

手動設定で、DNSサーバのIPアドレスを指定しても変化無し。プロバイダのDNSだけではなく、Google Public DNS も指定してみたけど、これも変化無し。何だろうな…。

DNSエラーが起きる症状についてググってみたら、ルータ側のIPv6を無効にせよ、みたいな話が。ソレをやっても大丈夫かな…。他のPCにも影響出ないかな…。いやまあ、IPv6を使ってるPCは居ないはず、ではあるけど…。

PS Vita側でIPv6を無効にできないのか、と妹に尋ねてみたら、システム設定のあたりを眺めてるうちに「アレ?」と言い出した。省電力設定を無効にしたらDNSエラーが無くなってネット接続できた、との話で。

ググってみたところ、環境によっては PS Vita の省電力設定を無効にしてずっと接続しっぱなしにしたほうが接続が安定する事例もあるようで。どうやらウチは、そのケースだったらしい…。

とりあえず勉強になった。省電力設定を無効にすると改善する可能性もある、のだな。

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

#1 [python] PySideのQToolBoxを確認

PySideの、QToolBoxの動作を確認してみたのだけど。

_qtoolbox_test.py
u"""
PySide + QToolBox の動作確認.

動作確認環境 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4
"""

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


class Dummy(QWidget):

    u"""仮表示用ウィジェット."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(Dummy, self).__init__(*argv, **keywords)
        l = QVBoxLayout(self)
        l.addWidget(QPushButton("Dummy", self))
        l.addWidget(QPushButton("Dummy", self))
        l.addWidget(QPushButton("Dummy", self))
        self.setLayout(l)


class MyWidget(QWidget):

    u"""メインウインドウ相当."""

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

        page1 = Dummy(self)
        page2 = Dummy(self)
        page3 = Dummy(self)

        qtb = QToolBox(self)
        qtb.addItem(page1, "page 1")
        qtb.addItem(page2, "page 2")
        qtb.addItem(page3, "page 3")

        l = QVBoxLayout(self)
        l.addWidget(qtb)
        self.setLayout(l)


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


if __name__ == '__main__':
    main()

qtoolbox_ss.gif

QTabWidgetを使った時と変わらんような…。「page x」をクリックすると、選んだウィジェットが表示されるけど、他のウィジェットが非表示になってしまう。そうじゃなくて、他のウィジェットは表示したまま、クリックしたところが開いたり閉じたりする動作を期待したのだけど…。

QTabWidgetを使った例。 :

ちなみに、QTabWidget を使った例は以下。

_qtabwidget_test.py
u"""
QTabWidgetのテスト.

動作確認環境 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4
"""

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


class Dummy1(QWidget):

    u"""仮表示用ウィジェット1."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(Dummy1, self).__init__(*argv, **keywords)
        l = QVBoxLayout(self)
        l.addWidget(QPushButton("Dummy1", self))
        self.setLayout(l)


class Dummy2(QWidget):

    u"""仮表示用ウィジェット2."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(Dummy2, self).__init__(*argv, **keywords)
        l = QVBoxLayout(self)
        l.addWidget(QPushButton("Dummy2", self))
        self.setLayout(l)


class TestQTabWidget(QWidget):

    u"""メインウインドウ相当."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(TestQTabWidget, self).__init__(*argv, **keywords)

        tab1 = Dummy1(self)
        tab2 = Dummy2(self)

        qtab = QTabWidget()
        qtab.addTab(tab1, "Tab 1")
        qtab.addTab(tab2, "Tab 2")

        l = QVBoxLayout()
        l.addWidget(qtab)
        self.setLayout(l)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = TestQTabWidget()
    w.show()
    sys.exit(app.exec_())

qtabwidget_ss.gif

これもまた、選んだウィジェットは表示されるけど、ソレ以外のウィジェットが非表示になる、という点は変わらないわけで。

ちと考えてること。 :

ボタンを押したら、ボタンのすぐ下に配置してあるウィジェットが非表示になる、みたいなカスタムウィジェットを作れば目的を果たせるのではあるまいか…。

しかし、QWidget の表示・非表示は、何のメソッドを呼べばできるのかな。ちと調べてみよう…。たぶん hide() とか setVisble() でイケるんじゃないかと思うけど…。

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

#1 [python] PySideでアコーディオンっぽいソレを実験

アコーディオンっぽいウィジェットを実現してみたい。クリックすると中身が開いたり閉じたりするアレ。

あのUI、名前が分からんのだけど。とりあえず以下のページでもアコーディオンって言ってるし、それでいいのかな…。どうなんだろ…。

_モバイルデバイスにおけるアコーディオンUI - U-Site

こんな感じだろうか…。アニメーションは無いけれど。

_hide_widget_test.py
u"""
Widgetの表示・非表示ができるかどうかのテスト.

動作確認環境 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4
"""

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


class AccordionWidget(QWidget):

    u"""アコーディオンモドキのカスタムウィジェット."""

    layout_changed = Signal()

    def __init__(self, title, parent=None):
        """init."""
        super(AccordionWidget, self).__init__(parent)
        self.title = title
        self.open_flag = True

        self.open_btn = QPushButton("", self)
        self.open_btn.setStyleSheet("text-align: left;")
        self.update_button_text()

        self.mainw = QLabel(self)
        self.mainw.setPixmap("./tmp_bg.png")

        l = QVBoxLayout(self)
        l.addWidget(self.open_btn)
        l.addWidget(self.mainw)
        self.setLayout(l)

        self.open_btn.clicked.connect(self.open_close_main_widget)

    def open_close_main_widget(self):
        u"""メインウィジェットを開いたり閉じたりする処理."""
        if self.open_flag:
            self.mainw.hide()
            self.open_flag = False
        else:
            self.mainw.show()
            self.open_flag = True
        self.update_button_text()
        # self.layout().update()
        self.updateGeometry()
        # self.update()
        self.layout_changed.emit()

    def update_button_text(self):
        u"""開閉ボタンのテキストを設定."""
        if self.open_flag:
            self.open_btn.setText(self.title)
        else:
            self.open_btn.setText(u"> " + self.title)


class TestQTabWidget(QWidget):

    u"""メインウインドウ相当."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(TestQTabWidget, self).__init__(*argv, **keywords)

        page1 = AccordionWidget("page 1", self)
        page2 = AccordionWidget("page 2", self)
        page3 = AccordionWidget("page 3", self)

        l = QVBoxLayout()
        l.addWidget(page1)
        l.addWidget(page2)
        l.addWidget(page3)
        self.setLayout(l)

        page1.layout_changed.connect(self.changed_layout)
        page2.layout_changed.connect(self.changed_layout)
        page3.layout_changed.connect(self.changed_layout)

    def changed_layout(self):
        u"""レイアウトが変更された時に呼ばれる処理."""
        # レイアウトを再計算させるときは以下のように書くらしい
        self.layout().activate()
        self.resize(self.sizeHint())


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = TestQTabWidget()
    w.show()
    sys.exit(app.exec_())

hide_widget_test_ss01.gif


QWidget を非表示にするのは、hide()。表示するのは show() でいいらしい。ちなみに最初は show() の代わりに setVisible(True) を呼んでたけど、そっちでも表示できた。

ウィジェットを表示した時はウインドウが大きくなってくれるけど、隠した時は小さくなってくれなくて悩んだり。レイアウトの再計算が必要なのだろうけど。

レイアウトを再計算してウインドウをリサイズする方法は、 _PySide and Python Tips - The Art & Tech of Christian Akesson で紹介されてた。
layout.activate()
self.resize(self.sizeHint())
これで、現状に合わせてサイズ変更されるようになった。

ボタン内のテキストを左詰めで表示したい時は、スタイルシートで指定するようで。
self.open_btn.setStyleSheet("text-align: left;")
こんな感じでいいらしい。

ボタンに記号を表示したいのだけど。 :

中身を開いてる時と閉じてる時で、ボタン上に異なる記号を表示したいのだけど、何を表示すればしっくりくるのか悩んでしまったり。

右向き・下向きの三角を表示したほうが分かりやすいのだろうか…。でも、その手の記号を含んでいて、どのOSでも使えるフォントってあるのかな。無さそうな気もするけど。

あるいは、「[+}」「[-]」みたいなソレのほうが…。いや、ソレはちょっと見た目がダサいかな…。

フツーは何を表示するんだろう。そういえば、Webページ上のソレは、記号の類は何も表示してない場合が多いか。実は何も表示しなくてもユーザ側はおおよそ使い方が分かる…のかなあ。どうなんだろ。

#2 [pc] クロスプラットフォームで使える記号って何だろう

Windows、Linux、Macで、共通して使える記号って何だろうなと。記号と言うか、全角記号を含んだフォント、なのだろうけど。

「+」「-」「=」等の半角記号ならどのOSでも使えるだろうけど、三角記号等は全角フォントじゃないと使えないのではないかな…。となると、どのOSでも使える日本語フォントがあるのかどうか、という話になってしまいそうな。

と思ってググり始めたのだけど。もしかして、 _Webdings_Symbol なら、どのOSでも使えたりするのだろうか? 少なくとも Ubuntu は、Webdings がインストールできるみたいだし、Mac は Webdings を持ってるみたいだし。であれば、わざわざ日本語フォントを探さなくても済むのかな。

_Ubuntu で Microsoft 系のフォントをインストール : Serendip - Webデザイン・プログラミング
_わかばマークのMacの備忘録 : 絵文字フォント「Webdings」「Wingdings」

でも、例えば PySide (PyQt)等で、ボタンのテキストに複数のフォントを指定、とかできるのかな…。最初の1文字は Webdings で、その後の文字列は _Arial とか。どうなんだろ。

余談。今まで知らなかったけど、Webdings っていきなりキーボードから英数字や半角記号を打つだけで表示されるのだな…。

#3 [nitijyou] 某所に行ってきた

Windows7が動いてるDELL製PC、OptiPlex 3020 が起動しない、という相談を受けたので某所まで。朝から雪が降っていて自転車で行くのはちと無理そうだったので、親父さんの運転する車で送り届けてもらった。AM09:00-AM11:20まで作業。

症状をメモ。 :

DELL製PCの電源スイッチLEDが黄色で点滅して起動しない、という症状で。DELL製PCに共通して起きる症状らしいので、一応メモ。

_電源ボタンが黄色に点滅してパソコンが起動しない場合の対処方法 | Dell 日本
_電源ボタンが黄色く点灯、または点滅してパソコンが起動しない場合の対処方法 (ビデオ) | Dell 日本

上記ページでは、確認作業として以下が提示されていたので試してみたり。一応、一昨日の時点で、某所内のPC大臣が既に一通り試したはずだけど、「とにかく様子を見てくれ」と言われてしまったので、念のために自分も試したり。
  • 外付け機器を外してみて、症状が変わるか確認。
  • ケースを開けて、HDD、光学ドライブ、M/Bに接続されたケーブルを全部差し直して、症状が変わるか確認。
  • ボタン型電池を外して、数分放置してから入れ直して、症状が変わるか確認。
  • メモリを外して、差し直して、症状が変わるか確認。

ボタン型電池を外したり、メモリを差し直したりしてから、電源ケーブルを差したところ、BIOS画面が表示された。メッセージに従ってF5キーを押してハードウェアチェックをしてみたところ、十数分ほど色々なチェックが走って、結果は異常無し。メニューからReboot(再起動)を選んだら、Windowsが起動した。

しかし、Windowsをシャットダウンさせたら、また電源LEDが黄色の点滅状態に。

そこから先は電源スイッチを押しても無反応。某所に到着した時と同様、電源が入らない状態に戻ってしまった。ケース裏の電源部分についてるスイッチを押すと、押した時だけ電源裏面のLEDが緑に光るけど、スイッチを離すと消灯したまま。何も変化無し。

DELLのサポートに電話して状況を報告してみたら、黄色の点滅のパターンでどこがおかしいのか少しは判別できるとの話で。等間隔で点滅してるか、2回点滅して少し間があいて1回点滅を繰り返してるかを質問された。後者のパターンであることを報告。

すると、「ケーブルを外して電源スイッチを15〜20秒ほど押し続けると内部放電する。症状が変わるかもしれないから試してほしい」とアドバイスが。その通りにしてみたら、BIOS起動・Windows起動に進んだ。

サポートの方の話では、「内部放電をしてみて起動するとなると、再現性がある症状として扱えない」とのことで。内部放電しても電源が入らなければ完全に故障扱いになるらしいけど…。仮に故障してるとしたら電源ユニットの可能性が高い、とも話していた。推測だけど、黄色の点滅が、2回+間+1回、なら電源が故障、ということなのかなと。こちらの勝手な推測なので違ってるかもしれんけど。

修理費用は、保証期間が切れているPCなので…。
  • 訪問修理の場合、約55,000円。
  • DELLに送って修理するなら、約30,000円。
  • 部品だけ取り寄せて自分で修理するなら約18,000円。
との話。更に、今から部品を手配しても来週頭に発送できるかどうか、とのこと。

某所としてはすぐにでも直してほしいそうで。さあ、困った…。

その他の詳細についてはGRPでメモ。

電源部分についてるスイッチについて。 :

_デスクトップパソコンが起動しない場合のトラブルシューティング | Dell 日本 によると、PC背面の、電源部分についてるスイッチやLEDは、自己診断スイッチ・自己診断ランプ、らしい。押してる間、LEDが緑に光る状態が正常で、何か壊れたハードウェアがどこかに接続されている場合、押しても光らない…という感じに読めるのだけど、違うのかな。

関連ページをメモ。 :

_DELLのオレンジ点滅:パソコン診療所のブログ
DELL製品の電源ランプのオレンジ点滅は、電源関連の不具合(電解コンデンサのパンク、ファンの不良など)が多い。

DELLのオレンジ点滅:パソコン診療所のブログ より


_Dellのパソコンの電源ランプがオレンジ色で起動しない対処方法 | エクセル・アクセスでデータベース管理
1.後ろの電源コードを抜いてください。抜いても緑色のランプがついたままではありませんか?
2.後ろの電源コードを抜いたまま、電源ボタンを押してください。当然、電源ボタンを押しても電源は入りません。
3.後ろの緑のランプが消えているでしょ! これで、問題は解決しました。

Dellのパソコンの電源ランプがオレンジ色で起動しない対処方法 | エクセル・アクセスでデータベース管理 より

えっ…。点灯したままだと問題が発生してる状態、だったのか…。

_電源ボタンが黄色に点滅してパソコンが起動しない場合の対処方法 ( その他趣味 ) - ブログ版−桐のイベント道場 - Yahoo!ブログ
背面の電源コンセント横の黒いスイッチを押して、LEDが点灯しなければ、電源ユニットに問題があるのだという。

電源ボタンが黄色に点滅してパソコンが起動しない場合の対処方法 ( その他趣味 ) - ブログ版−桐のイベント道場 - Yahoo!ブログ より

なるほど…。自己診断ランプで電源自体に問題があることが分かる、と…。

_パソコンの電源が入らない、電源ランプが点灯せず起動しない場合の対処方法 ( その他趣味 ) - ブログ版−桐のイベント道場 - Yahoo!ブログ
webを調べると分かるが、 「パソコンの電源が入らない、電源ランプが点灯せず起動しない」という話題は結構メジャーだっ。 なんでも、静電気が悪さをするらしい。

パソコンの電源が入らない、電源ランプが点灯せず起動しない場合の対処方法 ( その他趣味 ) - ブログ版−桐のイベント道場 - Yahoo!ブログ より


_Dellのパソコンで電源ランプがオレンジ色に点灯して起動しなくなったら | パソコンサポート IT-hands@福井市
Dellのサポートセンターに問い合わせたところ 考えられる原因として、
原因1.本体が帯電している。(電気が残っている)
原因2.メモリが気温で膨張している。(抜き差しを試すと改善すること多い)
原因3.本体内部にほこりが溜まっている。
原因4.本体内に熱がこもっている。

Dellのパソコンで電源ランプがオレンジ色に点灯して起動しなくなったら | パソコンサポート IT-hands@福井市 より

放電を試すように言われたのは、そういう事情があるから、なのか…。 ロスでは DELL製PCでは日常茶飯事、と…。

_Dell OptiPlex 3020 - ミニタワー オーナーズマニュアル

LEDの点滅が、2回+間+1回なら、M/Bで障害が、と書いてあるな…。電源がアレなわけじゃないのか…?

#4 [anime][zatta] 「買い切り」型の後にくるもの

思考メモ。

大昔のTVゲームは、ゲームセンターに行かないと遊べなかった。100円を入れると自分が負けるまで遊べる。そういう形で提供されてた。そのうち、家庭用ゲーム機が発売されて、ゲームソフトを1本単位で「買い切り」をして遊べるようになった。

大昔の漫画は、貸本屋さんで借りないと読めなかった。そのうち、雑誌を売ったり、単行本を売ったりする形で漫画が普及し始めた。読者は、一冊単位で「買い切り」をして漫画を読めるようになった。

大昔のアニメは、映画館に行かないと見れなかった。料金を払って、映画館に入って、館内に居る間は視聴することができた。そのうち、TVが家庭に普及して、映画館に行かなくても毎週見れるようになったけど。録画機器が無かったから、そのアニメが放送されてる時間、TVの前に座ってないと見れなかった。さらにそのうち、ビデオデッキが発売されて、お金を持ってる人は1本単位でアニメ(が入ったビデオテープ)を「買い切り」して視聴できるようになった。そして今では、ビデオテープはBDやDVDに置き換わり、BDやDVDを売ることでアニメ業界が成立するようになった。

TVゲームと、漫画と、アニメ。どれも展開が似てるよなと。

そして、今、TVゲームはどうなっただろう。

今も、アーケードゲームは存在するし、家庭用ゲーム機向けにゲームソフトが販売されてたりするけれど。おそらく勢いがあるのは、オンラインゲームというか、スマホゲーというか、そのあたりだろうなと。

ということは、いずれアニメも…。

と安易に思ってしまったけれど、スマホゲーの場合は「サービス」として提供されてるわけで。しかも時々アップデートされたりするわけで。

アニメの場合は、サービス提供、という形ではないよなと。アップデートもされないし。

となると、TVゲームとはまた違う展開になるのだろうか。

オチはないです。漠然と、「どうなるのかなー」と思っただけで。

未来のアニメオタク・アニメマニアのスタイルはどうなってるんだろう。山ほど記録メディアを持っているタイプも残っているだろうけど、おそらくそれは主流のスタイルじゃなくて…。例えば、未来世界を舞台にした漫画・アニメ・ゲーム作品内に登場する、未来のアニメオタク像とは…。どういうスタイルになってるのかな…。

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

#1 [python] PySideとPyQt4を両方入れているとImageQtがエラーを出す問題

PySide を使って書いていたスクリプトが、エラーを出すようになって悩んだり。

Pillow(PIL)の ImageQt を使うと、PIL の Image を PySide の QImage に変換できる、はずなのだけど、何故か該当行でエラーが出てしまって。こないだまで動いてたのに変だな変だなと。

考えてみたら、各種サンプルを動かすために PyQt4 をインストールしてから、この症状が出始めたような…。試しに PyQt4 をアンインストールしてみたら、エラーが出なくなった。

以下のページで、同様の不具合報告と回答があった。

_python - cannot use imageQt to cast Image to QImage - Stack Overflow

PIL の ImageQt は、PyQt を使ってることを前提に書かれているので、PySide と PyQt の両方が入ってる環境では PyQt のほうを優先して使おうとして、PySide を使ったスクリプトではエラーが出てしまうらしい。

一行入れると問題回避できるかも、と書いてあったので試してみたり。
u"""
Pillow(PIL)のImageQtについて動作確認.

動作確認 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4
"""

from PySide import QtGui
import sys
from PySide.QtCore import *  # NOQA
from PySide.QtGui import *   # NOQA
sys.modules['PyQt4.QtGui'] = QtGui   # PyQt4を使わないようにしている
from PIL import Image
from PIL import ImageQt


class MyWidget(QWidget):

    """main window."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(MyWidget, self).__init__(*argv, **keywords)
        img = Image.open("./tmp_00_default.png")
        img = img.convert("RGBA")
        qimg = ImageQt.ImageQt(img)
        pm = QPixmap.fromImage(QImage(qimg))

        lbl = QLabel(self)
        lbl.setPixmap(pm)
        self.setLayout(QVBoxLayout())
        self.layout().addWidget(lbl)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyWidget()
    w.show()
    sys.exit(app.exec_())

たしかに、1行入れてみたらエラーが出なかった。とメモ。

#2 [python] PySideのアレコレをメモ

PySide関係のアレコレをメモ。

QGraphicsSceneで表示してるアレコレの透明度。 :

QGraphicsView + QGraphicsScene に表示しているアイテムの透明度は、 _setOpacity で変更できる。指定できる値は、0.0〜1.0。0.0が透明で、1.0が不透明。

ウィジェットの隙間を少なくしたい。 :

PySideを使ったスクリプトを書いてたら、メインウインドウの大きさが、かなり大きくなってしまって。このままでは、例えば1366x768ドットのデスクトップ上で実行すると、はみ出して表示されてしまう…。

安いノートPCはえてして1366x768ドットの液晶を使ってるので、そのあたりの解像度が最低ラインだろうと。この解像度からはみ出してしまうようではマズい。 *1 ということで、アプリのウインドウサイズ、特に縦幅を小さくしないといけないなと。

とりあえず、 _QVBoxLayout の、中に入るウィジェットの隙間を小さくしていくことに。 _setSpacing() を呼ぶことで、隙間のドット数を指定することができる。0〜2ドットぐらいを指定してみたら、結構小さくなってくれた。

更に、Q*Layout の周囲の余白も少なくする。 _setContentsMargins() を使えば、周辺の余白を調整することができる。

これでようやく、ウインドウの縦幅を740ドットぐらいまで小さくできた。

ちなみに、今回書いてるアプリに特化すれば、別の策はいくつか思いつく。
  • 今は左側のドックだけを使って各種設定項目を並べているけど、右側のドッグにいくつか項目を引っ越しして表示する。ウインドウの横幅は無駄に(?)大きくなるけど。
  • ツールバーを上部に並べるのではなく、スクリプト起動時から左側に、縦一直線に並べることができれば…。だけど、そんなことできるのかな?
  • パレット表示部分と、RGB・HSLスライダー部分を、タブ表示で切り替える、という手もある。けれど、できればそこは両方を同時に表示しておきたいわけで。
  • むしろ、ブラシ種類一覧部分を隠せるようにしておいたほうがいいのかも。例えばGIMPは、ブラシのオプション設定と、ブラシ種類一覧が分離しているし。絶えず同時に表示しておかないと使い勝手が悪くなるわけではない、ということだろう。
  • 設定関係部分を、スクロールエリアに入れてしまう。ただ、目的の個所を表示・操作するのが面倒になる。
  • MDI風の見た目にしてしまう。

*1: もっとも、自分の手持ちのノートPC(昔、ネットブックと呼ばれてたソレ)は、画面解像度が 1024x600 だったりするので、本当はそのくらいを最低ラインとして考えておくべきかもしれない。けど、さすがに今時、その解像度では実用にならないだろうし、そういうPCについては考えなくてもいいのかもしれない。そのあたりの解像度の液晶を使ってるPCは、CPUもGPUもスペックは期待できないので、その手のスクリプトも満足な速度で動かないだろうし。

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

#1 [python] 塗りつぶし処理(floodfill)について

お絵かきアプリでよくある、閉じた範囲の中でクリックすると指定色で塗り潰す処理を、Python + PySide (+ PIL(Pillow)) を使って試したい。どうやら、その手の処理を、 _Flood fill と呼ぶらしいけど。

Ruby + DXRuby を使った版なら、昔書いたことがあったようで。 _2015/12/24の日記 にメモしてあった。Flood fill の一つ、Scanline Seed Fill を試していた模様。

コレを Python + PySide に移植してもいいのだけど、もしかしてこの手の処理は既に用意されているんじゃないか、と思ってググっていたら、どうやら PIL (Pillow) に、そのものズバリの floodfill というメソッドがあるようで。ただ、文書化はされていない模様。

_python - How to flood-fill part of a bitmap enclosed by a black border with my chosen color? - Stack Overflow

>>> from PIL import Image, ImageDraw
>>> help(ImageDraw.floodfill)
Help on function floodfill in module PIL.ImageDraw:

floodfill(image, xy, value, border=None)
    (experimental) Fills a bounded region with a given color.
    
    :param image: Target image.
    :param xy: Seed position (a 2-item coordinate tuple).
    :param value: Fill color.
    :param border: Optional border value.  If given, the region consists of
        pixels with a color different from the border color.  If not given,
        the region consists of pixels having the same color as the seed
        pixel.
たしかに入っているっぽい。だったら、コレを使わせてもらおうか…。

_floodfill_test.py
u"""
PIL(Pillow)のfloodfillを試す.

PIL(Pillow)には、文書化されていないが floodfill という関数があって、
塗りつぶし処理ができるらしい。

動作確認環境 : Windows10 x64 + Python 2.7.12 + Pillow 3.4.2
"""

from PIL import Image
from PIL import ImageDraw
import time

im = Image.open("./tmp_image.png")

start = time.time()
ImageDraw.floodfill(im, (64, 64), (255, 0, 0, 255), border=None)
ImageDraw.floodfill(im, (200, 240), (0, 255, 0, 255), border=None)
ImageDraw.floodfill(im, (480, 400), (0, 0, 255, 255), border=None)
etime = time.time() - start
print("%f" % etime)

im.save("./tmp_image_result.png")

以下の画像を渡してみる。
tmp_image.png

以下の結果が得られた。
tmp_image_result.png

塗りつぶしができてる。

3回塗り潰してみたけど、手元の環境で時間を測ってみたら 0.433000秒だった。そんなに遅くはない、かな…。

ググってみたら、以下のページが。

_python-imaging 1.1.7 - namespace_p_i_l_1_1_image_draw.html

この floodfill() は Python で書かれてる、ということかな…。上下左右を1ドットずつ見ていく、という比較的シンプルなアルゴリズムっぽい。

QImageからPIL Imageへの変換。 :

塗りつぶしは PIL の floodfill() を利用させてもらうとして。PySide上で使おうとするなら…。
  • QImage から PIL Image への変換。
  • PIL Image から QImage への変換。
この2つが必要になりそうだなと。

PIL Image から QImage への変換は、PIL の ImageQt を使えばできるはず、ということになってるけど。逆はどうすれば…。

ググったら以下のページが。

_setting python, eclipse, opencv and QT in linux: Converting from qimage to pil image
_PIL.Image と PyQt4.QtGui.QImageの相互変換 - None is None is None

バッファとやらを使って、一旦画像ファイルにして記録してから、また画像ファイルとして読み込む、ということをすれば変換できなくもないらしい。

ただ、PIL の ImageQt にも似たような処理をしてる部分があるような…。

_Pillow/ImageQt.py at master - python-pillow/Pillow

fromqimage() てのがあるけど、やってることが似ている…。が、pil_im = ImageQt.fromqimage(qim) とかやってみたらエラーが。

アレコレ試してたら、そもそも PySide + Pillow の組み合わせで ImageQt を使うと Python が不正終了・crash することに気が付いた。PIL Image から QImage への変換すらできないのでは困る。コレは別項でメモしたほうがいいかな…。

#2 [python] Windows + PySide + Pillow(PIL)の組み合わせでImageQtの動作が怪しい

Windows10 x64 + Python 2.7.12 + PySide 1.2.4 + Pillow(PIL) 3.4.2 の環境で、Pillow に入ってる ImageQt を使って PIL Image を PySide の QImage に変換しようとしていたのだけど、Python が強制終了・crashしまくることに気づいて悩んだり。

ちなみに、PySide 1.2.4 と PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe を両方インストールしてある環境。

例えば、以下のソースと画像を使って実験すると…。

_imageqt_test.py
u"""
Pillow(PIL)のImageQtについて動作確認.

動作確認 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4 + Pillow 3.4.2
"""

use_pyside = True

import sys

if use_pyside:
    # use PySide

    # import PySide
    # sys.modules['PyQt4'] = PySide

    # ここでPyQt4を使わないようにしているはず…なのだが…
    from PySide import QtGui
    from PySide import QtCore
    sys.modules['PyQt4.QtGui'] = QtGui
    sys.modules['PyQt4.QtCore'] = QtCore

    from PySide.QtCore import *  # NOQA
    from PySide.QtGui import *   # NOQA
else:
    # use PyQt4
    from PyQt4.QtCore import *  # NOQA
    from PyQt4.QtGui import *   # NOQA

from PIL import Image
from PIL import ImageQt


class MyWidget(QWidget):

    def __init__(self, *argv, **keywords):
        super(MyWidget, self).__init__(*argv, **keywords)

        layout = QGridLayout()

        lst = [
            "./tmp_grayscale.png",
            "./tmp_indexed.png",
            "./tmp_rgb.png",
            "./tmp_rgba.png"
        ]

        for i, fpath in enumerate(lst):
            im = Image.open(fpath)
            old_im_mode = im.mode

            if im.mode != "RGBA":
                im = im.convert("RGBA")
            print("im.mode %s -> %s" % (old_im_mode, im.mode))

            qim1 = ImageQt.ImageQt(im)

            qim2 = QImage(qim1)
            pm = QPixmap.fromImage(qim2)
            lbl = QLabel(old_im_mode, self)
            lbl.setPixmap(pm)
            layout.addWidget(lbl, i / 2, i % 2)

        self.setLayout(layout)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyWidget()
    w.show()
    sys.exit(app.exec_())

_tmp_grayscale.png
_tmp_indexed.png
_tmp_rgb.png
_tmp_rgba.png

やってることは、 してるだけ、のはずだけど。

コレを実行すると、Python が死ぬ。
python_crash_ss.png


これがまたよく分からんのだけど、PySide を使わずに PyQt4 を使うと Python が死なずに動いちゃう。

use_pyqt4_result_ss.png

最初のあたりの use_pyside = True を use_pyside = False にすると、PyQt4 を使って動かせる。

各行をコメントアウトして動作確認してみたけど、どうも QPixmap.fromImage() を呼んだ時点で Python が死んでる感じ。QPixmap.fromImage() をコメントアウトすると Python が死なないわけで。

すると ImageQt が QImage を返せていないのか、おかしな QImage を QPixmap.fromImage() に渡してしまっているのか。と思ったけど、 _Pillow/ImageQt.py at master - python-pillow/Pillow を眺めると、ImageQt は QImage を継承してるわけで…。github上の ImageQt.py と、手元の ImageQt.py は、どちらも似たようなソースだし…。

アレ? 待てよ? 微妙に違うな。

_Pillow 2.9 causes PySide application to crash (whereas 2.8.2 does not) - Issue #1370 - python-pillow/Pillow

上記のバグ報告ページで、self.__data = im_data['data'] を追加してソレを使って QImage を生成すると落ちない、という報告が。手元の ImageQt.py はその行が入ってない。

手元の ImageQt.py にも試しに反映させてみたら、落ちなくなった。

しかし困ったな…。これでは他の環境にスクリプトを持っていった際に ImageQt が使えないではないか…。一々、「ImageQt.py をエディタで開いて該当行を修正してください」とお願いするのもアレだし…。

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

#1 [python] PySide の QImage を使って PIL Image と同様のモード結果を得たい

PIL Image は、Image.mode で画像モードを調べることができる。

例えば…。
>>> from PIL import Image
>>> im = Image.open("hoge.png")
>>> im.mode
とでもすれば、文字列で画像モード? 画像フォーマット? が分かる。

コレと似たようなことを、PySide の QImage を使ってやりたいなと。

_image_format_out.py
u"""
QImageを使って画像フォーマットを確認する.

L(グレースケール)、P(インデックスカラー)、RGB、RGBAを判断する。

動作確認 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4 + Pillow 3.4.2
"""

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


class MyWidget(QWidget):

    u"""メインウインドウ相当."""

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

        l = QVBoxLayout()
        tb = QTextEdit(self)
        l.addWidget(tb)
        self.setLayout(l)

        lst = [
            "./tmp_1bit.png",
            "./tmp_grayscale.png",
            "./tmp_indexed.png",
            "./tmp_rgb.png",
            "./tmp_rgba.png"
        ]

        for i, fpath in enumerate(lst):
            qim = QImage(fpath)
            ret = self.get_image_mode(qim)
            tb.append("%s is %s" % (fpath, ret))

    def get_image_mode(self, qim):
        u"""画像モードを調べて返す.

        PIL Image.mode と同様、1,L,P,RGB,RGBAで返す。
        """
        ret = ""
        fmt = qim.format()
        if fmt == QImage.Format_Mono:
            ret = "1"
        elif fmt == QImage.Format_MonoLSB:
            ret = "1"
        elif fmt == QImage.Format_Indexed8:
            ret = "L" if qim.allGray() else "P"
        elif fmt == QImage.Format_RGB32:
            ret = "RGB"
        elif fmt == QImage.Format_ARGB32:
            ret = "RGBA"
        else:
            ret = "UNKNOWN"
            print(f)
        del qim
        return ret

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyWidget()
    w.show()
    sys.exit(app.exec_())

テスト画像は以下。

_tmp_1bit.png
_tmp_grayscale.png
_tmp_indexed.png
_tmp_rgb.png
_tmp_rgba.png

こんな結果に。

image_format_out_ss01.png

QImage.format() で、 _QImage.Format の値が得られるので、それを使って画像モードを判別。

QImage の場合、グレースケール画像も、インデックスカラー画像も、QImage.Format_Indexed8 が返ってきてしまって悩んだけど。 _QImage.allGray() を使えば、パレット内容がグレースケールか、そうでないかが分かるので、そこからグレースケール or インデックスカラーを判別できた。

#2 [python] PySide + Pillow(PIL) で塗り潰し

Pillow(PIL)の、ImageDraw.floodfill(PILImage, (x, y), (r, g, b, a)) を利用して、PySideのウインドウ上で塗り潰し処理を試したり。

_qimage2pil_test2.py
u"""
QPixmap → QImage → PIL Image に変換して塗り潰しの処理をしてみる.

ImageQtに相当するクラスをスクリプト内に置いてみた版。
Pillow のバージョンが上がれば、このクラスは不要になるかもしれない。

動作確認環境 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4 + Pillow 3.4.2
"""

import cStringIO
import sys

# ここでPyQt4を使わないようにしているはず…なのだが…
from PySide import QtGui
from PySide import QtCore
sys.modules['PyQt4.QtGui'] = QtGui
sys.modules['PyQt4.QtCore'] = QtCore

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

from PIL import Image
from PIL import ImageDraw
# from PIL import ImageQt


class ImageQtPoor(QImage):

    u"""ImageQt substitute. Supports only RGBA image."""

    def __init__(self, im):
        """convert PIL Image to PySide QImage."""
        self.org_mode = im.mode
        if im.mode != "RGBA":
            im = im.convert("RGBA")
        w, h = im.size
        fmt = QImage.Format_ARGB32
        self.__data = im.tobytes("raw", "BGRA")
        super(ImageQtPoor, self).__init__(self.__data, w, h, fmt)

    @staticmethod
    def fromqimage(qim):
        """convert PySide QImage ro PIL Image."""
        buf = QBuffer()
        buf.open(QIODevice.ReadWrite)
        qim.save(buf, "PNG")
        fp = cStringIO.StringIO()
        fp.write(buf.data())
        buf.close()
        fp.seek(0)
        return Image.open(fp)

    @staticmethod
    def toqimage(pim):
        """convert PIL Image to PySide QImage."""
        fp = cStringIO.StringIO()
        pim.save(fp, "PNG")
        qim = QImage()
        qim.loadFromData(fp.getvalue(), "PNG")
        return qim


class GView(QGraphicsView):

    u"""画像アイテム表示用."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(GView, self).__init__(*argv, **keywords)
        self.setBackgroundBrush(Qt.gray)
        scene = QGraphicsScene(self)
        self.setScene(scene)

        pm = QPixmap("./tmp_rgba.png")
        self.pm_item = QGraphicsPixmapItem(pm)
        scene.addItem(self.pm_item)

    def mousePressEvent(self, event):
        u"""マウスクリックした際に呼ばれる処理."""
        p = self.mapToScene(event.pos())
        x, y = int(p.x()), int(p.y())

        pm = self.pm_item.pixmap()         # QPixmapを取得
        qim = pm.toImage()                 # QImageに変換
        pim = ImageQtPoor.fromqimage(qim)  # PIL Imageに変換
        if pim.mode != "RGBA":
            pim = pim.convert("RGBA")

        # 塗り潰し(Flood Fill)をする
        ImageDraw.floodfill(pim, (x, y), (128, 0, 64, 255))

        qim2 = ImageQtPoor(pim)            # PIL Image → QImageに変換
        # qim2 = ImageQtPoor.toqimage(pim)

        pm2 = QPixmap.fromImage(qim2)      # QPixmapに変換
        self.pm_item.setPixmap(pm2)


class MyWidget(QWidget):

    u"""メインウインドウ相当."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(MyWidget, self).__init__(*argv, **keywords)
        gv = GView(self)
        l = QVBoxLayout()
        l.addWidget(gv)
        self.setLayout(l)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyWidget()
    w.show()
    sys.exit(app.exec_())

qimage2pil_test2_ss01.gif

クリックすると、クリックした位置から塗り潰しができる。今回は只の動作確認なので、塗り潰し色は固定。

現状、PySide 1.2.4 と Pillow(PIL) 3.4.2 の組み合わせでは、ImageQt を使って PIL Image から QImage へ変換すると Python が死んでしまう不具合が発生する時があるけど。RGBA しか使わないという前提にすれば、処理内容が数行で済むので、各スクリプト内に処理を書けそうだな、と思えてきたので上記のスクリプトではそのように書いてみたり。

#3 [python] PySide の QImage を QPixmap に変換するとアルファチャンネルが消える問題

QImageの全ドットが不透明な状態で、QPixmap.fromImage(QImage) を使って QPixmap に変換すると、QPixmap のアルファチャンネルが消滅する、という問題に遭遇。

PySide.QtCore.Qt.ImageConversionFlag の、Qt.NoOpaqueDetection を指定してやれば問題を回避できる。QPixmap.fromImage(QImage, Qt.NoOpaqueDetection) といった感じで指定する。以下のドキュメント内で、ImageConversionFlag でページ検索すれば説明が読める。

_Qt - PySide v1.0.7 documentation

参考までに、Google翻訳の結果をそのまま引用。
Qt.NoOpaqueDetection

イメージに不透明でないピクセルが含まれているかどうかを確認しないでください。 画像が半透明で、不透明でないピクセルが見つかるまで画像のピクセルをチェックするオーバーヘッドを避けたい場合や、その他の理由でピックスマップにアルファチャンネルを保持させたい場合は、これを使用します。 画像にアルファチャンネルがない場合、このフラグは効果がありません。
全ドットに対して、透明・半透明部分の有無をチェックしながら変換してたのか…。

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

#1 [python] PySideでカーソルを変更

QCursor でカーソルを作って、setCursor() でマウスカーソルを変更できる。

_qcursor_test.py
u"""
QCursorのテスト。マウスカーソルを変更する.

動作確認環境 : Windows10 x64 + Python 2.7.12 + PySide 1.2.4
"""

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


class GView(QGraphicsView):

    """GraphicsView."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(GView, self).__init__(*argv, **keywords)

        scene = QGraphicsScene(self)
        self.setScene(scene)

        pm = QPixmap(320, 240)
        pm.fill(QColor(192, 192, 192, 255))
        pitem = QGraphicsPixmapItem(pm)
        scene.addItem(pitem)

        lst = [
            ("pen", "./mouse_cursor_pen.png", 0, 0),
            ("eraser", "./mouse_cursor_eraser.png", 0, 0),
            ("picker", "./mouse_cursor_picker.png", 9, 22),
            ("fill", "./mouse_cursor_fill.png", 9, 22),
        ]
        self.curs = []
        for d in lst:
            name, filepath, hx, hy = d
            cur_pm = QPixmap(filepath)
            cur = QCursor(cur_pm, hotX=hx, hotY=hy)
            self.curs.append(cur)

        self.cur_idx = 0
        self.setCursor(self.curs[self.cur_idx])

    def mousePressEvent(self, event):
        u"""マウスボタンが押された時の処理."""
        # 左もしくは右ボタンを押したらマウスカーソルを変更
        if event.button() == Qt.LeftButton:
            self.cur_idx = (self.cur_idx + 1) % len(self.curs)
        elif event.button() == Qt.RightButton:
            self.cur_idx = (self.cur_idx - 1 + len(self.curs)) % len(self.curs)
        else:
            return

        self.setCursor(self.curs[self.cur_idx])  # マウスカーソルを設定

    def mouseReleaseEvent(self, event):
        u"""マウスボタンが離された時の処理."""
        pass
        # self.unsetCursor()


class MyWidget(QWidget):

    """Main Window."""

    def __init__(self, *argv, **keywords):
        """init."""
        super(MyWidget, self).__init__(*argv, **keywords)
        l = QVBoxLayout()
        l.addWidget(QLabel("Dummy", self))
        l.addWidget(QLabel("Dummy", self))
        l.addWidget(GView(self))
        l.addWidget(QLabel("Dummy", self))
        l.addWidget(QLabel("Dummy", self))
        self.setLayout(l)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyWidget()
    w.show()
    sys.exit(app.exec_())

マウスカーソル画像は以下。EDGE2を使って自作してみたり。

_mouse_cursor_pen.png
_mouse_cursor_eraser.png
_mouse_cursor_picker.png
_mouse_cursor_fill.png

実行すると、こんな感じに。マウスクリックでマウスカーソルを変更できる。

qcursor_test_ss01.gif

ソースと画像素材は、CC0 / Public Domain ってことで。

_QCursor - PySide v1.0.7 documentation を読むと、1bit画像を2枚指定する設定の仕方で、xor表示ができる、と書いてあるように見えるのだけど。よくよく読んでみると、Windows では xor表示できるけど他の環境ではどうなるか知らんよ、と書いてあった。となると、クロスプラットフォームを意識して使うなら xor表示は避けたほうがいいのかもしれない。

xor表示ってのは…。おそらくだけど、下地が黒ならマウスカーソルは白に、下地が白ならマウスカーソルが黒に、みたいな表示じゃないかと想像するけど、どうなんだろう。

#2 [anime] 「けいおん!」2期、文化祭の回を視聴

NHK-BSで「けいおん!」が放送されてるので録画して見ているのだけど。とうとう例の文化祭の回が。

やっぱり名作、というか名シーンだなと…。いかん…鼻水が…。

#3 [movie] 「プリンス・オブ・ペルシャ 時間の砂」を視聴

たまたまTVをつけたらNHK-BSで流れていたので途中から視聴。画面に映ったタイトルを目にしてビックリ。まさか、あのゲームの実写映画版? 恥ずかしながら存在を知りませんでした。そうか…アレって映画化されてたのか…。

ググってみたら、2004年の、今風にリメイクされたゲーム版、の映画化だそうで。そりゃそうか。PC-9801とかそのあたりのPCで動いてたアレを今になって映画化するわけないよなと。

内容は、フツーに面白かった。アクションシーンはハリウッド映画的なソレで、おそらくはゲーム版を連想させるのであろうアクションも盛り込まれてる、ような気配を感じたし。ラストのあたりはなんだかロマンチックなソレだし。誰でもそれなりに楽しめる系の映画、てな印象。

なのだけど。これで赤字だったらしい…。なんでや…。

#4 [zatta][anime][neta] 氷の○○の上で鉄郎とメーテルがキャッキャウフフとスケートしてたらどんな印象を受けるのだろう

ちとネタバレだけど…。銀河鉄道999の冥王星の回で、氷の 墓地が出てくるけど。

あの上で、鉄郎とメーテルが楽しそうにスケートしてたら、視聴者はどんな印象を持ったのだろうかと、なんだか妄想を。

「スケートなんか初めてだよ」と言いながら、メーテルに滑り方を教えてもらう鉄郎。そのうち慣れてきて、「どうだいメーテル。この滑り」と例のごとく調子に乗り始めて。そこでふと、足元のアレに気づく。「うわっ! なんだこりゃ」と驚いて尻餅を。メーテルが「この上で滑るのが、冥王星に住む機械化人間達の、娯楽の一つなのよ」と説明したりして。

鉄郎のことだから…。たぶん、一言ポロッと。「うへえ…悪趣味だなあ…」とか言いそうだなと。

てなシーンを見て、「オイオイ、鉄郎。お前は分かってないなあ」「それってとっても素敵な光景じゃないか!」「嗚呼、俺もそういう場所で滑りたいなあ」と言い出す視聴者って、どの程度居るんだろう。

てな感じの妄想が、 _例のスケートリンク 関連記事を目にして浮かんできました。

もはや日本人は、精神的に、機械化人間ばかりなのかもしれない…。

それとは別に。

TV番組等で、食べ物を粗末にする系の企画が流れる際は、「この後スタッフが美味しくいただきました」てなテロップが出てくることがあるけれど。アレがあることで建前上許してもらえている、みたいなところがあるよなと。

であるから当然、例のスケートリンクも、「この後スタッフが美味しくいただきました」をやってくれないと困るよなと。ソレが無いと許されないよなと。しかし、5千匹か…。完食できるのか…。まさか廃棄処分とかしないよな。例えばTV番組でそんなことしたら、その後の展開は誰でも容易に想像できるはずで。 *1

何にせよ、氷漬けにした光景をイメージできる、そういう種類の想像力は溢れてるけど、そういうことをしたらこうなる、という種類の想像力は乏しいのだろうな…。

故・藤子F先生なら、似たような漫画を描いてくれそう。地球人の死体を氷漬けにした、その上で、宇宙人がスケートしていて。宇宙人同士が「命に対する冒涜だ。けしからん」「死んでるんだから構わんだろ。何を怒ってるんだ」と喧々諤々、している様子を物陰に隠れて震えながら見ている、地球人の主人公。みたいな。や、そういう感じの作品、あったけど。藤子F先生のSF漫画は、名作揃いですよ…。
*1: と思ったけどもしかして、飼ってる動物達に食べさせるのかもしれないか…。 _件の施設はカピバラを飼ってる ようだけど、 _ヤツラは魚も食べる ようだし。仮に、どうせ冷凍庫で氷漬けにして大量に保管してるなら畜生共に食べさせる前にもう一働き、という話なら少しは違ってくるのだろうか。どうなんだ。

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

#1 [python] Pythonスクリプトをexe化

Windows + Pythonの環境で、Pythonスクリプト(PySideを使ったスクリプト)をexe化したいなと。

exe化する方法はいくつかあるようで。
_Packaging PySide applications on Windows - Qt Wiki

一番簡単なのは Pyinstaller らしいけど、exe化したソレの起動が遅いという話を見かけて。なので今回は、設定ファイルを書かないといけない代わりに起動が比較的速いと評されてる py2exe を試してみたり。

ただ、この py2exe、Python 2.7 までしか対応してないらしい。自分の手元の環境では Python 2.7 を使ってるからいいけれど、もし Python 3.x を使ってる場合は別の選択肢を選ぶことになるのだろうなと。

py2exeのインストール。 :

_py2exe は、 _py2exe - Browse /py2exe/0.6.9 at SourceForge.net からダウンロード。py2exe-0.6.9.win32-py2.7.exe を入手。

実行すると、Python 2.7インストールフォルダ\Lib\site-packages\py2exe\ のあたりにインストール、してくれたのかな。たぶん。

設定ファイル setup.cy を書く。 :

以下のページを参考にして記述。

_py2exeモジュールについて ・ PythonMatrixJp
_exe変換(py2exe) - Python入門から応用までの学習サイト
_py2exeでPythonのスクリプトを実行ファイル(exe)にする【py2exe】 - ぴよぴよ.py
_[Python] pyInstallerの遅さにがっかりしてpy2exeにしたら爆速で驚いた件 ・ Nobwak's Lair
_py2exe のハマりどころメモ - プログラマのネタ帳
_Pythonでプログラムを作るときのTips - つかさのほえほえ日記
_PyQt でクロスプラットフォームなデスクトップアプリケーションを | Kosei Kitahara's Blog
_PyQt を py2exe でフリージング | Kosei Kitahara's Blog

今回は PySide を使ってるので、以下のように書いてみた。
# -*- coding: utf-8 -*-
"""py2exe config."""

import sys
import os

if sys.platform == "win32":
    # Windows
    import py2exe
    from distutils.core import setup

    IMAGELIB_DIR = r"D:\Python\Python27\Lib\site-packages\PySide\plugins\imageformats"
    imgfiles = [os.path.join(IMAGELIB_DIR, i) for i in [
        "qgif4.dll",
        "qgifd4.dll",
        "qico4.dll",
        "qicod4.dll",
        "qjpeg4.dll",
        "qjpegd4.dll",
        "qmng4.dll",
        "qmngd4.dll",
        "qsvg4.dll",
        "qsvgd4.dll",
        "qtga4.dll",
        "qtgad4.dll",
        "qtiff4.dll",
        "qtiffd4.dll",
    ]]

    data_files = [("imageformats", imgfiles)]

    py2exe_options = {
        "compressed": 1,
        "optimize": 2,
        "bundle_files": 3
    }

    setup(
        data_files=data_files,
        options={"py2exe": py2exe_options},
        # console = [
        windows=[
            {"script": "stampixelart.py"}
        ],
        zipfile="lib/libs.zip"
    )

if sys.platform == 'darwin':
    # OS X
    pass

if sys.platform == 'linux2':
    # Linux
    pass

IMAGELIB_DIR のあたりは自分の環境に合わせて書き換える必要有。もしかすると setup.cfg なるものを書いてこのへん設定すべき、なのかもしれないけれどサンプルが見つからなくて…。

PySide の標準状態では扱える画像フォーマットがそれほど多くないので、plugins を使って svgその他に対応してるらしいのだけど。py2exe はそのあたりイイ感じにしてくれない。よって、必要になりそうな dll を imgfiles のあたりで全部列挙して data_files で渡してやって「コレもコピーしといてくれよな」と py2exe に指示してる、のだと思う。

py2exe に渡すオプションとしては compressed、optimize、bundle_files 等があるようだけど。"bundle_files" : 3 にすると、1つの exeファイルにアレもコレも入れるのではなく、各ファイルをバラバラにしておいてくれるらしいので、そのように。というのも、PySide ってライセンスがLGPLだったから、それぞれ個別のファイルになってないとマズいのかなと思ったわけで。それと、3 を指定しないとそもそもウチでは動かない、という話もチラホラ見かけたし。

setup() の個所で、console = [ ] と書けばコンソールアプリとして、windows=[ ] と書けばGUIアプリとして扱ってくれるらしい。

zipfile=None という記述もあちこで見かけたけど、手元の環境でソレをしたらエラーが出てしまった。仕方ないのでファイル名を指定。

exe化を実行。 :

python setup.py py2exe と打てば、buildフォルダとdistフォルダが作られて、distフォルダの中にexe化されたソレが出来上がる。

distフォルダの中には、動作に必要な他のファイルも色々入ってるので、他の環境に渡す際はdistフォルダごと渡す、ものらしい。

py2exeは、exe化が終わると、最後に、「〜.dllも動作に必要なんだけどライセンスの問題で同梱するわけにはいかないのでそのあたりはそっちでどうにかしてくれよな」的に、ずらずらとdllの一覧を表示してくれる。今回、Windows10 x64 上で動かしたら、以下の一覧が出力された。

*** binary dependencies ***
Your executable(s) also depend on these dlls which are not included,
you may or may not need to distribute them.

Make sure you have the license if you distribute any of them, and
make sure you don't distribute files belonging to the operating system.

   OLEAUT32.dll - C:\WINDOWS\system32\OLEAUT32.dll
   USER32.dll - C:\WINDOWS\system32\USER32.dll
   IMM32.dll - C:\WINDOWS\system32\IMM32.dll
   SHELL32.dll - C:\WINDOWS\system32\SHELL32.dll
   ole32.dll - C:\WINDOWS\system32\ole32.dll
   MSVCP90.dll - C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x86\MSVCP90.dll
   WINMM.dll - C:\WINDOWS\system32\WINMM.dll
   COMDLG32.dll - C:\WINDOWS\system32\COMDLG32.dll
   ADVAPI32.dll - C:\WINDOWS\system32\ADVAPI32.dll
   WS2_32.dll - C:\WINDOWS\system32\WS2_32.dll
   WINSPOOL.DRV - C:\WINDOWS\system32\WINSPOOL.DRV
   GDI32.dll - C:\WINDOWS\system32\GDI32.dll
   VERSION.dll - C:\WINDOWS\system32\VERSION.dll
   KERNEL32.dll - C:\WINDOWS\system32\KERNEL32.dll

C:WINDOWS\system32\以下のファイルは、Windows環境ならたぶん入ってるんだろうと想像できるけど。MSVCP90.dll てのが…。

MSVCP90.dll は、Microsoft Visual C++ 2008 再頒布可能パッケージ (x86)、に入ってるらしい、が…。

_Microsoft Visual C++ 2008 再頒布可能パッケージ - 窓の杜ライブラリ

コレ、インストールしようとすると、一番容量が大きいHDDのルートフォルダに謎ファイルをばら撒くんだよな…。

更に。 _Using py2exe the right way | Python Tips によると、「コレをインストールしてみたんだけど相変わらず 『見つからねえぞ』って言われて、HDDの中を検索してたまたま見つかったヤツをアプリのフォルダにコピーしたら動いた」みたいな話もあるようで。

このあたり、結局どうすればいいのか、さっぱり分からず。

#2 [python][ubuntu][linux] PySideを使ったスクリプトをUbuntu Linux上で動かしてみたり

今まで、Windows10 x64上で、Python 2.7.12 + PySide 1.2.4 + Pillow 3.4.2 を使ったスクリプトを動かしていたけど。コレをLinux上で動かせるのか気になってきたので試してみたり。

仮想PCソフト、VMware Workstation Player を使って、Ubuntu Linux 16.04 LTS を起動。

端末を開いて、python --version と打ってみたら、2.7.12 と表示された。Python 2.7 はインストールされてるらしい。

PySide のインストールは、 _PySide Binaries Linux - Qt Wiki で解説、されてるのかな。たぶん。件のページでは、apt のリポジトリを追加するように書いてあるけど、どうやら Ubuntu 16.04 LTS では標準でも PySide関連が用意されているようで。以下を打つだけで、PySide がインストールできたっぽい。
sudo apt-get install python-pyside
1.2.2-2build2 がインストールされた。最新版ではない、な…。 _PySide 1.2.4 : Python Package Index を眺めた感じでは、2年ほど放置されている模様。でもまあ、動けばいいか…。

Pillow(PIL)もインストールする。これは pip でインストールできた。と思う。たぶん。
sudo pip install Pillow
3.4.2 がインストールされた。これは最新版だな…。

VMwareの共有フォルダ機能を利用して、WindowsからPythonスクリプトをコピーしてきて、Ubuntu上で実行。

あっさり動いた。素晴らしい。

メニュー上でsvgファイルを使ったアイコンが表示されてない気もするけど…。PySideのバージョンが古いせいだろうか。

_PySide 1.2.4 : Python Package Index を眺めると、「Linux用のバイナリは用意してないから自分でビルドしろよな」と書いてある。 _Building PySide on a Linux System (Ubuntu 12.04 - 14.04) ・ PySide 1.2.4 documentation で、ビルドの仕方が説明してある。

最新版を使いたければ自分でビルド、そこまでしたくないし古い版でもOKだぜって時は apt-get でインストール、ということでいいのかな…。

ビルドしてみた。 :

_Building PySide on a Linux System (Ubuntu 12.04 - 14.04) ・ PySide 1.2.4 documentation に従って、VMware + Ubuntu 16.04 LTS 上で PySide をビルドしてみたり。

まずは必要なパッケージを sudo apt-get でインストールしていくのだけど。qtmobility-dev が無いな…。どうやら Ubuntu 16.04 LTS では qtmobility-dev は無くなったらしい。 _qtmobility-dev missing package on Ubuntu 16.04 - Issue #67 - pyside/pyside-setup でも報告が。とりあえず、qtmobility-dev 以外をインストールして先に進んだり。

2時間ほどかかって、他の作業は一応進んだ、ように見えた。distディレクトリに PySide-1.2.4-cp27-cp27mu-linux_x86_64.whl が生成されている。sudo pip install dost/PySide-1.2.4-cp27-cp27mu-linux_x86_64.whl で試しにインストール。

$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import PySide
>>> PySide.__version__
'1.2.4'
PySide 1.2.4 がインストールできたっぽい。

ちなみに、この状態でPySideを使ったスクリプトを動かしてみたけど、メニューにアイコンは出てこなかった。Windows版とLinux版ではそのあたりの動作が違うのかもしれず。

簡単にインストールできるスクリプトもあるっぽい。 :


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

#1 [python] グリッド表示機能を追加していたり

PySideを使ったアプリを書いてるけど、動作確認しているうちにグリッド表示機能が欲しくなってきて。試しに追加してみたけど、何かこう、見た目が分かりづらいなと感じてしまって悩んだり。

グリッドって、どういう見た目で表示するのが妥当なんだろう…。

今現在、QGrahicsView の表示スケールを変えて拡大表示してるので、グリッド用の線も太く表示されてしまうわけで。このあたり、根本的に変更しないとダメだろうか…。

以上、30 日分です。

過去ログ表示

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