モブ沢工房

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

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):

GIMP向けドット絵アニメーションプラグインを作っています

更新ネタが無いもので…orz

タイトル通り、GIMP向けドット絵アニメーションプラグインを作っています。そのうちgithubリポジトリ作ろうかと(^^)

今のところのスクリーンショットはこんな感じ。まだ、単純なアニメしかしませんw

f:id:dothiko:20160125201448j:plain

どういうものかというと非常に簡単な仕組みで

  • gimpからプラグインとして起動する。
  • 現在描いている絵をスクリプトから一時ファイルとしてpngエクスポート
  • 別アプリとなっているドット絵プレイヤーが起動していなければsubprocessで起動。していれば、ソケットで待ち受けしているのでコマンド送信。これにより、画像の更新を外部から認識させる。
  • プレイヤーは一時ファイルを読み込み、そのpngをタイルパターンの画像として、指定されたサイズで、左上からタイル0と認識し、最後のタイルまでを適当な期間で再生。
  • ソースとなる絵と同じ場所に同名の.animconfファイルがあればそれを読み込んで、タイルサイズやアニメーションパターン(たとえば、タイル0,2,4,5で1アニメパターン)を自動で設定できる

みたいなものを考えてます。

これでオリジナル駄作ゲーム制作にまた一歩近づけるかと…まぁ、Blenderモデリング覚えた方がいいような気も、しなくもないですけどねw

何でこんなものを作ってるのかというと、以前見つけた2D用アニメ機能付きスプライトエディターが機能豊富なのはいいんですが、やっぱり、慣れてないので非常に使いづらい…という。gimpのほうが手に馴染んでいるので、作ってみようと思いました。

第一作目はグロブダーのパクリゲーで行きたいなぁ…

ところでこんな動画を見て、やっぱゲーム作りたいな〜とさらに感化されたというか、なんと言うか。

Helious、というDOSゲーだそうです。今はフリーウェアになってるとか?ゲーム自体は何が目的なのかよく分かりませんが、何か雰囲気がマーブルマッドネス的な不思議な感じがして楽しいです。

ちなみにHeliousをDOSBoxでジョイスティックでプレイする場合、うちの環境(ubuntu 14.04LTS,Dosbox 0.74)ではcyclesを9000程度にしないと、DOSBox自体が「Exit to error: Illegal GRP4 Call 6」というエラーで落ちてしまいます。

こういう不思議な感じのゲームでもうひとつ思い出すのは、ブラウザのJava appletで作られた題名不詳のゲームなのですが、 全方向にスクロールする2Dで、全体的にアブストラクトな雰囲気の、自機?も生物なのか機械なのかよくわからない紋章みたいなやつで…それで、攻撃方法が自分の回りに巨大バリアを張って敵を巻き込むような感じの攻撃方法で、確か数回しか放てないのですね。

んで、敵の数からして一匹づつバリアを使ってるとクリアできない。 だから敵をおびき寄せて複数を同時に倒す。さらに、同時に倒すと攻撃回数を回復させるアイテムが出る、みたいな雰囲気だったように思います。いつごろプレイしたのかも思い出せないのですが、あのゲームは何だったのか…

いつかは、あのゲームに似たものも再現したいとは思っています。

GIMPでアルファ継承(擬似クリッピングレイヤー)

スクリプトで実装という。

つまるところ単純にグループレイヤーの最下層のアルファ値でマスクを作り、その他の兄弟レイヤにマスクとして適用するというモノです。

最下層のレイヤを描き変え終わったら、このスクリプトを走らせてマスクを同期させる、という原始的なものでございます。

マスクをかけるレイヤを制限するには、必要なレイヤにだけマスクを作るか、リンク指定するなどして、スクリプト実行時にソレに合わせた適切なオプションを選択して実行してください。

なお、トップレベルレイヤでは大抵の場合、最下層が全不透明の背景であることが多いため、このsync-layergroup-maskは単純に機能しないように作ってあります。

