今回は、PySide を使って画像をズームイン/ズームアウトしてみます。このやり方で、画像を拡大/縮小しながら表示するエフェクトが得られます。この記事を読む前に、PySide で画像をスライドさせる | DeVlog – 銀の翼で翔べ – を読んでいただくとより理解しやすいと思います。
今回の記事も Python3.3 で検証しています。
アプリケーションの骨格を作る
まず、いつものようにアプリケーションの骨格を作っておきます。メインウインドウ(window)内に、画像(hoge.jpg)を表示するラベル(label)を表示します。setGeometry のパラメータを必要に応じて調整してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import sys from PySide import QtCore from PySide import QtGui app = QtGui.QApplication(sys.argv) window = QtGui.QMainWindow() label = QtGui.QLabel(window) pixmap = QtGui.QPixmap('hoge.jpg') label.setPixmap(pixmap) label.resize(pixmap.size()) window.setGeometry(100, 100, 900, 700) window.show() sys.exit(app.exec_()) |
どのようにズームを実現するのか
QLabel には “scaledContents” というプロパティがあります。これを True に設定しておくと、中に格納されたイメージはラベルの大きさと同じになるように拡大/縮小されて(ラベルいっぱいに)表示されます。この特性を使えば、画像そのものを拡大/縮小する操作をしなくても、ラベルの拡大/縮小だけでズームが実現できます。
以下のようにズームアニメーションを取得するメソッドを書けます。target として QLabel オブジェクトを与え、mag1, mag2 に最初と最後のズーム倍率(0 〜 1)を与えます。
1 2 3 4 5 6 7 8 9 10 11 |
def zoom(target, duration, mag1, mag2) target.setScaledContents(True) animation = QtCore.QPropertyAnimation(target, "size") animation.setDuration(duration) mapsize = target.pixmap().size() animation.setStartValue(mapsize * mag1) animation.setEndValue(mapsize * mag2) return animation |
setScaledContents(True) で “scaledContents” を True にし、QLabel の “size” プロパティをアニメーション化することでズームを実現します。QSize オブジェクト (mapsize) には ‘*’ 演算が定義(オーバーロード)されているので、倍率を掛け算するだけで新しい QSize オブジェクトが得られます。Python でも演算子のオーバーロードが使えるんですね。
アニメーションを実行するには、アニメーションオブジェクトの start メソッドを実行します。
1 2 |
an = zoom(label, 10000, 1, 2) an.start() |
画像中央を基準にズームするには
先ほどの例では、左上角が固定されてズームされています。画像中央を中心として拡大/縮小していくようなエフェクトについても考えてみます。
画像中央を中心とする場合は、画像が左上のままでははみ出てしまいますので、まず準備として、画像を中央に配置しておきます。
以下のような、親オブジェクト(parent)の中央に、幅 width, 高さ height の画像を配置する場合の “pos” (画像の左上角) を計算するメソッドを作り、
1 2 3 |
def posOnParentCenter(parent, width, height): c = parent.rect().center() return QtCore.QPoint(c.x() - width / 2, c.y() - height / 2) |
このメソッドを使って画像をウインドウ中央に配置します。
1 |
label.move(posOnParentCenter(window, label.width(), label.height())) |
それでは、中央固定のズームについて、あらためて考えてみます。左上固定の場合というのは、”pos” が固定しているという事です。”pos” はターゲットの左上角の座標ですから当然といえば当然です。逆に言うと、中央固定の場合、ズームと共に “pos” が変化するという事です。
つまり、画像のズームと同時に “pos” も変化するアニメーションを作れば良いのです。”pos” を変化させる(画像をスライドさせる)アニメーションについては、すでに PySide で画像をスライドさせる | DeVlog – 銀の翼で翔べ – で解説していますのでそちらも参照してください。
では、最初に “pos” を pos1 から pos2 に変化させるアニメーションを取得するメソッドを定義します。
1 2 3 4 5 6 7 |
def move(target, duration, pos1, pos2): animation = QtCore.QPropertyAnimation(target, "pos") animation.setDuration(duration) animation.setStartValue(pos1) animation.setEndValue(pos2) return animation |
次に、ターゲットをズームした時の “pos” の位置を計算するメソッドを作っておきます。
1 2 3 4 |
def posZoomed(target, mag): g = target.geometry() c = g.center() return QtCore.QPoint(c.x() - g.width() / 2 * mag, c.y() - g.height() / 2 * mag) |
そして、ターゲット中央を固定してズームするアニメーションを取得するメソッドを作成します。QParallelAnimationGroup を使うことで、zoom と move を同時に実行します。(QSequentialAnimationGroup を使った例として、PySide で画像をフェードイン/フェードアウト | DeVlog – 銀の翼で翔べ – も参照して下さい。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def zoom2(target, duration, mag1, mag2): target.setScaledContents(True) an_zoom = QtCore.QPropertyAnimation(target, "size") an_zoom.setDuration(duration) mapsize = target.pixmap().size() an_zoom.setStartValue(mapsize * mag1) an_zoom.setEndValue(mapsize * mag2) ang = QtCore.QParallelAnimationGroup() ang.addAnimation(an_zoom) pos1 = posZoomed(target, mag1) pos2 = posZoomed(target, mag2) an_pos = move(target, duration, pos1, pos2) ang.addAnimation(an_pos) return ang |
このメソッドで返されるアニメーションオブジェクトの start() メソッドを実行すると、このページの最初に示したようなエフェクトが得られます。
なお、完全なコードをこちらに用意しましたので、hoge.jpg を用意して実行してみてください。
[関連サイト]
junf/PySideSamples · GitHub
[関連記事]