--- Title: PyQtで画像にsvgの矢印を入れる Author: yamasyuh68 Web: https://mimemo.io/m/3A2wRoNVZvo1zM6 --- 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() self.svgItem2 = svg() self.pix = QPixmap() 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 ``` ``` - arrow_black.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 で画像にパスを加えるツールを作る #](https://mimemo.io/m/JkWVal6ZmJlBEqd)