PyQt5でコミックビューワーっぽいアプリを作ってみる
2022/02/11 categories:Python| tags:Python|PyMuPDF|PyQt5|PDF|
Windowsアプリでいい感じのコミックビューワーが見つからなかったので、PyQtで作れないか試してみました。
処理内容
以下のような処理を実装してみました。
- フォルダ内にあるフォルダをブックとして取得
- ブックの1枚目の画像をサムネイルとして一覧表示
- ブックをクリックすると1ページ目から表示
- ページを表示中にビューの右側をクリックすると次のページ、逆は前のページを表示
実行結果
簡単な画像表示の切り替えだけですが、十分使用に耐えうる処理かなと思いました。もう少し機能を追加して、C++で書いてバイナリにすれば普通に使えるアプリになるかなと思いました。
Pythonコード
# -*- coding: utf-8 -*-
import re
import sys
from pathlib import Path
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.books = []
self.book_number = -1
self.page_number = -1
self.books_widget = BooksWidget()
self.image_view = ImageView()
self.resize(1200, 800)
self.setCentralWidget( QtWidgets.QStackedWidget() )
self.centralWidget().layout().addWidget(self.books_widget)
self.centralWidget().layout().addWidget(self.image_view)
self.books_widget.clicked.connect(self.set_image)
self.books_widget.add_button.clicked.connect(self.folder_open)
self.image_view.back.connect( lambda : self.centralWidget().setCurrentIndex(0) )
self.image_view.page_change_signal.connect(self.page_changed)
def folder_open(self):
for path in Path('comic').iterdir():
if path.is_file():
continue
images = sorted([p for p in path.glob('*') if re.search('/*\.(jpg|jpeg|png)', str(p))])
pixmap = QtGui.QPixmap( str(images[0]) ).scaledToHeight(300)
thumbnail = Thumbnail(pixmap, path.name)
self.books_widget.add_thumbnail(thumbnail)
self.books.append(images)
def set_image(self, index:int):
self.book_number = index
self.page_number = 0
pixmap = QtGui.QPixmap( str(self.books[index][self.page_number]) )
self.image_view.set_pixmap(pixmap)
self.centralWidget().setCurrentIndex(1)
def page_changed(self, value):
self.page_number += value
book = self.books[self.book_number]
if self.page_number < 0 or len(book) < self.page_number + 1:
self.page_number -= value
return
pixmap = QtGui.QPixmap( str(book[self.page_number]) )
self.image_view.set_pixmap(pixmap)
class BooksWidget(QtWidgets.QWidget):
clicked = QtCore.pyqtSignal(int)
def __init__(self):
super(BooksWidget, self).__init__()
self.book_count = 0
self.add_button = QtWidgets.QToolButton()
self.add_button.setText('Add folder')
self.add_button.setMaximumSize(100, 40), self.add_button.setMinimumSize(100, 40)
menu = QtWidgets.QWidget()
menu.setLayout( QtWidgets.QVBoxLayout() )
menu.layout().addWidget(self.add_button)
self.grid = QtWidgets.QGridLayout()
scroll_area = QtWidgets.QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setWidget( QtWidgets.QWidget() )
scroll_area.widget().setLayout(self.grid)
self.setLayout( QtWidgets.QHBoxLayout() )
self.layout().addWidget(menu)
self.layout().addWidget(scroll_area)
def add_thumbnail(self, thumbnail):
thumbnail.clicked.connect(self.image_clicked)
r = int( self.book_count / 3 )
c = int( self.book_count % 3 )
self.grid.addWidget( thumbnail, r, c, 1, 1 )
self.book_count += 1
def image_clicked(self, widget:QtWidgets.QWidget):
self.clicked.emit( self.grid.indexOf(widget) )
class Thumbnail(QtWidgets.QGraphicsView):
class View(QtWidgets.QGraphicsView):
clicked = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
self.setScene( QtWidgets.QGraphicsScene() )
def mousePressEvent(self, event: QtGui.QMouseEvent):
self.clicked.emit()
def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
self.fitInView(self.scene().itemsBoundingRect(), QtCore.Qt.KeepAspectRatio)
return super().resizeEvent(event)
clicked = QtCore.pyqtSignal(QtWidgets.QGraphicsView)
def __init__(self, pixmap, title):
super(Thumbnail, self).__init__()
self.view = self.View()
self.view.scene().addPixmap(pixmap)
self.title = QtWidgets.QLabel(title)
self.setMinimumSize(300, 300)
self.setMaximumSize(300, 300)
self.setLayout( QtWidgets.QVBoxLayout() )
self.layout().addWidget(self.view)
self.layout().addWidget(self.title)
self.view.clicked.connect( lambda : self.clicked.emit(self) )
class ImageView(QtWidgets.QWidget):
class GraphicsView(QtWidgets.QGraphicsView):
page_change_signal = QtCore.pyqtSignal(int)
def __init__(self):
super().__init__()
self.setScene( QtWidgets.QGraphicsScene() )
def mouseReleaseEvent(self, event: QtGui.QMouseEvent):
w = self.rect().width() * 0.2
if event.pos().x() < w:
self.page_change_signal.emit(-1)
elif self.rect().width() - w < event.pos().x():
self.page_change_signal.emit(1)
def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
self.fitInView(self.scene().itemsBoundingRect(), QtCore.Qt.KeepAspectRatio)
return super().resizeEvent(event)
back = QtCore.pyqtSignal()
page_change_signal = QtCore.pyqtSignal(int)
def __init__(self):
super(ImageView, self).__init__()
self.back_button = QtWidgets.QToolButton()
self.back_button.setText('Back')
self.back_button.clicked.connect(self.back)
self.back_button.setMaximumSize(100, 40), self.back_button.setMinimumSize(100, 40)
self.menu = QtWidgets.QWidget()
self.menu.setLayout( QtWidgets.QVBoxLayout() )
self.menu.layout().addWidget(self.back_button)
self.view = self.GraphicsView()
self.view.page_change_signal.connect( lambda x : self.page_change_signal.emit(x) )
self.setLayout( QtWidgets.QHBoxLayout() )
self.layout().addWidget(self.menu)
self.layout().addWidget(self.view)
def set_pixmap(self, pixmap):
self.view.scene().clear()
self.view.scene().addPixmap(pixmap)
self.view.scene().setSceneRect( QtCore.QRectF(pixmap.rect()) )
self.view.fitInView(self.view.scene().itemsBoundingRect(), QtCore.Qt.KeepAspectRatio)
self.view.setRenderHints(
QtGui.QPainter.Antialiasing |
QtGui.QPainter.SmoothPixmapTransform |
QtGui.QPainter.HighQualityAntialiasing
)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
widget = MainWindow()
widget.show()
app.exec()