画面キャプチャを即座にメモ化!Python × PyQt6で便利アプリ作成

プログラミング

概要

ちょっとした便利ツールのコードを紹介します。

皆さん一度はあると思うのですが
ブラウザの記事を参考にして作業したいのにブラウザの画面が隠れてしまう。
ブラウザとエディタを交互に表示して・・・

こういったときに便利に使えるツールです。

マウスで参考にしたい画面の範囲を指定して切り抜き、
常に最前面に表示してくれます。

邪魔な時は、ダブルクリックでアイコン化して画面の隅に置いておきます。

短いコードですので常駐化したり、コマンド化したりカスタマイズして使ってください。

ツールの操作方法

python memo_capture.py

で、起動します。

領域の選択

素のコードだとすぐに画面の色が変わりますのでその状態が領域を選択できる状態です。
マウスをドラッグしてメモ化したい部分を選択してください。

例)
このサイトの画面を一部選択するとコピー画面が作られます。

メモの移動

そうすると別のウィンドウにフォーカスが移動しても

先ほどコピーした画面が画面の最上面に表示されます。

この状態でドラッグするとメモ画面の位置を移動することができます。

見やすい場所に移動したら、メモを参考にしながら作業します。

アイコン化

メモは、常に最前面に表示されています。

邪魔な時は、ダブルクリックしてください。
アイコン化されます。

小さくなりますので、画面隅など邪魔にならないところへおいてください。

アイコンをダブルクリックすると元の大きさに戻ります。

例)この場合、細長い領域だったために、かなり見づらくなっていますが
青い枠に囲まれた部分がアイコンです。
元の画像の縮小画像が表示されます。

終了

メモ化された画面の上でマウスを右クリックしてください。

終了メニューが表示されますので選択するとツールを終了します。

PyQt6のインストール

pip install pyqt6

を行います。

コード

memo_capture.py

from PyQt6.QtWidgets import (
    QApplication, QWidget, QRubberBand, QMenu
)
from PyQt6.QtCore import (
    Qt, QRect, QPoint, QSize
)
from PyQt6.QtGui import (
    QScreen, QPainter, QPixmap, QPen, QColor
)


class ScreenSelection(QWidget):
    """
    画面全体を半透明で覆い、ドラッグで領域を選択して
    スクリーンキャプチャを行うクラス
    """
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Select Region")
        # フルスクリーンで表示して、画面全体を覆う
        self.showFullScreen()
        # ウィンドウの透明度を設定(0.3 = 30% 不透明)
        self.setWindowOpacity(0.3)

        # マウスドラッグ開始点を記録するための変数
        self.origin = QPoint()
        # ドラッグ中の選択範囲を表示するための枠(QRubberBand)
        # QRubberBandはマウスドラッグで矩形範囲を視覚的に示すための便利なクラス
        self.rubberBand = QRubberBand(QRubberBand.Shape.Rectangle, self)

    def mousePressEvent(self, event):
        """
        マウスの左クリックが押されたときの処理
        """
        if event.button() == Qt.MouseButton.LeftButton:
            # クリックされた位置を記録
            self.origin = event.pos()
            # 選択範囲の初期位置とサイズを設定(最初はゼロサイズ)
            self.rubberBand.setGeometry(QRect(self.origin, QSize()))
            # QRubberBandを表示
            self.rubberBand.show()

    def mouseMoveEvent(self, event):
        """
        マウスをドラッグ中の処理
        """
        if event.buttons() & Qt.MouseButton.LeftButton:
            # 現在のマウス位置をもとに選択範囲を更新
            rect = QRect(self.origin, event.pos()).normalized()
            # QRubberBandの位置とサイズを更新
            self.rubberBand.setGeometry(rect)

    def mouseReleaseEvent(self, event):
        """
        マウスの左クリックが離されたときの処理
        """
        if event.button() == Qt.MouseButton.LeftButton:
            # QRubberBandを非表示にする(選択が確定)
            self.rubberBand.hide()
            # 選択された領域の矩形情報を取得
            rect = self.rubberBand.geometry()

            # 自身のウィンドウを一時的に隠す(キャプチャ前に画面に映らないようにする)
            self.hide()

            # スクリーン全体から選択範囲をキャプチャ
            screen = QApplication.primaryScreen()
            screenshot = screen.grabWindow(
                0,
                rect.x(),
                rect.y(),
                rect.width(),
                rect.height()
            )

            # キャプチャウィンドウとアイコンウィンドウを作成
            self.capturedWindow = CapturedWindow(screenshot)
            icon_pixmap = screenshot.scaled(
                64, 64,
                Qt.AspectRatioMode.KeepAspectRatio,
                Qt.TransformationMode.SmoothTransformation
            )
            self.iconWindow = IconWindow(icon_pixmap, self.capturedWindow)

            # 初期位置を設定(選択した領域の左上に表示)
            initial_pos = rect.topLeft()
            self.capturedWindow.move(initial_pos)
            self.iconWindow.move(initial_pos)

            # キャプチャウィンドウを表示
            self.capturedWindow.show()

            # アイコンウィンドウは非表示のまま保持
            self.iconWindow.hide()

            # このウィンドウを閉じる
            self.close()


