2019年1月16日水曜日

wxPython 画像をドラッグ操作でリサイズできるようにしました。



wxPython 画像をドラッグ操作でリサイズできるようにしました。

動作

デフォルトで、Lキーを押すと画像収納フォルダ開きます。
フォルダから画像ファイルをwxのウィンドウ上にドロップすると画像が表示されます。
zキーを押して、画像上でドラッグ(左クリックしたままマウスを動かす)すると
それにつれて画像がリサイズします。
yキーを押せば又デフォルト状態に戻ります。

参考サイト

wxPythonで画像表示とかドラッグ移動とか勉強中
http://blawat2015.no-ip.com/~mieki256/diary/201506251.html

コードの概要

フォルダからドロップした画像のデータは、MyObjを経てmyobjListに
保存されますが、myobjList上のbmpをリサイズしても画像がおかしくなりました。
で、一旦myobjListから取り出しDrawPanel上のself.bmp としてリサイズ
そしてまた元のmyobjListに戻しています。
描画は、DrawPanel内のDrawの
dc.DrawBitmap(self.bmp, self.bmp_pos.x, self.bmp_pos.y, True) リサイズ用
all_obj_draw(dc)  ドロップされた画像用
で描画されています。


全コード


E:\MyBackups\goolgedrive\myprg_main\python_my_prg\wxpython\dc\お絵かき3\gazou_scale_two.

# coding: UTF-8

import wx

#MyObjのリスト
myobjList  = []


class MyFrame(wx.Frame):
    #メニューバー 、ステータスバー,ツールバーなどはFrmae上し
    #か置けないため、paelを作成し、そこで描画する
    ## *args 可変長タプル  **kwargs 可変長辞書
    def __init__(self, parent, *args ,**kargs):
        frame = wx.Frame.__init__(self, parent, *args, **kargs)

        pal = DrawPanel(self)

        #画像、またはエクスプローラ上の画像ファイルをドラッグした時に
        #描画のアップデートをする関数を指定する
        func = pal.InitBuffer
        #画像のドラッグ機能をMyFrameに組み込む Panelには組み込めない
        #ようだ
        MetaMyFileDropTarget(self, func)
        self.Show()


"""ダブルバッファで表示するFrame"""
class DrawPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.bmp = None
        self.bmp_pos = wx.Point(0,0)
        self.InitBuffer()

        self.child_list = []
        da = DropAction(self)
        self.child_list.append(da)

        dp = ResizeAction(self)
        self.child_list.append(dp)

        self.bd = DropMyBind(self, self.child_list)

        # RefreshによってOnPaintがよばれている
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)


    """画面書き換え要求があった時に呼ばれる処理"""
    def OnPaint(self, event):
        print "OnPaint"
        dc = wx.BufferedPaintDC(self, self.buffer)


    """ウインドウサイズが変更された時に呼ばれる処理"""
    #かなり複雑になるのでこのイベントやめる
    #以下のコードによりかなりbufferを大きくしているので、OnSizeの必要がない。
    # self.buffer = wx.EmptyBitmap(1280, 708)
    def OnSize(self, event):
        print "OnSize"
        pass

    """描画更新"""
    def InitBuffer(self):
        #print "UpdateDrawing"
        self.buffer = wx.EmptyBitmap(1280, 708)
        dc = wx.BufferedDC(None, self.buffer)
        dc.SetBackground(wx.Brush('white'))
        dc.Clear()  # デバイスコンテキストでクリア
        self.Draw(dc)  # 実際の描画処理
        # Falseを指定して背景を消さなくしたら画面のちらつきが出なくなった
        self.Refresh(eraseBackground=False)

    """画面を一旦クリアしないと画像がずれたように描画される"""
    def clearWindow(self):
        #print "UpdateDrawing"
        self.buffer = wx.EmptyBitmap(1280, 708)
        dc = wx.BufferedDC(None, self.buffer)
        dc.SetBackground(wx.Brush('white'))
        dc.Clear()  # デバイスコンテキストでクリア
        #self.Draw(dc)  # 実際の描画処理
        # Falseを指定して背景を消さなくしたら画面のちらつきが出なくなった
        #self.Refresh(eraseBackground=False)

    """実際の描画処理"""
    def Draw(self, dc):
        #print "Draw"

        dc.SetBrush(wx.Brush(wx.Colour(128, 255, 128)))
        dc.SetPen(wx.Pen("RED", 3))
        #背景が透過の画像でテストするため、わざと円の描画を大きくしている。
        #ウィンドウの背景が。黄緑色?になっている
        #つまり 輪郭が赤中が黄緑色?になる
        dc.DrawCircle(100, 100, 1000)

        if self.bmp is not None:
            #引数の数字は画像の位置座標
            #Trueで背景透過の画像も透過のままで表示できる
            dc.DrawBitmap(self.bmp, self.bmp_pos.x, self.bmp_pos.y, True)

        #myobjListで今までドラッグした画像を保存しておいて
        #ドラッグ完了すると、ドラッグ中の画像もふくめて
        #描画している
        """ドラッグした画像すべてを描画する  """
        all_obj_draw(dc)