その他、真下のレイヤのアルファ値と同期とかのスクリプトも一緒に混ぜてあります。

バグがあるとは思うんですが適当に修正してください(^^;)

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#[license] GPLv3
#[plugin]
#[name] sync-layer-mask
#[desc] 
# レイヤのマスクをワンタッチで同期させるスクリプト
# 指定された正規表現にマッチしたレイヤを適合させたり
# 兄弟レイヤのマスクを統一したり
# アクティブレイヤをベースレイヤ(マスクが存在しない場合は不透明部分)と同じマスクにすることができる。
# 兄弟レイヤやベースレイヤの関係性は、基本的にレイヤの名前から決定される.
#
# なお、sync-layergroup-maskについては若干挙動が異なり、「アルファ相続」「クリッピングレイヤー」
# 的な挙動を行うためのものになっている。
# 所属するレイヤグループの最下層のレイヤのアルファ値と、その他の兄弟レイヤのマスクを同期するというものだ。
# 子グループについては「再帰して処理を行う」
# トップレベルレイヤについてはこのsync-layergroup-maskは機能しない(常にではないが大抵の場合、
# 最下層が完全に不完全なキャンバスな事が多く、まず機能せず混乱を招くだけという考え)
#
#[version]
#0.1 初期リリース
#0.2 グループの最下層レイヤーのアルファ値に同期させる(クリッピングレイヤー|アルファ相続)関数を追加
#[end]

#  このプログラムはGPLライセンスver3で公開します。
# 
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You may have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.           
#
#   Copyright (C) 2015 dothiko(http://dothiko.blog.fc2.com/) 


from gimpfu import *
import re

SIBLING_PATTERN='^%s(-\S+)?$'

def python_fu_sync_layer_mask(a_img,a_drawable,src_layer,regexp_pattern):
    if src_layer==None:
        src_layer=a_img.active_layer

    sync_layer_mask(a_img,src_layer,re.compile(regexp_pattern),None)

def python_fu_sync_layer_mask_of_sibling(a_img,a_drawable):
    names=a_img.active_layer.name.split('-')
    head=names[0]
    sync_layer_mask(a_img,a_img.active_layer,re.compile(SIBLING_PATTERN % head),None)

def python_fu_sync_layer_mask_of_base(a_img,a_drawable):

    names=a_img.active_layer.name.split('-')
    head=names[0]

    # searching '-base' layer
    regexp=re.compile('^%s-base$' % head)
    for bl in a_img.layers:

        mo=regexp.search(bl.name)
        if mo:
            if bl==a_img.active_layer:
                gimp.message("アクティブレイヤがベースレイヤです")
            else:
                sync_layer_mask(a_img,re.compile(SIBLING_PATTERN % head),bl,targets=(a_img.active_layer,))

            return
    
    gimp.message("ベースレイヤ(%s-base)は発見できませんでした" % head)

def python_fu_sync_layergroup_mask(a_img,a_drawable,limit_to_linked,limit_to_has_mask,apply_prev_mask):

    cl=a_img.active_layer
    if len(cl.children)>0:
        # this is the layer group
        target_layers=cl.children
    elif cl.parent==None:
        gimp.message("グループに所属するレイヤにのみ有効です")
        return
    else:
        target_layers=cl.parent.children

    cl=target_layers[-1] # the last child used as unified mask source.
    sync_layer_mask(a_img,None,cl,targets=target_layers,
            limit_to_linked=limit_to_linked,
            limit_to_has_mask=limit_to_has_mask,
            apply_prev_mask=apply_prev_mask,
            force_other_mask=True)


def python_fu_sync_layer_mask_to_below(a_img,a_drawable,apply_prev_mask):
    al=a_img.active_layer
    if al.parent:
        plst=al.parent.children
    else:
        plst=a_img.layers

    curidx=pdb.gimp_image_get_item_position(a_img,al)
    
    if curidx < len(plst):
        sync_layer_mask(a_img,None,plst[curidx+1],(a_img.active_layer,),apply_prev_mask=apply_prev_mask)
    else:
        gimp.message("これより下にレイヤがありませんので、同期できません")