class CapturedWindow(QWidget):
    """
    キャプチャした画像をフレームレスで表示するウィンドウ。
    ダブルクリックでアイコン化、再び大きなウィンドウに戻せる。
    """
    def __init__(self, screenshot):
        super().__init__()
        self.setWindowFlags(
            Qt.WindowType.WindowStaysOnTopHint |
            Qt.WindowType.FramelessWindowHint |
            Qt.WindowType.Tool
        )
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)

        self.screenshot = screenshot
        self.resize(self.screenshot.size())

        self.dragging = False
        self.dragPos = QPoint()

        # アイコンウィンドウの参照を保持(後で再表示用)
        self.iconWindow = None

    def paintEvent(self, event):
        """
        キャプチャ画像を描画
        """
        painter = QPainter(self)
        pen = QPen(QColor("red"))
        pen.setWidth(8)
        painter.setPen(pen)

        painter.drawPixmap(0, 0, self.screenshot)
        painter.drawRect(0, 0, self.width() - 1, self.height() - 1)

    def mousePressEvent(self, event):
        """
        ウィンドウをドラッグ移動するための初期設定
        """
        if event.button() == Qt.MouseButton.LeftButton:
            self.dragPos = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
            self.dragging = True

    def mouseMoveEvent(self, event):
        """
        ドラッグ中にウィンドウを移動
        """
        if self.dragging and (event.buttons() & Qt.MouseButton.LeftButton):
            self.move(event.globalPosition().toPoint() - self.dragPos)

    def mouseReleaseEvent(self, event):
        """
        ドラッグ終了
        """
        if event.button() == Qt.MouseButton.LeftButton:
            self.dragging = False

    def mouseDoubleClickEvent(self, event):
        """
        ダブルクリックでアイコン化
        """
        if event.button() == Qt.MouseButton.LeftButton:
            # アイコンウィンドウを現在の位置に移動して表示
            self.iconWindow.move(self.pos())
            self.iconWindow.show()
            self.hide()

    def contextMenuEvent(self, event):
        """
        右クリックでアプリケーション終了メニューを表示
        """
        menu = QMenu(self)
        exit_action = menu.addAction("終了")
        action = menu.exec(event.globalPos())

        if action == exit_action:
            QApplication.quit()


