dothikoのカクカクワールド2D REBOOT

プログラミングとかLinux関連(特にOSSのグラフィックツール関連)とかレトロゲームとか色々。

gimpのpython-fuで作る四角形の自由変形プラグイン

昔、サターンというゲーム機があり(って別に解説は要りませんなw) 、その特徴が自由変形スプライトだった…と聞いております。プレイしたことはありますが開発環境など、触ったことは無いので詳しいことはわかりません。

それが確か、四角形の四頂点の位置を自由に指定できる、というものだったらしいのですね。まぁ、自由にといっても制限はあるのでしょうけども。

さてそんな前フリをしつつ、先日gimpでお絵かきに勤しんでいたところ、床を描画する必要が出てきました。 それで、実際の床を撮影してテクスチャのようにし、遠近法変形を使用して床を作ろうとしたのですが、なにやら非常に使いづらい

まず変形プレビューで画面が覆われてしまい、どういう形にすればいいのかサッパリ分からなくなるのですな。まぁ、これは後で気づいたのですが、変形オプションのダイアログで透明度を設定すればマシになりますが、処理は重いし、枠が多すぎてなにやらという局面もありそう。

そこで思いついたのが、パスで四角形を作りパスを可視状態にすればガイドになる、ということです。 可視状態のパスは常に画像の上に表示されますからね。

その時はそれで済ませたのですが、gimpのプロシージャデータベースを見ていてハッと閃きました。

pdb.gimp_item_transform_perspectiveなんてものがあるじゃないか、と。

そこでさらに気づく!

あ、さっきやってみた、ガイド用のパスを作るように、四角形なパスの頂点をこのプロシージャに与えたら、遠近法変形というよりもはや自由変形じゃね?と

いやまぁ、自由変形スプライトというものがそもそも、そういうもんなのかもですがw

使い方

使い方は簡単、パスを時計回りで4頂点作ります。閉じても閉じなくてもいいですが、閉じたほうが自由変形しているという雰囲気が出ます。例では閉じていません。

なお、ベジェ曲線化しても単純にコントロールポイントの位置だけが利用され、曲線は無視されます。ので、操作を間違えて多少曲線になってしまっても変形には問題ありません。

f:id:dothiko:20150412005531j:plain

そして変形対象となるレイヤを用意します。

この場合は木の模様のようなテクスチャをレイヤとして読み込みました。全ての可視部分を覆ってしまいました…

f:id:dothiko:20150412005550j:plain

そしてそのパスをアクティブにしたまま(この時点で、パスを可視状態にしたほうがわかりやすいでしょう)レイヤ(もしくは選択領域)を設定してこのプラグインを起動します。

そこで実行。このように「パスに当てはめるように変形」します。選択範囲ですとフローティングになります。

f:id:dothiko:20150412005555j:plain

レイヤ重ね合わせを調整すると…なんか雰囲気出てる??(と思いたい)

f:id:dothiko:20150412005559j:plain

なんか、とても自由変形スプライトっぽくないですかー?

なお、このままでは、元々のレイヤを本当に変形させてしまいます。いわゆるdestructiveです。そのため、レイヤを破壊したくはない…という状況を考え、「アクティブなレイヤを自動で複製してそれを変形の対象にする」というもの(deform-to-vector-duplicate)も作りました。状況に応じて使い分けてください。

ダイアログ出すようにしても良かったかな…と思ったりもしますが、例えばビルの窓みたいなものをホイホイと次々と量産するような状況を考えてこうしてみました。*1

とは言え、実はまだそのような状況に出くわしたことはないのですけどね。

コード

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#[license] GPLv3
#[plugin]
#[name] deform-to-vector / deform-to-vector-duplicate
#[desc] 
#遠近法変形をパスの頂点を利用してスピーディーに実行する
#[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) 2013 dothiko(http://dothiko.blog.fc2.com/) 


from gimpfu import *
def python_fu_deform_to_vector_duplicate(a_img,a_drawable):
    deform_to_vector(a_img,a_drawable,True)

def python_fu_deform_to_vector(a_img,a_drawable):
    deform_to_vector(a_img,a_drawable,False)

def deform_to_vector(a_img,a_drawable,duplicate_as_layer):

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

    if len(v.strokes)!=1:
       #pdb.gimp_message("the stroke should be a rectangle,and only one at path.")
        pdb.gimp_message("このスクリプトは、ひとつの矩形だけを持つパスにしか対応しません")
        return

    ctl_points=v.strokes[0].points[0]

    if len(ctl_points)/6 != 4:
       #pdb.gimp_message("the stroke should be a rectangle.this stroke of path has %d control points." % ctl_cnt)
        pdb.gimp_message("このパスは %d つの制御点を持ち、矩形ではありません。" % ctl_cnt)
        return

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

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

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

        pdb.gimp_context_set_transform_direction(0)
        pdb.gimp_context_set_transform_resize(0)
        pdb.gimp_context_set_transform_recursion(3)


        if duplicate_as_layer:
            newlayer=pdb.gimp_layer_new_from_drawable(a_drawable,a_img)
            pdb.gimp_image_insert_layer(a_img,newlayer,a_img.active_layer.parent,0)
            target=newlayer
        else:
            target=a_drawable

        pdb.gimp_item_transform_perspective(target,#a_img.active_layer,
                 x0,y0,x1,y1,x2,y2,x3,y3)

        # postprocess:when a_drawable is floating,make it as layer,and delete duplicated one.
        if duplicate_as_layer and a_img.active_layer.is_floating_sel:
            pdb.gimp_floating_sel_to_layer(a_img.active_layer)
            a_img.remove_layer(newlayer)


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



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

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


main()

*1:パスの全てのストロークに対して同一のレイヤを用いて一斉に変形を行うというアイデアが湧いてしまった!!