def sync_layer_mask(a_img,regexp,src_layer,targets,
    limit_to_linked=False,limit_to_has_mask=False,
    apply_prev_mask=False,force_other_mask=False):
    """
    core function.

    Arguments:
    regexp -- the regular expression object.if this is None,all name should be match.
    targets -- a sequence of target layers.if None,a_img.layers used.

    Returns:
    None
    """


    if targets==None:
        targets=a_img.layers

    if src_layer.mask!=None:
        src_mask=src_layer.mask
    elif src_layer.has_alpha:
        src_mask=pdb.gimp_layer_create_mask(src_layer,2) # 2==ADD ALPHA-COMPONENT MASK
    else:
        gimp.message("この作業には、レイヤ %s にマスクかアルファチャネルが必要です" % src_layer.name)
        return


    # start of groping undoable operations
    pdb.gimp_image_undo_group_start(a_img)

    try:
        ax,ay=src_layer.offsets

        def do_sync(targets):
        # Use this local function ,to recursive call does not needs copy flags.
            for cl in targets:
                if cl!=src_layer:
                    
                    if len(cl.children)>0:
                        # this is layer group.so, walk into layer group
                        do_sync(cl.children) # RECURSIVE CALL
                    else:

                        if regexp:
                            mo=regexp.search(cl.name)
                        else:
                            flag_limit_linked=True
                            if limit_to_linked and not cl.linked:
                                flag_limit_linked=False

                            flag_limit_mask=True
                            if limit_to_has_mask and cl.mask==None:
                                flag_limit_mask=False

                            mo=(flag_limit_linked and flag_limit_mask)

                        if mo:
                           #pdb.gimp_message("processing %s" % cl.name)
                            if cl.mask==None:
                                if force_other_mask:
                                    new_mask=pdb.gimp_layer_create_mask(cl,1) # 1==Completely transparent mask,to composite it.
                                else:
                                    new_mask=pdb.gimp_layer_create_mask(cl,2) # 2==ADD ALPHA-COMPONENT MASK
                            elif apply_prev_mask:
                                pdb.gimp_layer_remove_mask(cl,0) # 0 means 'apply mask and delete it'
                                new_mask=pdb.gimp_layer_create_mask(cl,1) # completely transparent mask,to replace 
                            else:
                                pdb.gimp_layer_remove_mask(cl,1) # 1 means 'simply delete it without changing pixel.'
                                new_mask=pdb.gimp_layer_create_mask(cl,1) # completely transparent mask,to replace 

                            pdb.gimp_layer_add_mask(cl,new_mask)

                            cx,cy=cl.offsets
                            pdb.gimp_channel_combine_masks(cl.mask, src_mask, 0, -cx+ax , -cy+ay) # mask composited.

                            pdb.gimp_layer_set_edit_mask(cl,0)

        # Start processing here.
        do_sync(targets)

    finally:
        # end of grouping undoable operations
        pdb.gimp_image_undo_group_end(a_img)

        if src_layer.mask!=src_mask:
            gimp.delete(src_mask)

register(
        "python_fu_sync_layer_mask",
        "sync-layer-mask",
        "指定された正規表現パターンに適合する名前のレイヤ間で、マスクを同期させる",
        "dothiko",
        "dothiko",
        "apr 2015",
        "<Image>/Python-Fu/mask/sync-layer-mask",
        "RGB*,GRAY*",
        [
            (PF_LAYER,"src_layer","元レイヤ",None),
            (PF_STRING,"regexp_pattern","正規表現パターン(pythonのreモジュールの書式で)","\S+"),
        ],
        [],
        python_fu_sync_layer_mask)