class IconWindow(QWidget):
    """
    キャプチャ画像のアイコン化ウィンドウ。
    ダブルクリックで元のウィンドウを再表示。
    """
    def __init__(self, icon_pixmap, captured_window):
        super().__init__()
        self.setWindowFlags(
            Qt.WindowType.WindowStaysOnTopHint |
            Qt.WindowType.FramelessWindowHint |
            Qt.WindowType.Tool
        )
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)

        self.icon_pixmap = icon_pixmap
        self.captured_window = captured_window
        self.resize(self.icon_pixmap.size())

        # アイコンウィンドウをCapturedWindowに設定
        self.captured_window.iconWindow = self

        # ドラッグ移動用
        self.dragging = False
        self.dragPos = QPoint()

    def paintEvent(self, event):
        """
        アイコン画像を描画
        """
        painter = QPainter(self)
        pen = QPen(QColor("blue"))
        pen.setWidth(8)
        painter.setPen(pen)

        painter.drawPixmap(0, 0, self.icon_pixmap)
        painter.drawRect(0, 0, self.width() - 1, self.height() - 1)

    def mousePressEvent(self, event):
        """
        ドラッグ開始
        """
        if event.button() == Qt.MouseButton.LeftButton:
            self.dragging = True
            self.dragPos = event.globalPosition().toPoint() - self.frameGeometry().topLeft()

    def mouseMoveEvent(self, event):
        """
        ドラッグ中にウィンドウを移動
        """
        if self.dragging and (event.buttons() & Qt.MouseButton.LeftButton):
            self.move(event.globalPosition().toPoint() - self.dragPos)

    def mouseReleaseEvent(self, event):
        """
        ドラッグ終了
        """
        if event.button() == Qt.MouseButton.LeftButton:
            self.dragging = False

    def mouseDoubleClickEvent(self, event):
        """
        ダブルクリックでキャプチャウィンドウを再表示
        """
        if event.button() == Qt.MouseButton.LeftButton:
            self.captured_window.move(self.pos())
            self.captured_window.show()
            self.hide()

    def contextMenuEvent(self, event):
        """
        右クリックでアプリケーション終了メニューを表示
        """
        menu = QMenu(self)
        exit_action = menu.addAction("終了")
        action = menu.exec(event.globalPos())

        if action == exit_action:
            QApplication.quit()


def main():
    app = QApplication([])
    selector = ScreenSelection()
    selector.show()
    app.exec()


if __name__ == "__main__":
    main()

 

コード解説

詳しくは、コードのコメントをご覧ください。

ScreenSelectionCapturedWindwIconWindow の3つのクラスを使っています。

ScreenSelectionで画像を取得し、そのあとは CapturedWindow と IconWindow がそれぞれ入れ替わりながら使われます。

ScreenSelection

最初に、ScreenSelectionクラスのインスタンスを作っています。

選択領域の画像を取得するためのクラスです。
ここで QRubberBand という選択領域専用のクラスを使い画像を取得します。

QRubberBandは、ユーザーがドラッグ操作を行った際に、その範囲を矩形や線で視覚的に示すためのクラスです。
このツールでは、矩形選択モードを使っています。

取得した画像を使って、CapturedWindow と IconWindow をそれぞれ作ります。
CaputuredWindow つまり取得した画面は、表示して IconWindow のほうは隠しておきます。

CapturedWindow

CaputuredWindow  は、メモ化された画像を常に画面最前面に表示します。

ドラッグして位置を変更することができます。
ダブルクリックで、IconWindow を表示して自身は非表示となります。
右クリックでコンテキストメニューを表示してツールを終了することができます。

IconWindow

IconWindow  は、アイコン化して小さな画面を常に画面最前面に表示します。
これは、CapturedWindowが邪魔になる場合の対応です。

ドラッグして位置を変更することができます。
ダブルクリックで、CapturedWindow を表示して自身は非表示となります。
右クリックでコンテキストメニューを表示してツールを終了することができます。

改善点

  • 細長い画像が見えづらい場合は、アイコン化時にリサイズの方法を調整することで、もっと見やすくできます。例えば、短い辺に合わせてリサイズするか、一定の最大サイズを設定すると良いでしょう。
    IconWindowでは、QPixmap.scaledメソッドを使って画像を縮小表示しています。例えば、短い辺に合わせてリサイズするには以下のようにコードを変更できます。

    icon_pixmap = screenshot.scaled(
        64, 64,
        Qt.AspectRatioMode.KeepAspectRatioByExpanding,
        Qt.TransformationMode.SmoothTransformation 
    )
  • 常駐化する。常駐化することにより必要な時に素早く起動することが出来る。
  • 文字列コピーをする。実現するのは、難しそうですが画像取得時にまとめてテキストをとるとか取得した画像からOCRで文字列を取得するとか。
  • スレッドを使って複数の画像を扱えるようにする。
  • アイコン化しないで最小化(タスクバーにアイコン表示)するようにする。

などなど

まとめ

ちょっとしたメモを見ながら作業をしたいときに使えるツールをご紹介しました。
なお、このコードを複数起動することで複数のメモを取得することができます。

RubberBand バンドの使い方を解説しました。

コードの内容は、すぐ理解できると思います。
自分好みにカスタマイズして便利に使ってください。

Commnts

タイトルとURLをコピーしました