"""ドラッグした画像すべてを描画する  """
#myobjListにはMyObjのobjがすべて入っている
def all_obj_draw(dc):
    for obj in myobjList:
        #obj.setScl()
        obj.Draw(dc)  # オブジェクトを描画


"""マウス座標と重なってるオブジェクトを返す"""
def FindMyObj(pos):
    result = None
    myobj_index = 0
    for obj in myobjList:
        #posマウスの座標と重なっているobjがあればTrue
        if obj.HitTest(pos):
            myobj_index = myobjList.index(obj)
            result = obj
    return result, myobj_index


"""マウスドラッグで移動できるオブジェクト用のクラス"""
#そのオブジェクトの描画、座標の管理などをしている
class MyObj():
    def __init__(self, bmp, x=0, y=0):
        self.bmp = bmp  # bitmapを記録
        self.pos = wx.Point(x, y)  # 表示位置を記録
        self.diff_pos = wx.Point(0, 0)

        self.width = self.bmp.GetWidth()
        self.height = self.bmp.GetHeight()

        self.scl = 1

        #以下のコードだと引数の大きさに画像がちぎれて来るだけ
        #self.bmp.SetSize((50,50))
        #画像のサイズ変更
        #self.bmp = scale_bitmap(self.bmp, 150, 150)

    """与えられた座標とアタリ判定して結果を返す"""
    def HitTest(self, pos):
        #pntはマウスpos selfはオブジェクト
        #で、オブジェクト内にマウスが入っていればTrue
        rect = self.GetRect()  # 矩形領域を取得
        return rect.InsideXY(pos.x, pos.y)  # 座標が矩形内に入ってるか調べる

    """画像の表示位値から画像の幅高さが入る矩形領域を返す"""
    def GetRect(self):
        return wx.Rect(self.pos.x, self.pos.y,
                       self.bmp.GetWidth(), self.bmp.GetHeight())

    """マウス座標と自分の座標の相対値を記録。"""
    """    この情報がないと、画像をドラッグした時の表示位置がしっくりこない"""   
    def SavePosDiff(self, pnt):
        #pnt:マウス座標
        self.diff_pos.x = self.pos.x - pnt.x
        self.diff_pos.y = self.pos.y - pnt.y


    """与えられたDCを使って画像を描画する"""
    def Draw(self, dc):
        print "MyObj Draw"
        if self.bmp.Ok():
            r = self.GetRect()  # 矩形領域を取得
            print "MyObj Draw self.GetRect()",r
            # ペンを設定しないと何故か描画できない
            # 私の環境ではok
            #dc.SetPen(wx.Pen(wx.BLACK, 4))

            # 画像を描画
            dc.DrawBitmap(self.bmp, r.x, r.y, True) 
            #dc.DrawBitmap(self.bmp, self.pos.x, self.pos.y, True) 

            # 画像枠を描画
            dc.SetBrush(wx.TRANSPARENT_BRUSH)  # 透明塗り潰し
            dc.SetPen(wx.Pen(wx.RED, 1))  # 赤い線を指定
            # 矩形を描画
            dc.DrawRectangle(r.x, r.y, r.width, r.height)


"""ドラッグアンドドロップ担当クラス"""
"""あとで、MyFrameに組み込まれる """
class MyFileDropTarget(wx.FileDropTarget):
    def __init__(self, func):
        wx.FileDropTarget.__init__(self)
        self.func = func
        #self.dc = dc
        self.x = 0
        self.y = 0

    #この関数どこからもよびだされていない。
    #内部的によびだされいるのだろう
    #ただ単にドロップ時に実行する関数を指定しているだけ
    #自動的にドロップ時のfilenamesを取得している
    """ファイルをドロップした時の処理"""
    def OnDropFiles(self, x, y, filenames):
        print "OnDropFiles"
        #実験したらわかるが以下のfor文をとおさないことには実際の
        #パスが得られない
        for filepath in filenames:
            #ファイルをドロップした時に実行される関数を以下に設定
            self.evt_foo(filepath, self.func) 

    #ファイルをドロップした時に実際に実行される関数
    def evt_foo(self, filepath, func):
        print "evt_foo"

        #ファイルパスから直接ビットマップを作成している?
        b = wx.Bitmap(filepath)
        #画像のファイルパスと座標を受け取っている
        obj = MyObj(b,  self.x, self.y)
        myobjList.append(obj)
        self.x += 32
        self.y += 32
        # 描画更新
        func()