register(
        "python_fu_sync_layer_mask_of_sibling",
        "sync-layer-mask-of-sibling",
        "アクティブレイヤの兄弟レイヤ間で、マスクを同期させる",
        "dothiko",
        "dothiko",
        "apr 2015",
        "<Image>/Python-Fu/mask/sync-layer-mask-of-sibling",
        "RGB*,GRAY*",
        [
        ],
        [],
        python_fu_sync_layer_mask_of_sibling)

register(
        "python_fu_sync_layer_mask_of_base",
        "sync-layer-mask-of-base",
        "アクティブレイヤのマスクを、ベースレイヤのマスクで同期する",
        "dothiko",
        "dothiko",
        "apr 2015",
        "<Image>/Python-Fu/mask/sync-layer-mask-of-base",
        "RGB*,GRAY*",
        [
        ],
        [],
        python_fu_sync_layer_mask_of_base)

register(
        "python_fu_sync_layergroup_mask",
        "alpha-inherit-mask",
        "アルファ相続的な動作で、グループの最後尾のアルファと各レイヤーのマスクを同期させる",
        "dothiko",
        "dothiko",
        "apr 2015",
        "<Image>/Python-Fu/mask/sync-layergroup-mask",
        "RGB*,GRAY*",
        [
            (PF_BOOL,"limit_to_linked","リンクされたレイヤに限定",False),
            (PF_BOOL,"limit_to_has_mask","現時点でマスクを持つレイヤに限定",True),
            (PF_BOOL,"apply_prev_mask","対象レイヤが既にマスクを持つ場合、同期前にマスクを適用する",False),
        ],
        [],
        python_fu_sync_layergroup_mask)

register(
        "python_fu_sync_layer_mask_to_below",
        "sync-layer-mask-to-below",
        "真下のレイヤの不透明部分をマスクとして同期させる",
        "dothiko",
        "dothiko",
        "apr 2015",
        "<Image>/Python-Fu/mask/sync-layer-mask-to-below",
        "RGB*,GRAY*",
        [
          (PF_BOOL,"apply_prev_mask","対象レイヤが既にマスクを持つ場合、同期前にマスクを適用する",False),
        ],
        [],
        python_fu_sync_layer_mask_to_below)
main()






if __name__ == '__main__':

    pass


GIMPでG'MICのColorize(comic)向けレイヤ分割スクリプト

最近になってG'MICプラグインのColorize(comic)を知ったのですが、本当に凄いですね。

これで下塗りが楽になったので、ものすごい速度向上しました。

ちなみにワタクシのお絵かきブログにて備忘録的にメモしていたりして…で、そこで少しだけ愚痴っているように、レイヤを色ごとに分けるオプションでやるとものすごい数のレイヤを作ってきて大変なことになるので、主線+色1枚のレイヤで出力させているのです。

それで、いままで色範囲選択->cut->paste->レイヤ化でちまちまとレイヤを分けていたのです。

でもやっぱり面倒くさい。それで作りました。自動レイヤ分けスクリプトです。

仕組みはごく簡単、色レイヤをアクティブにしてこのスクリプトを起動すると、自動的に使われている色を検出してレイヤ分けします。

