モブ沢工房

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

gimpのpython-fuな自由変形プラグインを、さらに改造

先日作ったgimppython-fuな自由変形スプライト的プラグイン。 さらに改造して変貌しつつあります。

なんと!今回の改造では

  • パス内の複数の四角形ストロークに対応
  • ソース(アクティブレイヤー)がレイヤグループの場合、レイヤグループのメンバをソースとしてランダムで選択する

という機能を付け加えました。

デモ

そんなわけで実演してみましょう。 テスト状況はこのような感じ。

f:id:dothiko:20150418134126j:plain

レイヤグループの中に、予め描かれた木が三本入っています。ここで重要なのは、それぞれの木のレイヤはきっちり木の寸法でレイヤーがトリム(選択範囲で切り抜き)されていることです。なぜかというと、切り抜いておかないと遠近法変形は透明部分を含めて変形させるため、パスストロークから変形後の形状を想定しづらいのですな。少し細長く変形させたつもりが、ものすごい細長くなってたり。

背景とされたレイヤが真っ白で放置されているのはミスです。背景はその上の青→水色→緑のグラデとなります。

そして、パスを作成。実に、適当に。

その後、レイヤダイアログで「レイヤグループ」を選択し、deform-to-vectorを実行。ダイアログで条件を選びますが今回は全てデフォルト値で行います。

一瞬でできた!!

…でもなんか寂しいので、これらのレイヤーをマージしてさらに複製して重ねてみます。

盛れてきましたが隙間が有りますね。

寂しいのでお友達を増やしてあげましょう!(ボブの絵画教室風)

新規パスに、3つほど四角形ストロークを新設して

そして、背景を取り去った上で可視レイヤーのコピー→横512pxに縮小して、さらに遠景の木とします。言うまでもなく、レイヤの優先度も背景側に。

そしてそれをさらに複製してそれぞれマージして、透明度をちょいと弄くりますと…

ね、簡単でしょ?(ボブの絵画教室風)

このデモの場合、根本のあたりが非常に違和感高いのでトリミングしてやると中々雰囲気が出ます

…まぁ単品で見るとふざけてるの?って感じの絵にしかなりませんが、「ゲームの背景(のモックアップ)を作る」とか「パースの消失した遠景を適当かつ即座にでっちあげる」とか、「誰も細かく見ないモブ群衆を即座に作る」みたいなときに役に立つ…立つといいなぁ!

あと雑草の密集とか、使えそうだと思うんですが(まだ自分でも実際にはやってません)

まぁ思いついて実装してみたけどあんまり使えない…のはよくあることだったりするので…

ソースコード

追記:微修正して「出力したレイヤを統合(マージ)する」オプションを付けました

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#[license] GPLv3
#[plugin]
#[name] deform-to-vector
#[desc] 
#遠近法変形をパスの頂点を利用してスピーディーかつ感覚的に実行する
#[version]
#0.1 初期リリース
#0.2 パスの持つすべての矩形ストロークに対して変形を行うように変更。同時に、複製に対する操作のみに変更。
#0.3 ダイアログ化して、詳細な設定を可能に。今までの版は簡略版として添付。
#0.4 生成したレイヤをマージするオプションを付加
#[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) 2013 dothiko(http://dothiko.blog.fc2.com/) 


from gimpfu import *
import random

def python_fu_deform_to_vector_now(a_img,a_drawable):
    python_fu_deform_to_vector(a_img,a_drawable,0.0,0.0,True)