#MyFileDropTargetを組み込む関数
def MetaMyFileDropTarget(main_obj, func):
    # ファイルドロップの対象をフレーム全体に
    #MyFrameにドロップされたら...という設定
    main_obj.droptarget = MyFileDropTarget(func)
    # ファイルドロップ受け入れを設定
    main_obj.SetDropTarget(main_obj.droptarget)



class DropMyBind():
    def __init__(self, parent, child_list):
        self.key = 89

        self.parent = parent
        self.drop   = child_list[0]
        self.resize = child_list[1]
        self.foo  = self.drop

        #メインbind
        self.parent.Bind(wx.EVT_KEY_DOWN, self.OnKeyDownEvent)   
        #サブbind
        self.parent.Bind(wx.EVT_LEFT_DOWN, self.OnLeftButtonDownEvent)
        self.parent.Bind(wx.EVT_LEFT_UP,   self.OnLeftButtonUpEvent)
        self.parent.Bind(wx.EVT_MOTION,     self.OnMotionEvent)


    def OnKeyDownEvent(self, event):
        print "bind OnKeyDownEvent"
        #g71 c67 x88  y89
        self.key = event.GetKeyCode()
        print self.key
        print
        #resizeの作業をする
        #dropの作業をする場合は    drawで parent.bd.key = 89 として
        #ここのkeyを89にする。 dropからdrawに行く場合も同様
        #キーzがおされた
        if 90==self.key:
            self.foo = self.resize   
            print "bind self.key", self.key
        #drop の作業をする
        #キーyがおされた
        if 89==self.key:
            self.foo = self.drop   
            print "bind self.key", self.key
        #xキーがおされた.ダミー処理
        if 88==self.key:
            pass
            print "bind self.key", self.key
        #キーにより書くイベント処理関数に分岐
        self.foo.OnKeyDownEvent(event)   

    def OnLeftButtonDownEvent(self, event):
        print "bind OnLeftButtonDownEvent"
        self.foo.OnLeftButtonDownEvent(event)

    def OnLeftButtonUpEvent(self, event):
        self.foo.OnLeftButtonUpEvent(event)
   
    def OnMotionEvent(self, event):
        self.foo.OnMotionEvent(event)


class DropAction():
    def __init__(self, parent):
        print "dropAction"
        # マウスドラッグ処理用の変数を確保
        self.parent = parent
        self.drag_myobj = None
        self.drag_start_pos = wx.Point(0, 0)

        self.key = 0
   
    """マウスの左ボタンが押された時の処理"""
    def OnLeftButtonDownEvent(self, event):
        print "drop OnLeftButtonDownEvent"
        # マウス座標を取得
        pos = event.GetPosition() 
   
        """マウス座標と重なってるオブジェクトを取得"""
        myobj_tuple = FindMyObj(pos) 
        myobj = myobj_tuple[0]
   
        #objが空でないなら、何かobjがあったら
        #isのほうが==より処理がはやい
        if myobj is not None:
            #ドラッグ移動するオブジェクトを記憶
            self.drag_myobj = myobj 
            # ドラッグ開始時のマウス座標を記録
            #以下の変数どこにもつかわれていない
            self.drag_start_pos = pos 
            #マウス座標と自分の座標の相対値を記録。"""
            #自分:myobjの画像のことだろう
            #posでマウスposを引数にしている
            self.drag_myobj.SavePosDiff(pos)

   
    """マウスの左ボタンが離された時の処理"""
    def OnLeftButtonUpEvent(self, event):
        print "drop_OnLeftButtonUpEvent"
        if self.drag_myobj is not None:
            pos = event.GetPosition()
            self.drag_myobj.pos.x = pos.x + self.drag_myobj.diff_pos.x
            self.drag_myobj.pos.y = pos.y + self.drag_myobj.diff_pos.y
            print self.drag_myobj.pos.x , self.drag_myobj.pos.y
   
        self.drag_myobj = None

        #parentの描画更新の関数の呼び出し   
        self.parent.clearWindow()
        self.parent.InitBuffer()
   
   
    "マウスカーソルが動いた時の処理"""
    def OnMotionEvent(self, event):
        print "drop_OnMotionEvent"
        if self.drag_myobj is None:
            # ドラッグしてるオブジェクトが無いなら処理しない
            return
       
        # ドラッグしてるオブジェクトの座標値をマウス座標で更新
        pos = event.GetPosition()
        self.drag_myobj.pos.x = pos.x + self.drag_myobj.diff_pos.x
        self.drag_myobj.pos.y = pos.y + self.drag_myobj.diff_pos.y

        #parentの描画更新の関数の呼び出し   
        self.parent.clearWindow()
        self.parent.InitBuffer()  # 描画更新


    #Lキーをおしてファイルダイヤログをひらく
    def OnKeyDownEvent(self, event):
        print "drop_OnKeyDownEvent"
        self.key = event.GetKeyCode()
        print self.key
        # キーLがおされた
        if 76==self.key:
            self.openDialog()

    def openDialog(self):
        dp = "E:\MyBackups\goolgedrive \myprg_main\python_my_prg\wxpython\dc\gazou_drop"
        #osのファイルダイヤログをよびだす
        # 引数 dp 開くフォルダ "":デフォルトのファイル名 
        # *.png : pngファイルだけ表示

        """Dialogの親は、parentになる"""   
        d = wx.FileDialog(self.parent, "画像ファイル", dp,"","*.png")
        d.ShowModal()
        d.Destroy()