ここで注意せねばならないのは、あまりにも色領域が小さいと無視されることです。 なぜなら、ピクセルのサーチには64x64にリサイズした複製レイヤを使っているからです(^^;

っと、ここまで書いて気づいたのですがdothikoutil.pyという自作ライブラリを使っておりますな…そんなわけで、少し書きなおしましたが、本来はメンテナンスの為にまとめています。そのうちgithubに公開リポジトリを作って、そこに今まで作ったようなgimpスクリプトをまとめて置いておこうと思いますです…

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#[license] GPLv3
#[plugin]
#[name] layer-divide-by-color
#[desc] 
#レイヤを色で分割するスクリプト。
#G'MICのColorize(comic)で出力された彩色レイヤの分割向け。
#[version]
#0.1 初期リリース
#[end]

#  このプログラムはGPLライセンスver3で公開します。
# 
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You may have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.           
#
#   Copyright (C) 2016 dothiko(http://dothiko.hatenablog.com/) 


from gimpfu import *
#from dothikoutil import *

def scale_layer(layer,w,h,method,scale_itself=False):
    """
    method is 
    INTERPOLATION-NONE (0), 
    INTERPOLATION-LINEAR (1), 
    INTERPOLATION-CUBIC (2), 
    INTERPOLATION-LANCZOS (3) 
    """
    if not scale_itself:
        layer=duplicate_layer(layer)

    old_method=pdb.gimp_context_get_interpolation()
    pdb.gimp_context_set_interpolation(method)
    pdb.gimp_layer_scale(layer,w,h,1)
    pdb.gimp_context_set_interpolation(old_method)
    return layer

def duplicate_layer(layer,pos=0):
    """
    Utility function,to automatically insert copied layer to its image.
    """
    nl=pdb.gimp_layer_copy(layer,0)
    pdb.gimp_image_insert_layer(layer.image,nl,None,pos)
    return nl


def python_fu_layer_divide(a_img,a_drawable,sample_arg=True):

  
    pdb.gimp_context_push()
    pdb.gimp_image_undo_group_start(a_img)

    try:
        pdb.gimp_context_set_sample_threshold(0.0)
        w=64
        h=64
        pixels={}
        sl=scale_layer(a_drawable,w,h,0)# 0 means "no-interporation"
        iy=0
        while iy < h:
            ix=0
            while ix < w:
                p=sl.get_pixel(ix,iy)
                if p == (0,0,0,0):
                    pass
                elif p in pixels:
                    pixels[p]+=1
                else:
                    pixels[p]=1
                ix+=1
            iy+=1

        if pdb.gimp_selection_is_empty(a_img)==0:
            save=pdb.gimp_selection_save(a_img)
        else:
            save=None
        
        # Enumerate pixel and divide layer by pixel color.
        # The last color component is remained at original layer,
        # because there should be a empty layer after all colors floated.
        for cc in pixels.keys()[:-1]:
            pdb.gimp_image_select_color(a_img,2,a_drawable,cc)
            fl=pdb.gimp_selection_float(a_drawable,0,0)
            pdb.gimp_floating_sel_to_layer(fl)

        if save:
            pdb.gimp_selection_load(save)
        else:
            pdb.gimp_selection_none(a_img)

        a_img.remove_layer(sl)

    finally:
        pdb.gimp_image_undo_group_end(a_img)
        pdb.gimp_context_pop()



register(
        "python_fu_layer_divide",
        "layer-divide-by-color",
        "divide a layer from its color",
        "dothiko",
        "kakukaku world",
        "dec 2015", 
        "<Image>/Python-Fu/layer/layer-divide-by-color", 
        "RGB*,GRAY*",
        [
          # (PF_BOOL,"sample_arg","sample of argument",True),
        ],
        [],
        python_fu_layer_divide)


main()

Mypaint 1.2.0 betaでインクからブラシに切り替えると妙ちくりんな線が描画される件

(追記あり)

(現在のupstreamのコードでは治っていますので、この修正は不要です)

後でバグレポートとか見なおして見ますが、まだ出てなかったら報告すべきですかね…

まず、インクツールストロークを描きます。

  1. チェックボタンを押してインクストロークを確定
  2. 直後にメニューでフリーハンド(=ブラシ)に切り替えます
  3. マウスをキャンバスに動かすと、妙な線が出ます。

という現象を発見しました。

つまりこうです。まず適当にストロークを描いて…

f:id:dothiko:20151215212400j:plain

ここで、右のチェックボタンを押して確定。そして、メニューに行って「編集」→「フリーハンド」を選びます。

その後キャンバスにカーソルが戻ると、突然出現だ!

f:id:dothiko:20151215212413j:plain

最後の部分からストロークと誤認されてるっぽい感じ。

なお言うまでもありませんが、もちろんオイラが弄っているエンバグしてるかもな素人改造版ではなく、ベータリリース版で試しています。

なお、何度か試した所、バケツ塗りツールでも同じ現象が稀に起きる模様…

インクツールを使いまくろうと思っているワタクシ的にはちょっと致命的だ!

なお、キーボードにインクツールを割り当てると(ワタクシはKキーにしております…calligraphy→発音が似てるK、的な意味で…)、kキーをもう一回押すと以前のツールに戻るっぽいです。それだと、この現象は起きないのでした。

そこでデバッガなどで追いまくってどうも起きなくなるようにするのを発見しました。 gui/document.pyの1837行目をなんだかgit pullで最新に追いつくと当然ながら場所がかわりますので… mode_flip_action_activated_cbメソッド内の以下のあたりを、このようにします。abrupt_start=True 引数指定を追加です。

        # If a mode object of this exact class is active, pop the stack.
        # Otherwise, instantiate and enter.
        if self.modes.top.__class__ is mode_class:
            self.modes.pop()
            flip_action.keyup_callback = lambda *a: None  # suppress repeats
        else:
            if issubclass(mode_class, gui.mode.OneshotDragMode):
                mode = mode_class(ignore_modifiers=True, temporary_activation=False)
            else:
                mode = mode_class(ignore_modifiers=True, abrupt_start=True) # <- change here like this. ココをこのように変更

2015/12/20 09:57:43 追記

上記のままだと塗りつぶしツールで例外出しますね。InteractionModeがコンストラクタを持たず、引数を受け付けないのが原因のよう… issubclassで見分けたほうがいいのか考えましたが、動けば良かろうなのだァァァァッ!というわけで、とりあえずコンストラクタを新設しておきました。

gui/mode.pyの以下のあたりです。

     #: switch to. If the iterable is empty, all modes are possible.
     permitted_switch_actions = ()
 
     def __init__(self,**kwds):
         # Workaround to force aburpt_start for all modes.
         pass

ただこうして副作用がどうなるのかは、今んところよくわかりません…

うーん、オイラのとこだけの現象だったら、ちょっと恥ずかしいな(^^;

ちなみにUbuntu Unityでもxfceでも発生しました。

2016/01/30 10:16:50 追記

上にも書きましたが現在のupstreamのコードでは治っていますので、上記の修正は不要です

gimp 2.8.16で.oraファイルを開くのが失敗する件

gimpに、mypaintで吐いたora(OpenRaster)ファイルを読ませようと思った時に事件は起きた!

なんと、oraを読んでくれない…エラーダイアログを出して終了です。

ターミナルから起動してみたら

Traceback (most recent call last):
File "/usr/lib/gimp/2.0/python/gimpfu.py", line 851, in _run
res = apply(func, params[1:])
File "/usr/lib/gimp/2.0/plug-ins/file-openraster.py", line 274, in load_ora
if not name:
UnboundLocalError: local variable 'name' referenced before assignment

なんともポカミスのようですね。確かにリリースノートを見るとora関連の改正が入っている…何かの手違いが起きたのでしょうか。

そんなわけで、/usr/lib/gimp/2.0/plug-ins/file-openraster.pyの、268行目ぐらいのfor item in get_layers(stack):の直前に

    name=None
    path=filename

を入れといたら読めるようになりました。

わかりやすいように、その上下の部分を含めて書くとこんな感じ。 parent_groups = [] が265行めぐらいです。

    parent_groups = []

    layer_no = 0
    name = None # <- この行を追加
    path = filename # <- この行も追加

    for item in get_layers(stack):

        if item is NESTED_STACK_END:
            parent_groups.pop()
            continue

(追記 2016/11/10) 実はXubuntu 16.04にアップデートして書き換え忘れてたのでora読ませたらエラーが出てビックリ、自分で自分のブログ見て対応したという(^^;

自分でこの記事を読んでみたら、実にわかりにくい書き方で閉口しましたorz

Windowsではどうだかわかりませんが、多分インストールされたディレクトリの下にあるplug-insディレクトリにfile-openraster.pyがあれば、そのファイルではないでしょうか。

diff

修正前のオリジナルと修正後のdiffも一応念の為に置いておきます。 まぁそもそも、このテキトーな修正で良いかどうかは不明なのですが…とりあえずうちでは読めています(汗

268,269d267
< name=None
< path=filename

問題箇所がC/C++で書かれてなくて面倒臭いことにならずに、よかった(^^;

追記:てかbugzillaよく見たら、すでにファイルされてて似たような解決策も出てましたw

dot言語の「デフォルトフォント」設定とレンダリングを行うスクリプトを作ってみた

ハンドル名とかぶっている…(汗

ともかく、実はいままでdot言語(とGraphviz)なるものがあることを知らず。 最近になって知り、そんな便利なものがあったのか!と感激したわけです。

しかし、いかんせん古めかしい?ものなのか、多少融通が効かない模様… 特に、全体のフォントを指定できないのは実に悲しい。

そこでググったところ、さすがstackoverflow、そのものずばりの解答がありました。 なるほどsvgにしてフォント名をsedで置き換えてしまう…これは目から鱗です。

そこでこのstackoverflowの話を元に、さらにinkscapeを経由してpngやらjpgにしてしまおう、と考え、その一連の行動をスクリプトにすることを思い立ちました。

inkscapeのexport周りは拡張子で面倒くさいことになりそうなので、png決め打ちにして、pngでないファイルが出力指定された場合はimagemagickを使うということにしました。 そのため、

の3つに依存するということになりました。

多少、オプション形式がdotコマンドと変わった上に、ものすごい厳選されたオプションしか使えなくなっているわけですが(汗

オイラ的な実用にはこれで十分かなぁ〜、と…

なにか簡単な説明図を描く時、いままでわざわざマウス動かしてinkscapeやdiaを使ってたわけですが、これで捗りそうです。

#!/bin/bash

fontname="IPA ゴシック"

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

    case $1 in
    -f | --fontname)
        fontname=$2
        shift 1
        ;;
    -o | --output)
        outputname=$2
        outputext=${outputname##*.}
        shift 1
        ;;
    -h | --help)
        linenum=`cat $0 | grep -n -e '^#--*#' | sed -e 's/:.*//g'`
        head -n $linenum $0
        exit 0
        ;;
    * )
        ext=${1##*.}
        if [ "$ext" = "dot" ];then
            inputfile=$1
        else
            echo "[WARNING] unknown option $1 ignored."
        fi
        ;;
    esac

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

curdate=`date +"%y%m%d_%H%M%S"`
basefontname="Times,serif"
workfilename="/tmp/${curdate}_tmp_dot.svg"


if [ "$outputname" = "" ];then
    echo "[ERROR]:no output filename set.quitted."
    exit 1
fi

if [ "$outputext" != "svg" ];then
    convedname=$outputname
    outputname="/tmp/${curdate}_conv_dot.svg"

    convedext=${convedname##*.}
    if [ "$convedext" != "png" ];then
        finalimg=$convedname
        convedname="/tmp/${curdate}_conv_dot.png"
    fi


fi

dot $inputfile -Tsvg -o${workfilename}
sed "s/${basefontname}/${fontname}/g" $workfilename > $outputname

rm $workfilename

# Do not forget to rasterize it.
if [ "$convedname" != "" ];then
    inkscape --file=$outputname --export-png=$convedname
    if [ -e "$convedname" ];then
        rm $outputname

        if [ "$finalimg" != "" ];then
            convert $convedname $finalimg
            rm $convedname
        fi

    else
        echo "[WARNING] 画像ファイルが生成されませんでした.そのため中間ファイル ${outputname} をそのまま残しました。"
    fi

fi