def python_fu_deform_to_vector(a_img,a_drawable,xflip_chance,yflip_chance,use_group_as_list,force_visible,merge_all):

    v=pdb.gimp_image_get_active_vectors(a_img)
    if v==None:
       #pdb.gimp_message("please select a path")
        pdb.gimp_message("パスを選択してください。")
        return

    if len(v.strokes)<=0:
       #pdb.gimp_message("the path should have at least one rectangle stroke.")
        pdb.gimp_message("このスクリプトは、パスの内部にひとつ以上のストロークが必要です")
        return





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

    def get_control_points(c,idx):
        return ( c[idx*6+2],c[idx*6+3] )

    def copy_layer(src):
        dup=pdb.gimp_layer_new_from_drawable(src,a_img)
       #pdb.gimp_image_insert_layer(a_img,dup,src.parent,0)
        pdb.gimp_image_insert_layer(a_img,dup,None,0)
        return dup

    try:

        generated_layers=[]

        if pdb.gimp_selection_is_empty(a_img)==0:# 1 means selection IS EMPTY.so selection exists when 0.
            dup=copy_layer(a_img.active_layer)
            source=pdb.gimp_selection_float(dup,0,0)
            pdb.gimp_floating_sel_to_layer(source)
            a_img.remove_layer(dup)
            pdb.gimp_selection_none(a_img)
        else:
            if len(a_img.active_layer.children) > 0: # whether layer group or not
                source=copy_layer(a_img.active_layer)
            else:
                source=a_img.active_layer
           #source=a_drawable

        # transform settings.change your favorite ones. 
        # 変形設定。好きなように改造してください。
        pdb.gimp_context_set_transform_direction(0) # 面の向き? 0=FORWARD 1=BACKWARD
        pdb.gimp_context_set_transform_resize(2)    # 変形ではみ出した時の処理 0=ADJUST 1=CLIP 2=CROP 3=CROP WITH ASPECT
        pdb.gimp_context_set_transform_recursion(3) # サンプリングレベル(1以上) デフォルトは3
        pdb.gimp_context_set_interpolation(1) # 補間有り

        for stroke in v.strokes:
            ctl_points=stroke.points[0]

            if len(ctl_points)/6 != 4:
               #pdb.gimp_message("the path of ID %d has %d control point(s),so not rectangular。" % (stroke.ID,ctl_cnt))
                pdb.gimp_message("ID %dのパスは %d つの制御点を持ち、四角形ではありません。" % (stroke.ID,ctl_cnt))
            else:
                x0,y0=get_control_points(ctl_points,0) # control point 0 = upper-left
                x1,y1=get_control_points(ctl_points,1) # control point 1 = upper-right
                x2,y2=get_control_points(ctl_points,3) # control point 3 = lower-right
                x3,y3=get_control_points(ctl_points,2) # control point 2 = lower-left

                if use_group_as_list and len(source.children) > 0:
                    target=copy_layer(source.children[random.randint(0,len(source.children)-1)])
                else:
                    target=copy_layer(source)

                # flip related
                flip_x=xflip_chance > random.random()
                flip_y=yflip_chance > random.random()
                if flip_x or flip_y:
                    if flip_x and flip_y:
                        target=pdb.gimp_item_transform_flip(target,0.0,0.0,float(target.width),float(target.height))
                    elif flip_x:
                        hw=(target.width)/2.0
                        target=pdb.gimp_item_transform_flip(target,hw,0.0,hw,float(target.height))
                    else:
                        hh=(target.height)/2.0
                        target=pdb.gimp_item_transform_flip(target,0.0,hh,float(target.width),hh)

                target=pdb.gimp_item_transform_perspective(target,
                         x0,y0,x1,y1,x2,y2,x3,y3)

                generated_layers.append(target)

                if force_visible:
                    target.visible=True

        a_img.remove_layer(source)

        if merge_all and len(generated_layers)>1:
            rl=generated_layers[-1]
            for cl in reversed(generated_layers[1:]):
                rl=a_img.merge_down(rl,0)
                

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



register(
        "python_fu_deform_to_vector_now",
        "deform-to-vector-now",
        "アクティブな四角形パスにはめ込むように遠近法変形を行う(簡易版)",
        "dothiko",
        "kakukaku world",
        "apr 2015", 
        "<Image>/Python-Fu/others/deform-to-vector-now", 
        "RGB*,GRAY*",
        [
        ],
        [],
        python_fu_deform_to_vector_now)

register(
        "python_fu_deform_to_vector",
        "deform-to-vector-dialog",
        "アクティブな四角形パスにはめ込むように遠近法変形を行う(詳細版)",
        "dothiko",
        "kakukaku world",
        "apr 2015", 
        "<Image>/Python-Fu/others/deform-to-vector", 
        "RGB*,GRAY*",
        [
            (PF_ADJUSTMENT,'xflip_chance',"水平反転の確率(0で反転しない)",0.0,(0.0,1.0,0.1)),
            (PF_ADJUSTMENT,'yflip_chance',"垂直反転の確率(0で反転しない)",0.0,(0.0,1.0,0.1)),
            (PF_BOOL,"use_group_as_list","レイヤグループの場合、メンバをランダムに配置する",True),
            (PF_BOOL,"force_visible","変形後のレイヤは元レイヤの状況に関係なく可視化する",True),
            (PF_BOOL,"merge_all","生成したレイヤを一つに統合する",False),
        ],
        [],
        python_fu_deform_to_vector)


main()