class ResizeAction():
    #def __init__(self, bmp):
    def __init__(self, parent):
        print "ResizeAction"
        self.drag_myobj = None  #bitmap画像の事
        self.drag_start_pos = wx.Point(0, 0)
        self.diff_pos = wx.Point(0, 0)

        self.bmp = None
        self.bmp_pos = wx.Point(0, 0)

        self.key = 0
        self.parent = parent
        #self.parent = parent
        self.width  = 0
        self.height = 0
        self.myobj_index = 0

        self.gs = None

    def OnKeyDownEvent(self, event):
        print "resize OnKeyDownEvent"

    def OnLeftButtonDownEvent(self, event):
        print "resize_OnLeftButtonDownEvent"
        # マウス座標を取得
        pos = event.GetPosition() 

        """マウス座標と重なってるオブジェクトを取得"""
        myobj_tuple = FindMyObj(pos) 
        myobj = myobj_tuple[0]
        self.myobj_index = myobj_tuple[1]
   
        if myobj is not None:
            self.drag_start_pos = pos 
            self.drag_myobj = myobj 

            self.bmp        = self.drag_myobj.bmp
            self.bmp_pos    = myobj.pos

            self.parent.bmp_pos = myobj.pos
            #一旦リサイズする画像をリストから削除、この画像は
            #parentのDrawBitmapで画像表示
            del myobjList[self.myobj_index]

            self.gs = GazouScale(self.bmp)


    def OnLeftButtonUpEvent(self, event):
        print "resize_OnLeftButtonUpEvent"
        self.drag_myobj = None
        #myobjListにこの画像を再び追加、この画像は、all_obj_drawから描画
        obj = MyObj(self.bmp,  self.bmp_pos.x, self.bmp_pos.y)
        myobjList.append(obj)
        self.parent.bmp = None

        #parentの描画更新の関数の呼び出し   
        self.parent.clearWindow()
        self.parent.InitBuffer()
   
   
    def OnMotionEvent(self, event):
        print "resize OnMotionEvent"
        if self.drag_myobj is None:
            # ドラッグしてるオブジェクトが無いなら処理しない
            return

        pos = event.GetPosition()
        self.diff_pos   = pos - self.drag_start_pos
        self.bmp        = self.gs.scale_bitmap(self.diff_pos)
        self.parent.bmp = self.gs.scale_bitmap(self.diff_pos)

        #parentの描画更新の関数の呼び出し   
        self.parent.clearWindow()
        self.parent.InitBuffer()  # 描画更新



""" 画像のリサイズ """
#myobjListのbmpをリサイズする方法もあるが、これだとリサイズすると画像が 
#なぜかくずれてくる。で、一旦myobjListからbmpだけを取り出し、リサイズ
# そして又myobjListにもどしている。
class GazouScale():
    def __init__(self, bmp):
        print "GazouScale"
        self.bmp = bmp
        #self.parent = parent
        self.width = self.bmp.GetWidth()
        self.height = self.bmp.GetHeight()

    #画像のリサイズ
    def scale_bitmap(self, diff_pos):
        print "scale_bitmap"
        #5 ドラッグに対してあまりdiff_posが大きくならにようにする
        self.width  += diff_pos.x/5
        self.height += diff_pos.y/5
        #あまり小さくなるとエラーを起こす
        if self.width < 50:
            self.width = 50
        if self.height < 50:
            self.height = 50
        print "self.width",self.width
        print "self.height",self.height
        print
        image = wx.ImageFromBitmap(self.bmp)
        #下の行のコードでは一部透過とならない所ができる
        #image = image.Scale(self.width, self.height, wx.IMAGE_QUALITY_HIGH)
        image = image.Scale(self.width, self.height)
        result = wx.BitmapFromImage(image)
        return result

if __name__ == '__main__':
    app = wx.App(False)
    MyFrame(None, -1, "Draw Test 4", size=(800, 600))
    app.MainLoop()



0 件のコメント:

コメントを投稿

About

参加ユーザー

連絡フォーム

名前

メール *

メッセージ *

ブログ アーカイブ

ページ

Featured Posts