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

2019/07/22 15:57 by yamasyuh68
  :追加された部分   :削除された部分
(差分が大きい場合、文字単位では表示しません)
ビットマップPyQtで画像にSVGデータsvgの矢印配置す入れ
190721

- 数ヶ月前ってあったプログラム

190504
# はじめ
自分で撮たスクリーンショットに矢印を入れみたくなりました
矢印は自由変形でも劣化しないようにベクター系にしたい
そうなるとペイント系とドロー系の2種類の画像を扱うことになります
Qtの画像処理には様々なものがり、他のライブラリを頼らずにQtだけで十分イケますが、簡単なチュートは見つけることが出来なかったので、書いてみることにしました
間違いがあったら指摘いただけると嬉しいです
よろしくお願いします
- 環境
win7sp1-64  Python 3.72  PyQt 5.12
 https://live.staticflickr.com/65535/48337307742_bef60a599c_b.jpg

## コード
まずコードを全て載せます
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の仕組みを使うのが良いと思います
これらの関係の簡単な概念図を私なりに書いてみました
---------figure-------------
ちょっと面倒に見えますがコードをご覧いただいたように、全然面倒ではないです
この仕組みを使わず、適当なwidgetを用意してそこにpaintしても同じ事は出来ますが、この仕組みを利用した方が色んな事が簡単に出来る、生産性が高いってことです
## コードの全体像
メインのウインドウはQGraphicsViewになります
画像と矢印はQgaphicsItemとしてファイルから読み込んで作り、QGraphicsSceneに追加して、QGraphicsViewで表示する流れです
アイテムの属性を選択可・移動可にしておくだけで、マウスで簡単に移動できるようになります
矢印をキーで拡大・回転出来るようにしましたが、この操作はViewでもQgaphicsItem自身でも出来ます
Itemでやる場合はサブクラスを作ってそこにコードを書くことになります
Sceneに登録するアイテムが少なければviewで操作しても良いと思うけど、多くなると管理が大変なので、アイテム自身にやらせた方が良いと思い、私はサブクラスを作って操作することにしました
## 細かい解説
コードの中で気がついたところを簡単に解説します
①
②
③
## svgについて
矢印のベクタデータはsvg形式としました
Qt環境でベクタデータを扱う場合、基本はPathを使うべきだと思いますが、私には今ひとつ理解できてないのと、保存して再利用する方法がわからなかったのでsvgにしました。しかしpathデータでも同じやり方で出来るはずです
矢印以外のオブジェクトを作成したければ、オンラインのsvgエディタも豊富ですし、InkScapeでも簡単に作れます
ただし自由な図形を描けるようになるには少し勉強が必要かもしれません




- スクリーンキャプチャのプログラムを書いたときにそのまま矢印なども入れれたら良いかなと思って作った
- パーツは画像だと劣化するのでベクトル系にしたかったが、パスは難しいので他のソフトでパーツを作ってSVGで書き出して利用することにした
- Qt の QGraphicsView 、QGraphicsScene の使い方の勉強になった
- svgパーツの拡大・縮小・回転はサブクラス化して自分でやらせてる
**ただしキーボード対応**
マウスでグリグリが本来だと思うが難しそうだったので┐('д')┌

- 色んなパーツが使えるので広がりがある
**改めてQtのすごさを感じました**


---
--> [PyQt でベクターオブジェクト作成ツールを作る #](https://mimemo.io/m/3Rx1XoR367le95E)
      

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の仕組みを使うのが良いと思います
これらの関係の簡単な概念図を私なりに書いてみました
---------figure-------------
ちょっと面倒に見えますがコードをご覧いただいたように、全然面倒ではないです
この仕組みを使わず、適当なwidgetを用意してそこにpaintしても同じ事は出来ますが、この仕組みを利用した方が色んな事が簡単に出来る、生産性が高いってことです

コードの全体像

メインのウインドウはQGraphicsViewになります
画像と矢印はQgaphicsItemとしてファイルから読み込んで作り、QGraphicsSceneに追加して、QGraphicsViewで表示する流れです
アイテムの属性を選択可・移動可にしておくだけで、マウスで簡単に移動できるようになります
矢印をキーで拡大・回転出来るようにしましたが、この操作はViewでもQgaphicsItem自身でも出来ます
Itemでやる場合はサブクラスを作ってそこにコードを書くことになります
Sceneに登録するアイテムが少なければviewで操作しても良いと思うけど、多くなると管理が大変なので、アイテム自身にやらせた方が良いと思い、私はサブクラスを作って操作することにしました

細かい解説

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


svgについて

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

  • スクリーンキャプチャのプログラムを書いたときにそのまま矢印なども入れれたら良いかなと思って作った

  • パーツは画像だと劣化するのでベクトル系にしたかったが、パスは難しいので他のソフトでパーツを作ってSVGで書き出して利用することにした

  • Qt の QGraphicsView 、QGraphicsScene の使い方の勉強になった

  • svgパーツの拡大・縮小・回転はサブクラス化して自分でやらせてる
    ただしキーボード対応
    マウスでグリグリが本来だと思うが難しそうだったので┐('д')┌

  • 色んなパーツが使えるので広がりがある
    改めてQtのすごさを感じました


--> PyQt でベクターオブジェクト作成ツールを作る #