モブ沢工房

プログラミングとかLinux関連(特にOSSのグラフィックツール関連)とかレトロゲームとか3Dプリンタやら日曜大工等、色々。

MyPaintのレイヤ切り替えを超高速化するたったひとつの冴えたやりかた

MyPaintにおいて、レイヤ切り替えはちょっともっさり。これがMyPaintの宿命だと思い込んでおりました。が…

gui/stategroup.pyのStateクラスのクラス変数、autoleave_timeoutをデフォの0.8から0.1とかにするとめっちゃ高速化します。

これはまさかして…拘束具…?

ってかユーザー設定で変更できたほうがいいなぁ(^^;

追記

確かに軽快ですが、速すぎて、今までの切り替えでは何のレイヤか見えていたのだけど、殆ど見えないので何のレイヤかわからなくなる罠 (>_<)

名前をちゃんと付ければいいのだけども…結構このブリンク表示?に依存していたのだなぁと思いました。

外部ターミナルにデバッグ情報などを表示してみた

当たり前なのかもですが (& よくわかってないので迂遠な使い方かもですが) 最近ようやくttyの使い方に気づいたのでメモっておきます。

ターミナルエミュレーターやコンソールでttyってやるとその端末のデバイスファイルが出るわけですが、当然、それに対してechoなどするとそっちに行くわけです。

これ便利だな〜と思いますがイマイチ使いドコロが分からなかったのです、というのも他の端末のウィンドウから特定の端末のウィンドウのttyを知る方法が無いような気がしたわけです。

そこで思いつきました!xfce4-terminalでは(あと他のターミナルでも)-eとかで起動時コマンドを受け付けるわけですよ。

だからデバッグ対象のソフトを立ち上げる前に、xfce4-terminalを立ち上げて起動時にtty > /tmp/ユニークなファイル名 して、それでそのファイルをcat ユニークなファイル名 で変数に受けて、

デバッグ対象のソフト > ユニークなファイル名 2&1

とするとそのソフトの出力が全部そっちにいってprintデバッグですごい便利じゃないですか?

さらにwmctrlを使って、自動でその端末の画面をサブモニタに飛ばしてしまえば超便利だ!!

こんな面倒なことをせず、最初から元のサブモニタに端末を置いて、そっちで起動するというのも最初に思いつきましたが、これだとターゲットのプログラムもサブモニタ側で起動しちゃうのでよろしくないわけです。WMにもよるんでしょうけど…

って今思ったけどターゲットのプログラムをwmctrlでプライマリに飛ばすのもアリか…(汗

そんなわけでMyPaint開発で使ってるシェルスクリプトがこちら

#!/bin/sh

branch="my_build"
std_err="2>&1"

#====================#
while [ "$1" != "" ];do

    case $1 in
    -b | --branch)
        branch=$2
        shift 1
        ;;
    -c | --current-branch)
        branch="CURRENT"
        ;;
    -e | --no-std-error)
        std_err=""
        ;;
    -o | --remain-open)
        remain_open=1
        ;;
    * )
        echo $1 is unrecognized option
        ;;
    esac

shift 1
done
#--------------------#


INSTALLDIR=$HOME/python/others/mypaint_src/mypaint
curdate=`date +"%y%m%d-%H%M%S"`

cd $INSTALLDIR
if [ "$branch" != "CURRENT" ];then
    result=`git branch | grep "\* ${branch}"`
    echo $result
        if [ "$result" = "" ];then
            shownotify -t mmypaint -m "mypaintのブランチが${branch}ではありません"
            exit 1
        fi
fi

ttyfile=/tmp/mypaint_${curdate}_tty
title=my_mypaint_${curdate}

# launch terminal emulator.
xfce4-terminal -e "bash -c \"tty > ${ttyfile}\";bash" -H --title="$title"

# move terminal window
wmctrl -r $title -e 0,1920,-1,-1,-1

./mypaint > `cat $ttyfile` 2>&1

rm $ttyfile

if [ "$remain_open" != "" ];then
    wmctrl -c $title
fi

なんか書き方がださいというか恥ずかしい気もしますがなんというか備忘録的なモノとして…

ちなみに、この手法では何故かpudbとかncurses使ったソフトは2>&1するとアカンっぽい?(なんで?)

それにしても…いや〜、LinuxってっていうかPC-UNIXってほんと素晴らしいですね!(シベリア超特急の人風)

私家版改造MyPaintがなんだかエライことになってきた件

