0 PyQtで画像にsvgの矢印を入れる

190504

はじめに

自分で撮ったスクリーンショットに矢印を入れてみたくなりました
矢印は自由変形でも劣化しないようにベクター系にしたい
そうなるとペイント系とドロー系の2種類の画像を扱うことになります
Qtの画像処理には様々なものがあり、他のライブラリを頼らずにQtだけで十分イケますが、簡単なチュートは見つけることが出来なかったので、書いてみることにしました
間違いがあったら指摘いただけると嬉しいです
よろしくお願いします

  • 環境
    win7sp1-64  Python 3.72  PyQt 5.12

コード

まずコードを全て載せます
pyファイルは一つです
すぐ実行出来るように、素材の矢印ファイル(svg)も書いておきます
テキストです、拡張子を.svgにして保存して下さい
背景画像は何でも良いっす

import sys
from PyQt5.QtWidgets import (QApplication, QGraphicsItem, QGraphicsScene, QGraphicsView,
        QGraphicsDropShadowEffect)
from PyQt5.QtSvg import QGraphicsSvgItem
from PyQt5.QtGui import  QPixmap , QColor 
from PyQt5.QtCore import Qt,QSizeF ,QPoint,QPointF

# Movable属性をセットして自分では書かない

class svg(QGraphicsSvgItem):
    def __init__(self,filename):
        super().__init__(filename)
        self.setCursor(Qt.OpenHandCursor)
        self.setAcceptedMouseButtons(Qt.LeftButton)
        self.setFlag(QGraphicsItem.ItemIsSelectable,1)
        self.setFlag(QGraphicsItem.ItemIsMovable,1)
        self.setGraphicsEffect(QGraphicsDropShadowEffect())

        self.setTransformOriginPoint(self.boundingRect().width()/2,
            self.boundingRect().height()/2)
        self.angle=0
        self.scale=1.0
        self.distance=5.0
        self.radius=1.0

    def mousePressEvent(self, event):
        self.setCursor(Qt.ClosedHandCursor)
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        self.setCursor(Qt.OpenHandCursor)
        super().mouseReleaseEvent(event)

    def mouseMoveEvent(self, event):
        super().mouseMoveEvent(event)

    def keyPressEvent(self, event):

        if (event.modifiers() & Qt.ShiftModifier): # shift
            if (event.key() == Qt.Key_Up):
                self.distance += 0.5
            elif (event.key() == Qt.Key_Down):
                self.distance -= 0.5
            elif (event.key() == Qt.Key_Right):
                self.radius += 0.5
            elif (event.key() == Qt.Key_Left):
                self.radius -= 0.5
            self.graphicsEffect().setOffset(self.distance)
            self.graphicsEffect().setBlurRadius(self.distance)
            return

        if (event.key() == Qt.Key_Right):
            self.angle += 1
        elif (event.key() == Qt.Key_Left):
            self.angle -= 1
        elif (event.key() == Qt.Key_Up):
            self.scale += 0.1
        elif (event.key() == Qt.Key_Down):
            self.scale -= 0.1
        # elif (event.key() == Qt.Key_Escape):
        #     return

        self.setScale(self.scale)
        self.setRotation(self.angle)



class SvgView(QGraphicsView):
    def __init__(self, parent=None):
        super(SvgView, self).__init__(parent)
        self.svgItem = svg(<pas>)
        self.svgItem2 = svg(<pas>)
        self.pix = QPixmap(<pas>)

        self.setScene(QGraphicsScene(self))
        self.scene().addPixmap(self.pix)
        self.scene().addItem(self.svgItem)
        self.scene().addItem(self.svgItem2)
        self.scene().selectionChanged.connect(self.selectionChanged)
        self.selected=None

    def selectionChanged(self):
        if self.selected :
            self.selected.ungrabKeyboard()
        p=self.scene().selectedItems()
        if len(p) >= 1 :
            p=p[0]
            p.grabKeyboard()
            self.selected=p
    
