PyQtでイメージビューワ

     画像を表示したいっ!! 俺は今、ムォォォォレツに画像を表示したいっ!!
     って事で今回はQGraphicsViewシリーズに関する話題で。

     Qtでイメージを表示する時にはいくつか方法があります。例えばQLabelにQPixmapをセットしてそれを表示するって言う方法がPyQt付属のサンプルで提供されてますね。
     しかし、その方法だと操作が微妙~(´ε`;)ウーン… 何が微妙かと言うと
    • 画像が枠より小さい場合、移動ができない。
    • スケールをかける際にマウスポイントを中心に・・・って事が難しい
    • 回転も無理ポ。。。
     などなど、色々制約がついてしまいますね。


     そんな不満いっぱいの我々に用意されているのがQGraphics~シリーズ.+゜*。:゜+(人*´∀`)+゜:。*゜+
     描画に関する事はこいつを使用すれば宜しいようです。
     ではではQGraphicsシリーズに初挑戦~


     まず、QGraphicsシリーズはアイテムビューのようなモデル・ビューアーキテクチャが採用されています。
     QTreeViewとQStandardItemみたいにアイテムを管理する部分と、表示を管理する部分が分かれているわけですね。アイテムビューとアイテムモデルの話はQStandardItemModel & QItemSelectionModelで無駄にAAいっぱいで書きましたが、あんな感じです。

    QGraphics~系では2次元グラフィックスのお話の話を参考にさせて頂きました。ありがたや~



    ■QGraphicsView
     ビュー、つまり見た目のガワに該当する部分にあたるのがこのQGraphicsViewです。
     QGraphicsSceneをこの中にセットして使用します。



    ■QGraphicsScene
     モデルに該当するのがこのQGraphicsSceneですね。この中に後述するQGraphicsItemから派生するオブジェクトをセットする事によってアイテムを描画します。



    ■QGraphicsItem
     実際に図として表示されるのがこのQGraphicsItemから派生するシリーズです。
     QGraphicsSceneの中にセット(追加)する事によって描画されます。
     描画だけでなく選択や移動なんかも出来たり、Transform(3×3の行列)を個別に持てたり、親子付けを出来たりと非常にフレキシブルに操作出来て使い勝手が良さそうですな。
     QGraphicsItemには次のような派生オブジェクトが用意されていて、用途に応じて使い分けます。 qGraphicsScene001.png
     各種アイテムを使ってみたの図。画像のアンチエリアスが意外と汚い? 細い線には弱いんですかね。
     これらのアイテムを使用してシーンに望んだ形状を描画していきます。今回はイメージビューアって事で、画像を表示するためのアイテムQGraphicsPixmapItemを使用しています。

    ではサンプルコードを
    # -*- coding:utf-8 -*-
     
    import sys
    from PyQt4 import QtGui, QtCore
     
    imagepath = ':/sampleImage.png'
     
    # /////////////////////////////////////////////////////////////////////////////
    # ビューを表示するためのシーン。                                             //
    # /////////////////////////////////////////////////////////////////////////////
    class ImageViewScene( QtGui.QGraphicsScene ):
        def __init__( self, *argv, **keywords ):
            super( ImageViewScene, self ).__init__( *argv, **keywords )
            self.__imageItem     = None
            self.__currentPos    = None
            self.__pressedButton = None
     
        def setFile( self, filepath ):
            # イメージをアイテムとしてシーンに追加するためのメソッド。
            pixmap = QtGui.QPixmap( filepath )
     
            # 既にシーンにPixmapアイテムがある場合は削除する。
            if self.__imageItem:
                self.removeItem( self.__imageItem )
     
     
            # 与えられたイメージをPixmapアイテムとしてシーンに追加する。-----------
            item = QtGui.QGraphicsPixmapItem( pixmap )
            # アイテムを移動可能アイテムとして設定。
            item.setFlags(
                QtGui.QGraphicsItem.ItemIsMovable
            )
            self.addItem( item )
            self.__imageItem = item
            # ---------------------------------------------------------------------
     
            self.fitImage()
     
        def imageItem( self ):
            return self.__imageItem
     
        def fitImage( self ):
            # イメージをシーンのサイズに合わせてフィットするためのメソッド。
            # アスペクト比によって縦にフィットするか横にフィットするかを自動的に
            # 決定する。
            if not self.imageItem():
                return
     
            # イメージの元の大きさを持つRectオブジェクト。
            boundingRect = self.imageItem().boundingRect()
            # シーンの現在の大きさを持つRectオブジェクト。
            sceneRect    = self.sceneRect()
     
            itemAspectRatio  = boundingRect.width() / boundingRect.height()
            sceneAspectRatio = sceneRect.width() / sceneRect.height()
     
            # 最終的にイメージのアイテムに適応するためのTransformオブジェクト。
            transform        = QtGui.QTransform()
     
            if itemAspectRatio >= sceneAspectRatio:
                # 横幅に合わせてフィット。
                scaleRatio = sceneRect.width() / boundingRect.width()
            else:
                # 縦の高さに合わせてフィット。.
                scaleRatio = sceneRect.height() / boundingRect.height()
     
            # アスペクト比からスケール比を割り出しTransformオブジェクトに適応。
            transform.scale( scaleRatio, scaleRatio )
            # 変換されたTransformオブジェクトをイメージアイテムに適応。
            self.imageItem().setTransform( transform )
     
        def mouseDoubleClickEvent( self, event ):
            self.fitImage()
     
        # マウスのドラッグ中の処理イベント。=======================================
        def mousePressEvent( self, event ):
            self.__currentPos    = event.scenePos()
            self.__pressedButton = event.button()
     
            if self.__pressedButton == QtCore.Qt.RightButton:
                cursorShape = QtCore.Qt.SizeAllCursor
            else:
                cursorShape = QtCore.Qt.ClosedHandCursor
            QtGui.qApp.setOverrideCursor( QtGui.QCursor( cursorShape ) )
     
        def mouseMoveEvent( self, event ):
            if not self.__currentPos:
                return
     
            # シーンの現在位置。
            cur = event.scenePos()
     
            value = cur - self.__currentPos
            self.__currentPos = cur
     
            # 最終的に適応するTransformオブジェクト。
            transform = self.imageItem().transform()
     
            # 現在のシーン上のマウスカーソル位置情報から、イメージアイテム上での
            # マウスカーソル位置を示すTransformオブジェクトを呼び出す。
            localTrs = self.imageItem().mapFromScene( cur )
     
            if self.__pressedButton == QtCore.Qt.RightButton:
                # 右ボタンでドラッグされた場合はスケール処理を実行。
                if value.x() < 0:
                    value = 0.9
                else:
                    value = 1.1
     
                # マウスカーソル位置を中心にスケールするように変換を実行。
                transform.translate( localTrs.x(), localTrs.y() ) \
                    .scale( value, value ) \
                    .translate( -localTrs.x(), -localTrs.y() )
            else:
                # それ以外のボタンでドラッグされた場合は移動処理を実行。
                transform *= QtGui.QTransform().translate( value.x(), value.y() )
     
            # 最終的に変換されたTransformオブジェクトを
            # イメージオブジェクトへ適応する。
            self.imageItem().setTransform( transform )
     
        def mouseReleaseEvent( self, event ):
            self.__currentPos    = None
            self.__pressedButton = None
            QtGui.qApp.restoreOverrideCursor()
            super( ImageViewScene, self ).mouseReleaseEvent( event )
        # =========================================================================
    # /////////////////////////////////////////////////////////////////////////////
    #                                                                            //
    # /////////////////////////////////////////////////////////////////////////////
     
     
     
             
     
    # /////////////////////////////////////////////////////////////////////////////
    # メインとなるビュー。                                                       //
    # /////////////////////////////////////////////////////////////////////////////
    class ImageViewer( QtGui.QGraphicsView ):
        def __init__( self ):
            super( ImageViewer, self ).__init__( )
     
            # QGraphicsViewの設定。------------------------------------------------
            self.setCacheMode( QtGui.QGraphicsView.CacheBackground )
            self.setRenderHints( QtGui.QPainter.Antialiasing |
                QtGui.QPainter.SmoothPixmapTransform |
                QtGui.QPainter.TextAntialiasing
            )
            # ---------------------------------------------------------------------
     
            # QGraphicsSceneの作成・および設定。------------------------------------
            scene = ImageViewScene( self )
            scene.setSceneRect( QtCore.QRectF( self.rect() ) )
            self.setScene( scene )
            #scene.setFile( imagepath )
            # ---------------------------------------------------------------------
     
        def setFile( self, filepath ):
            # ビューが持つシーンにファイルパスを渡して初期化処理を
            # 実行するメソッド。
            self.scene().setFile( filepath )
             
        def resizeEvent( self, event ):
            # ビューをリサイズ時にシーンの矩形を更新する。
            super( ImageViewer, self ).resizeEvent( event )
            self.scene().setSceneRect( QtCore.QRectF( self.rect() ) )
    # /////////////////////////////////////////////////////////////////////////////
    #                                                                            //
    # /////////////////////////////////////////////////////////////////////////////
     
     
    if __name__ == '__main__':
        app = QtGui.QApplication( sys.argv )
        viewer = ImageViewer()
        # QGraphicsSceneにイメージパスをセットする。
        viewer.setFile( imagepath )
        viewer.show()
         
        sys.exit( app.exec_() )
    
    qGraphicsScene002.png

     これで、パスで指定した画像を表示できるようになりました!☆^v(*^∇')乂('∇^*)v^☆ヤター
     また、今回のサンプルではマウス左ボタンドラッグで画像を移動、マウス右ボタンドラッグで画像の拡大縮小、ダブルクリックで画像をフィットする機能もとりあえずつけときました。
     これでひと通り必要そうな機能はそろったかなぁ・・・?


     アイテムの表示演算を行列(QTransform)で出来るため、慣れると非常に心強いのもいいですね~(*^_^*)


     QGraphics~シリーズは他のUI要素と比べて名前の通りグラフィカルなモノを作りやすく設計されているため、今まで以上に面白いインターフェースが作れそうでワシ好みっすな~。
     また上記で紹介したアイテム以外にも通常のWidgetを代理として組み込めるProxyWidgetなども用意されているので広がりは無限大!!

     ちょっとコレ使ってネタを考えてみるか~
    スポンサーサイト

    コメントの投稿

    非公開コメント

    承認待ちコメント

    このコメントは管理者の承認待ちです
    プロフィール

    Eske

    Author:Eske
    萌えイラストレーターを目指す3DCGイラストレーター。
    現在ポケモンカードゲーム、ガンダムトライエイジ、ガンダムコンクエスト、妖怪ウォッチとりつきカードゲームなどで3DCGを使用したイラストレーターとして参加中。

    主にここでは日々気づいたメモなんかを残してます。
    イラストのお仕事も受け付けております。ココのメールアドレスからご連絡できますので、お気軽にご相談下さい。

    最新記事
    最新コメント
    カテゴリ
    最新トラックバック
    月別アーカイブ
    検索フォーム
    リンク
    QRコード
    QR