機能つけすぎてわけわからんわ〜という感じになりつつあり… (^^;

github.com

↑なんか比較的カッコいいリンクが作れたので、これで参ります。

当私家版MyPaintでは様々な機能を実験していますが、その中でもひたすらインクツールを改良しまくっています。いや、改良になってればいいんですがね…

正直、誰かいいのを実装してくれないかと「ようすをみている…」のですが

f:id:dothiko:20160211165654j:plain

とりあえず、現在まだ私家版にしかない機能としては

Inktool

  • ノードの複数選択 (ADJUSTフェーズ時に、空白キャンバスでシフトキーを押したままドラッグで枠選択、もしくはCTRLキーを押したままノードをクリック)
  • キャンバス上での筆圧変更 (シフトキー押したままでノードの上で上下もしくは左右にドラッグ)
  • ノードの位置を平均化する (当然ながら選択中のノードにしか効果がない、ノードが一つしか選択されていないときは全体を平均化)

セーブ関連

  • ファイル名末尾にバージョンっぽい番号を付けて保存するインクリメンタルセーブ機能

デバイス関連

  • ペンタブのスタイラスをひっくり返して消しゴムにすると、ツール(Mypaintでは、モードというほうが正しいのかな…)も同時に切り替わる機能。なぜか現時点のmypaintは、ブラシのみでツールは切り変わらない。

アシスト機能

  • フリーハンドの描線を平均化して安定化するスタビライザー機能。 (手ブレ補正を最大にしたよりさらにストロークが遅くなるので、必要なときだけ使うようキーボードでトグルにしました)

次の計画を忘れないようにメモ

さて細かいバグフィックスやモデファイアキーを押下した時の反応なども修正していかねばなりませんね。

それが完璧中の完璧と感じられたら(とてもウッカリさんなワタクシの場合、これでようやく人並みの完成度です)、いよいよ小分けにして(これもまた動作確認して、それもgit merge可能かどうかなど)pull requestしていこうかと思います。

しかしそれ以前に私家版としての次は、超高速セーブか…これは当ブログではすでにMyPaint 1.1の時に実験済みですが、1.2ではautosaveという機能が付いて、これがほぼ同じ仕組みなのです。そして当時嘆いていたdirty tileを得られない件、これはすでに解決しております。だから、基本的には楽勝の筈…

しかし、この機構はautosaveにしか使われず、ドキュメントの保存は今までどおりのものなのですね。

現状のMyPaintの保存は大きく分けて通常のora保存とautosaveです(一枚絵の実質エクスポートは横に置いておくとして)

そこに、「プロジェクト保存」みたいなモードがあれば、いいんじゃないかなぁと…

さて、その他、いくつか手法が考えられます。

  • Mypaint専用のバイナリ保存ファイル形式を作る事。しかし、これはいかにも後々祟る感じがしますね。メンテナンスで。
  • xcfを吐くプラグインを作る!これは悪くないですが、Source atopなどはどうするのか?(勝手に付けたのをgimpで読み込まれるとクラッシュするでしょうねぇ…)

うーん、やっぱり「プロジェクト保存」がいいのかも(^^)

VAIO SVT1311AJにUbuntu 15.10をインストールしてみる

さて、何故か最近ノートづいているワタクシですが。 今度はVAIO SVT1311Aだ!

f:id:dothiko:20160205231807j:plain ↑インストール直後のバニラなUbuntu 15.10

最近メモリが安くなっていたため、サクッと4GBのキングストンのDDR3L PC-12800を購入して8GBに増量…はいいのですが、後になって12GB積めることを知り愕然。まぁ、気分的に4GBがデュアルチャネルじゃないような気がするので、いいけど…

さて、Ubuntuをインストールするにあたり気にしたのは、SSD構成です。デフォで積んでるmSATAの32GBを、IRSTのRAID構成ではなくここにシステムを収納したいわけですね。

よく分からなかったのですが取り敢えず、なんかBIOS画面とは違うのですが出てくるRAID設定画面でRAIDをすべて排除。プレーンな構成にします。やり方は忘れましたが、かなり簡単でした。

しかし残念なのはBIOSでもブートがHDDからしか設定出来ないのですね。SSDはダメです。まぁ、こんなことはLinuxなら無問題なわけですけど(^^)

その後、UbuntuをUSBから起動してインストール開始。ここで、/dev/sdb1がSSDです。こっちにマウントポイントを/で指定。そして、当然ながらブートローダーは/dev/sdaのHDDに入れます!HDDはスワップの8GBと残りに分け、/homeにマウント。これでOK。

後はオイラの好みとしては。SSDの消耗を避けるため、

  • /home/share/var,/home/share/tmpを作成。
  • そして/etc/fstabで
/home/share/var /var none bind 0 0 
/home/share/tmp /tmp none bind 0 0 

そしてリブートしないうちに sudo rsync -av /var/ /home/share/var して整合性を取り即リブート!無事起動、というわけでございました。

1.4GHzのi3ですが、CPUパワーとしてはC2Dの2.53GHzに匹敵するっぽいですね。これでどこまでやれるか、一度試してみたく思います。

githubでの初pull request@mypaintが通りました!

一応記事にしておきますです…

githubについて超ド素人の分際でいきなりmypaintのinktool node関連のpull requestを出してしまい、いろいろ修正しましたが結局、 achadwickさんにご迷惑をかける始末… 本当に申し訳ありませんでしたachadwickさん&mypaint guyの皆様!Very Thanks & I'm so sorry! 一応向こうにも書いといたけど!オイラのへっぽこEngrishが通じていればいいが…

inktoolを、改良案が出てるのに改良が一向に出てこないのでこのまま放置ですか?!と思って焦った結果がこのザマだよ…

いや〜もう適当にググって出た「pull requestは気軽にしてもいいんだよ」みたいな記事に乗せられてアホなワタクシが躍り出た結果、mypaint界隈の皆様に迷惑をかけてしまい本当に申し訳ない気持ちでいっぱいです。

いやほんと、慎重に行こう。な! (←自分に向けて言ってる)

というわけでもう今後はものすごい発明をしない限り、いや、してもものすごい慎重に、せいぜい年一回ぐらいのペースで。pushする前に少なくとも3日ぐらいはチェックを重ね。完全に大丈夫と思うまでpushしない。pull requestは本当にもう一週間ぐらいかけるぐらいの心構えが必要ですね。

それもすごい小さいコミットがいい。と思いました。

しかしまた黒歴史を一つ積み重ねてしまいました…てかgit難しすぎとも思ったり。いやオイラの頭が悪いのだけど。

てかprivate repositoryって意味大きいんですねぇ。最初は別に全部公開でいいじゃんと思ってたけど。こうやって商売するのだなぁ。

VAIO CR92HSのCPUを換装してみる

要らなくなったとのことで、VAIO CR92HSをいただきました。

しかし、CPUがCeleron 550… そこで、以前Thinkpad SL510のCPUを載せ替えた時に余ったCeleron Dualcore T3000にダメ元で載せ替えてみることにしました。

フタを開けて驚きです。

f:id:dothiko:20160203230808j:plain

えっ? こんな簡単にCPUにアクセスできるの?

これは凄いノートだ…まるで自作PCのような簡単さ、いや、それ以上の簡単さで載せ替えられます。

なぜこれをウリにしなかったのだ! > VAIO

載せ替え後の写真を撮りましたが、殆ど同じなので見せても仕方ない感じです(^^;

そんなわけで起動したら、最初はCeleron 550のまま認識して焦りましたが、再度リブートしたら無事認識。

f:id:dothiko:20160203230344j:plain

反応もグッとよくなりました。550の時は、応答なしが非常に多かったです。

このPC、OSはVista->Windows7->(無料)Windows10という変遷をたどっているらしいです。

Windows エクスペリエンスインデックスでは、4.3 -> 5.1と爆増!なんだか、やっぱりまた中古CPUでパワーアップしたくなりそうな雰囲気ですが、T3000で満足しておくのが正しいでしょう。

さてここからどう調理するか?Windows10マシンは既に確保済みなので別にこいつで走らせる必要は無いのでした。なにかLinuxを入れようかな〜

しかし残念ながら何故か光学ドライブだけがIDEで、手持ちのスリムラインSATA用ドライブキャディは使えませんでした。 SATA変換付きのを買ってこないと…

mypaint 1.2にkrita風stabilizer(の簡易版)をつけてみるテスツ

(追記あり)

mypaintにどうしてもKritaのstablizerを付けたかったのですが、とりあえずKritaのソースも読まず適当なものをつけてみたら思いの外、中々よいスタビライズ感があったのでここにメモしておきます。

なのであくまでkritaなのですが、使ってみた感触は中々kritaっぽい。

UIの作り方が良くわからないのとテストなので、使われていないらしい「d」キーを押している間、スタビライザーが発動するというふうにしました。 github版では消しゴムボタンとフリーハンドボタンの間に新設したスタビライザートグルボタンを押すことで発動します。キーボード割り当ても可能。なので、押している間ではなくトグルになりました。まぁそのほうが使いやすいかも。

単に現在もmypaintにある「手ブレ補正」を強めたのとどう違うのか?実はオイラも分かってません(^^;

当初はキーを押しているあいだ「手ブレ補正」を最強にするコードだったのですが、今ひとつ効果が弱い気がしたのでコードを追って見ると、どうも殆どはbrushlibで行われていて敷居が高い。

そこで適当に、単に最近の32サンプルを平均化するというだけのコードを書いてみたら、割といい感じ?になってしまいました。ただ、仕方ない話ですがカーソルから離れた位置に線が描かれるのが難点かな〜と。真面目にやるならKrita読まないと…orz

これがあるとどう言う局面で役立つのかというと

  • どうでも良い線→そのままサクッと
  • ちょっと難しい線→stabilizerでサクッと
  • かなり難しい線(長いカーブなど)→inktoolでじっくりと

このようにサクサク書けるのではないか?と。

stabilizerとinktoolを合わせれば炎になる!炎となったmypaintは…無敵だ!!

ような気がするのですが、まだそれほど使っていません(^^;

コード

gui/freehand.pyをいじります。 めんどうくさいので、この時点のmasterとワタクシが使っておりますテスト用ブランチの差分で…

2016/01/30 18:24:08 追記 少し修正しました。eventhackと言われる部分(多分、キューに溜まったデータ?)も対応するように。

2016/02/03 23:46:24 追記 やけくそでgithubのリポジトリで公開することにしました。自分の頭の悪さと英語力のなさには愕然ですがinktoolを使いやすくしたい一心なのです…ってか誰か良いコードを書いてくれ比較的マジに (本当はそんな事が起きないかな〜と淡い期待を抱いていたのですが、どうも意味の分からん英語を書く変なやつと思われている模様っていうか正確な認識です、ハイ(^^; ))

diff --git a/master:gui/freehand.py b/my_build:gui/freehand.py
index 7b56268..2dad4a7 100644
--- a/master:gui/freehand.py
+++ b/my_build:gui/freehand.py
@@ -22,6 +22,7 @@ from gettext import gettext as _
 import gobject
 import gtk
 from gtk import gdk
+from gtk import keysyms
 from libmypaint import brushsettings
 
 import gui.mode
@@ -102,6 +103,10 @@ class FreehandMode (gui.mode.BrushworkModeMixin,
     # gives us, but position them at the x and y that Xi2 gave us.
     # Pressure and tilt fidelities matter less than positional accuracy.
 
+    # Stablizer ring buffer
+    _stabilize_max = 32
+    _stabilize_src_pos = [None] * _stabilize_max 
+
     ## Initialization
 
     def __init__(self, ignore_modifiers=True, **args):
@@ -110,6 +115,11 @@ class FreehandMode (gui.mode.BrushworkModeMixin,
         self._cursor_hidden_tdws = set()
         self._cursor_hidden = None
 
+        # Stablizer init
+        self._stabilize_init()
+        self._stabilized=False
+
+
     ## Metadata
 
     pointer_behavior = gui.mode.Behavior.PAINT_FREEHAND
@@ -394,6 +404,8 @@ class FreehandMode (gui.mode.BrushworkModeMixin,
             # Hide the cursor if configured to
             self._hide_drawing_cursor(tdw)
 
+
+
             result = True
         return (super(FreehandMode, self).button_press_cb(tdw, event)
                 or result)
@@ -418,6 +430,11 @@ class FreehandMode (gui.mode.BrushworkModeMixin,
             self._reinstate_drawing_cursor(tdw)
 
             result = True
+
+        # Stablize
+        self._stabilize_reset()
+
+        
         return (super(FreehandMode, self).button_release_cb(tdw, event)
                 or result)
 
@@ -467,9 +484,19 @@ class FreehandMode (gui.mode.BrushworkModeMixin,
             if not same_device:
                 tdw.doc.brush.reset()
 
-        # Extract the raw readings for this event
-        x = event.x
-        y = event.y
+        # Stabilizer cursor position fetch
+        self._set_stabilize_point(event.x, event.y)
+        if self._stabilized:
+            pos = self._get_stabilize_point()
+            if not pos:
+                return
+            else:
+                x, y = pos
+        else:
+            # Extract the raw readings for this event
+            x = event.x
+            y = event.y
+
         time = event.time
         pressure = event.get_axis(gdk.AXIS_PRESSURE)
         xtilt = event.get_axis(gdk.AXIS_XTILT)
@@ -573,18 +600,35 @@ class FreehandMode (gui.mode.BrushworkModeMixin,
             # Remove the last item: it should be the one corresponding
             # to the current motion-notify-event.
             hx0, hy0, ht0 = drawstate.evhack_positions.pop(-1)
-            # Check that we can use the eventhack data uncorrected
-            if (hx0, hy0, ht0) == (x, y, time):
-                for hx, hy, ht in drawstate.evhack_positions:
-                    hx, hy = tdw.display_to_model(hx, hy)
-                    event_data = (ht, hx, hy, None, None, None)
-                    drawstate.queue_motion(event_data)
+            if self._stabilized:
+                if (hx0, hy0, ht0) == (event.x, event.y, time):
+                    for hx, hy, ht in drawstate.evhack_positions:
+                        self._set_stabilize_point(hx,hy)
+                        pos = self._get_stabilize_point()
+                        if pos:
+                            hx, hy = tdw.display_to_model(pos[0], pos[1])
+                            event_data = (ht, hx, hy, None, None, None)
+                            drawstate.queue_motion(event_data)
+                else:
+                    # FIXME code duplication
+                    logger.warning(
+                        "Final evhack event (%0.2f, %0.2f, %d) doesn't match its "
+                        "corresponding motion-notify-event (%0.2f, %0.2f, %d). "
+                        "This can be ignored if it's just a one-off occurrence.",
+                        hx0, hy0, ht0, event.x, event.y, time)
             else:
-                logger.warning(
-                    "Final evhack event (%0.2f, %0.2f, %d) doesn't match its "
-                    "corresponding motion-notify-event (%0.2f, %0.2f, %d). "
-                    "This can be ignored if it's just a one-off occurrence.",
-                    hx0, hy0, ht0, x, y, time)
+                # Check that we can use the eventhack data uncorrected
+                if (hx0, hy0, ht0) == (x, y, time):
+                    for hx, hy, ht in drawstate.evhack_positions:
+                        hx, hy = tdw.display_to_model(hx, hy)
+                        event_data = (ht, hx, hy, None, None, None)
+                        drawstate.queue_motion(event_data)
+                else:
+                    logger.warning(
+                        "Final evhack event (%0.2f, %0.2f, %d) doesn't match its "
+                        "corresponding motion-notify-event (%0.2f, %0.2f, %d). "
+                        "This can be ignored if it's just a one-off occurrence.",
+                        hx0, hy0, ht0, x, y, time)
         # Reset the eventhack queue
         if len(drawstate.evhack_positions) > 0:
             drawstate.evhack_positions = []
@@ -599,6 +643,51 @@ class FreehandMode (gui.mode.BrushworkModeMixin,
                                     priority=self.MOTION_QUEUE_PRIORITY)
             drawstate.motion_processing_cbid = cbid
 
+    def key_press_cb(self, win, tdw, event):
+        if event.keyval == keysyms.d and not self._stabilized:
+            self._stabilize_init()
+
+    def key_release_cb(self, win, tdw, event):
+        if self._stabilized:
+            self._stabilize_reset()
+
+    ## Stabilize related
+    def _stabilize_init(self):
+        self._stabilized_index=0
+        self._stabilized_cnt=0
+        self._stabilized=True
+
+    def _stabilize_reset(self):
+        self._stabilized_index=0
+        self._stabilized_cnt=0
+        self._stabilized=False
+
+    def _set_stabilize_point(self,x,y):
+        self._stabilize_src_pos[self._stabilized_index]=(x,y)
+        self._stabilized_index+=1
+        self._stabilized_index%=self._stabilize_max
+        self._stabilized_cnt+=1
+
+    def _get_stabilize_point(self):
+        if self._stabilized_cnt < self._stabilize_max:
+            return None
+
+        ox=0
+        oy=0
+        idx=0
+        while idx < self._stabilize_max:
+            cx,cy=self._get_stabilize_element(idx)
+            ox+=cx
+            oy+=cy
+            idx+=1
+
+        return (ox/self._stabilize_max,oy/self._stabilize_max)
+
+    def _get_stabilize_element(self,idx):
+        return self._stabilize_src_pos[(self._stabilized_index + idx) % self._stabilize_max]
+    
+        
+
     ## Motion queue processing
 
     def _motion_queue_idle_cb(self, tdw):