app = QApplication(sys.argv)
window = SvgView()
window.show()
sys.exit(app.exec_())
  • arrow.svg
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="1000" height="1500" viewBox="0 0 1000 1500" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <g transform="matrix(1.142076 -0.013494 0.018638 1.577387 -116.619079 -364.626648)">
    <path transform="matrix(0.875472 0.00749 -0.010345 0.633866 98.324981 232.000289)" stroke-width="0" stroke-miterlimit="10" fill="#ff0000" fill-rule="evenodd" stroke="#ff0000" d="M373.09 487.83 L315.95 522.32 L386.77 308.67 L493.47 491.01 L432.5 481.3 C439.75 566.75 444.7 721.09 505.76 870.59 C401.11 729.95 386.14 535.67 373.09 487.83 Z"/>
  </g>
</svg>
  • arrow_black.svg
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="304" height="138" viewBox="0 0 304 138" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <g transform="matrix(1 0 0 1 -164 -162)">
    <g id="aNtch0pLc">
      <path fill="#05010b" d="M254.52 162.23 L164.59 230.86 L254.52 299.49 L254.52 254.88 L377.59 254.88 L377.59 299.49 L467.52 230.86 L377.59 162.23 L377.59 206.84 L254.52 206.84 L254.52 162.23 Z"/>
    </g>
  </g>
</svg>

基本的な仕組みについて

  • Qtにおける画像処理について、今回のように複数の画像オブジェクトを扱う場合は、QgaphicsItem・QGraphicsScene・QGraphicsViewの仕組みを使うのが良いと思います
  • Itemは部品です。画像もベクタパスも部品になります。それをsceneに登録して、viewで表示します
    私としてはviewとsceneが分離されている理由が今ひとつ理解できてませんが、使い方は簡単です
  • この仕組みを使わず、適当なwidgetを用意してそこにpaintしても同じ事は出来ますが、この仕組みを利用した方が色んな事が簡単に出来る、生産性が高いってことです

コードの全体像

  • メインのウインドウはQGraphicsViewになります
    画像と矢印はQgaphicsItemとしてファイルから読み込んで作り、QGraphicsSceneに追加して、QGraphicsViewで表示する流れです
  • アイテムの属性を選択可・移動可にしておくだけで、マウスで簡単に移動できるようになりものすごく便利です!!
  • 矢印をキーで拡大・回転出来るようにしましたが、この操作はViewでもQgaphicsItem自身でも出来ます
    Itemでやる場合はサブクラスを作ってそこにコードを書くことになります
    Sceneに登録するアイテムが少なければviewで操作しても良いと思うけど、多くなると管理が大変なので、アイテム自身にやらせた方が良いと思い、私はサブクラスを作って操作することにしました
  • ここら辺はプログラムの設計の問題だと思いますが、この方法だと多分アイテムの種類毎にサブクラスを作らなければいけなくなる。
    種類が増えた場合は親のviewで一括して管理した方がコードは短くてすむような気もするけどどうだろう??
  • オブジェクトの変形は普通はマウスでグリグリですよね。キーの方が簡単なので現状はとりあえずということで・・・

細かい解説

コードの中で気がついたところを簡単に解説します


svgについて

  • 矢印のベクタデータはsvg形式としました
    Qt環境でベクタデータを扱う場合、基本はPathを使うべきだと思いますが、私には今ひとつ理解できてないのと、保存して再利用する方法がわからなかったのでsvgにしました。しかしpathデータでも同じやり方で出来るはずです
  • 矢印以外のオブジェクトを作成したければ、オンラインのsvgエディタも豊富ですし、InkScapeでも簡単に作れます
    ただし自由な図形を描けるようになるには少し勉強が必要かもしれません

---> PyQt で画像にパスを加えるツールを作る #

0

メモを他の人に見せる

このメモを見せたい人に、このURL(今開いているページのURLです)を教えてあげてください

コメント(0)

  • someone

  • someone