Skip to content

Instantly share code, notes, and snippets.

@ce-dric
Forked from acbetter/QImageViewSync.py
Created October 7, 2020 03:37
Show Gist options
  • Save ce-dric/eceeae04be1fdf8e9e8f054ad8d3f1bc to your computer and use it in GitHub Desktop.
Save ce-dric/eceeae04be1fdf8e9e8f054ad8d3f1bc to your computer and use it in GitHub Desktop.
Two Image Synchronous Scrolling in the window by PyQt5 and Python 3
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QImage, QPixmap, QPalette, QPainter
from PyQt5.QtPrintSupport import QPrintDialog, QPrinter
from PyQt5.QtWidgets import QLabel, QSizePolicy, QScrollArea, QMessageBox, QMainWindow, QMenu, QAction, \
qApp, QFileDialog, QWidget, QHBoxLayout
class QImageViewSync(QWidget):
def __init__(self, window=None):
super().__init__()
self.window = window
self.printer = QPrinter()
self.scaleFactor = 0.0
self.imageLabelLeft = QLabel()
self.imageLabelLeft.setBackgroundRole(QPalette.Base)
self.imageLabelLeft.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
self.imageLabelLeft.setScaledContents(True)
self.scrollAreaLeft = QScrollArea()
self.scrollAreaLeft.setBackgroundRole(QPalette.Dark)
self.scrollAreaLeft.setWidget(self.imageLabelLeft)
self.scrollAreaLeft.setVisible(False)
self.imageLabelRight = QLabel()
self.imageLabelRight.setBackgroundRole(QPalette.Base)
self.imageLabelRight.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
self.imageLabelRight.setScaledContents(True)
self.scrollAreaRight = QScrollArea()
self.scrollAreaRight.setBackgroundRole(QPalette.Dark)
self.scrollAreaRight.setWidget(self.imageLabelRight)
self.scrollAreaRight.setVisible(False)
self.centralWidget = QWidget()
self.layout = QHBoxLayout(self.centralWidget)
self.layout.addWidget(self.scrollAreaLeft)
self.layout.addWidget(self.scrollAreaRight)
self.scrollAreaLeft.verticalScrollBar().valueChanged.connect(
self.scrollAreaRight.verticalScrollBar().setValue)
self.scrollAreaLeft.horizontalScrollBar().valueChanged.connect(
self.scrollAreaRight.horizontalScrollBar().setValue)
self.scrollAreaRight.verticalScrollBar().valueChanged.connect(
self.scrollAreaLeft.verticalScrollBar().setValue)
self.scrollAreaRight.horizontalScrollBar().valueChanged.connect(
self.scrollAreaLeft.horizontalScrollBar().setValue)
self.scrollAreaLeft.mouseMoveEvent = self.mouseMoveEventLeft
self.scrollAreaLeft.mousePressEvent = self.mousePressEventLeft
self.scrollAreaLeft.mouseReleaseEvent = self.mouseReleaseEventLeft
self.scrollAreaRight.mouseMoveEvent = self.mouseMoveEventRight
self.scrollAreaRight.mousePressEvent = self.mousePressEventRight
self.scrollAreaRight.mouseReleaseEvent = self.mouseReleaseEventRight
self.imageLabelLeft.setCursor(Qt.OpenHandCursor)
self.imageLabelRight.setCursor(Qt.OpenHandCursor)
def mousePressEventLeft(self, event):
self.pressed = True
self.imageLabelLeft.setCursor(Qt.ClosedHandCursor)
self.initialPosX = self.scrollAreaLeft.horizontalScrollBar().value() + event.pos().x()
self.initialPosY = self.scrollAreaLeft.verticalScrollBar().value() + event.pos().y()
def mouseReleaseEventLeft(self, event):
self.pressed = False
self.imageLabelLeft.setCursor(Qt.OpenHandCursor)
self.initialPosX = self.scrollAreaLeft.horizontalScrollBar().value()
self.initialPosY = self.scrollAreaLeft.verticalScrollBar().value()
def mouseMoveEventLeft(self, event):
if self.pressed:
self.scrollAreaLeft.horizontalScrollBar().setValue(self.initialPosX - event.pos().x())
self.scrollAreaLeft.verticalScrollBar().setValue(self.initialPosY - event.pos().y())
def mousePressEventRight(self, event):
self.pressed = True
self.imageLabelRight.setCursor(Qt.ClosedHandCursor)
self.initialPosX = self.scrollAreaRight.horizontalScrollBar().value() + event.pos().x()
self.initialPosY = self.scrollAreaRight.verticalScrollBar().value() + event.pos().y()
def mouseReleaseEventRight(self, event):
self.pressed = False
self.imageLabelRight.setCursor(Qt.OpenHandCursor)
self.initialPosX = self.scrollAreaRight.horizontalScrollBar().value()
self.initialPosY = self.scrollAreaRight.verticalScrollBar().value()
def mouseMoveEventRight(self, event):
if self.pressed:
self.scrollAreaRight.horizontalScrollBar().setValue(self.initialPosX - event.pos().x())
self.scrollAreaRight.verticalScrollBar().setValue(self.initialPosY - event.pos().y())
def open(self):
options = QFileDialog.Options()
# fileName = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath())
fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '',
'Images (*.png *.jpeg *.jpg *.bmp *.gif)', options=options)
if fileName:
print(fileName)
image = QImage(fileName)
if image.isNull():
QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName)
return
self.imageLabelLeft.setPixmap(QPixmap.fromImage(image))
self.imageLabelRight.setPixmap(QPixmap.fromImage(image))
self.scaleFactor = 1.0
self.scrollAreaLeft.setVisible(True)
self.scrollAreaRight.setVisible(True)
self.window.printLeftAct.setEnabled(True)
self.window.printRightAct.setEnabled(True)
self.window.fitToWindowAct.setEnabled(True)
self.updateActions()
if not self.window.fitToWindowAct.isChecked():
self.imageLabelLeft.adjustSize()
self.imageLabelRight.adjustSize()
def openLeft(self):
options = QFileDialog.Options()
# fileName = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath())
fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '',
'Images (*.png *.jpeg *.jpg *.bmp *.gif)', options=options)
if fileName:
print(fileName)
image = QImage(fileName)
if image.isNull():
QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName)
return
self.imageLabelLeft.setPixmap(QPixmap.fromImage(image))
self.scaleFactor = 1.0
self.scrollAreaLeft.setVisible(True)
self.window.printLeftAct.setEnabled(True)
self.window.fitToWindowAct.setEnabled(True)
self.updateActions()
if not self.window.fitToWindowAct.isChecked():
self.imageLabelLeft.adjustSize()
def openRight(self):
options = QFileDialog.Options()
# fileName = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath())
fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '',
'Images (*.png *.jpeg *.jpg *.bmp *.gif)', options=options)
if fileName:
print(fileName)
image = QImage(fileName)
if image.isNull():
QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName)
return
self.imageLabelRight.setPixmap(QPixmap.fromImage(image))
self.scaleFactor = 1.0
self.scrollAreaRight.setVisible(True)
self.window.printRightAct.setEnabled(True)
self.window.fitToWindowAct.setEnabled(True)
self.updateActions()
if not self.window.fitToWindowAct.isChecked():
self.imageLabelRight.adjustSize()
def printLeft(self):
dialog = QPrintDialog(self.printer, self)
if dialog.exec_():
painter = QPainter(self.printer)
rect = painter.viewport()
size = self.imageLabelLeft.pixmap().size()
size.scale(rect.size(), Qt.KeepAspectRatio)
painter.setViewport(rect.x(), rect.y(), size.width(), size.height())
painter.setWindow(self.imageLabelLeft.pixmap().rect())
painter.drawPixmap(0, 0, self.imageLabelLeft.pixmap())
def printRight(self):
dialog = QPrintDialog(self.printer, self)
if dialog.exec_():
painter = QPainter(self.printer)
rect = painter.viewport()
size = self.imageLabelRight.pixmap().size()
size.scale(rect.size(), Qt.KeepAspectRatio)
painter.setViewport(rect.x(), rect.y(), size.width(), size.height())
painter.setWindow(self.imageLabelRight.pixmap().rect())
painter.drawPixmap(0, 0, self.imageLabelRight.pixmap())
def zoomIn(self):
self.scaleImage(1.25)
def zoomOut(self):
self.scaleImage(0.8)
def normalSize(self):
self.imageLabelLeft.adjustSize()
self.imageLabelRight.adjustSize()
self.scaleFactor = 1.0
def about(self):
QMessageBox.about(self, "Image View in the Main Window",
"<p>The <b>Image Viewer</b> example shows how to combine "
"QLabel and QScrollArea to display an image. QLabel is "
"typically used for displaying text, but it can also display "
"an image. QScrollArea provides a scrolling view around "
"another widget. If the child widget exceeds the size of the "
"frame, QScrollArea automatically provides scroll bars.</p>"
"<p>The example demonstrates how QLabel's ability to scale "
"its contents (QLabel.scaledContents), and QScrollArea's "
"ability to automatically resize its contents "
"(QScrollArea.widgetResizable), can be used to implement "
"zooming and scaling features.</p>"
"<p>In addition the example shows how to use QPainter to "
"print an image.</p>")
def updateActions(self):
self.window.zoomInAct.setEnabled(not self.window.fitToWindowAct.isChecked())
self.window.zoomOutAct.setEnabled(not self.window.fitToWindowAct.isChecked())
self.window.normalSizeAct.setEnabled(not self.window.fitToWindowAct.isChecked())
def scaleImage(self, factor):
self.scaleFactor *= factor
self.imageLabelLeft.resize(self.scaleFactor * self.imageLabelLeft.pixmap().size())
self.imageLabelRight.resize(self.scaleFactor * self.imageLabelRight.pixmap().size())
self.adjustScrollBar(self.scrollAreaLeft.horizontalScrollBar(), factor)
self.adjustScrollBar(self.scrollAreaLeft.verticalScrollBar(), factor)
self.adjustScrollBar(self.scrollAreaRight.horizontalScrollBar(), factor)
self.adjustScrollBar(self.scrollAreaRight.verticalScrollBar(), factor)
self.window.zoomInAct.setEnabled(self.scaleFactor < 3.0)
self.window.zoomOutAct.setEnabled(self.scaleFactor > 0.333)
def adjustScrollBar(self, scrollBar, factor):
scrollBar.setValue(int(factor * scrollBar.value()
+ ((factor - 1) * scrollBar.pageStep() / 2)))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.imageViewSync = QImageViewSync(window=self)
self.setCentralWidget(self.imageViewSync.centralWidget)
self.createActions(self.imageViewSync)
self.createMenus()
self.setWindowTitle("Image View Sync in the Main Window")
self.resize(1200, 600)
def fitToWindow(self):
fitToWindow = self.fitToWindowAct.isChecked()
self.imageViewSync.scrollAreaLeft.setWidgetResizable(fitToWindow)
self.imageViewSync.scrollAreaRight.setWidgetResizable(fitToWindow)
if not fitToWindow:
self.imageViewSync.normalSize()
self.imageViewSync.updateActions()
def createActions(self, view):
self.openLeftAct = QAction("&Open Left...", self, shortcut="Ctrl+O", triggered=view.openLeft)
self.openRightAct = QAction("&Open Right...", self, shortcut="Shift+Ctrl+O", triggered=view.openRight)
self.printLeftAct = QAction("&Print Left...", self, shortcut="Ctrl+P", enabled=False, triggered=view.printLeft)
self.printRightAct = QAction("&Print Right...", self,
shortcut="Shift+Ctrl+P", enabled=False, triggered=view.printRight)
# self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=image.close)
self.zoomInAct = QAction("Zoom &In (25%)", self, shortcut="Ctrl++", enabled=False, triggered=view.zoomIn)
self.zoomOutAct = QAction("Zoom &Out (25%)", self, shortcut="Ctrl+-", enabled=False, triggered=view.zoomOut)
self.normalSizeAct = QAction("&Normal Size", self, shortcut="Ctrl+S", enabled=False, triggered=view.normalSize)
self.fitToWindowAct = QAction("&Fit to Window", self,
enabled=False, checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow)
self.aboutAct = QAction("&About", self, triggered=view.about)
self.aboutQtAct = QAction("About &Qt", self, triggered=qApp.aboutQt)
def createMenus(self):
self.fileMenu = QMenu("&File", self)
self.fileMenu.addAction(self.openLeftAct)
self.fileMenu.addAction(self.openRightAct)
self.fileMenu.addSeparator()
self.fileMenu.addAction(self.printLeftAct)
self.fileMenu.addAction(self.printRightAct)
self.fileMenu.addSeparator()
# self.fileMenu.addAction(self.exitAct)
self.viewMenu = QMenu("&View", self)
self.viewMenu.addAction(self.zoomInAct)
self.viewMenu.addAction(self.zoomOutAct)
self.viewMenu.addAction(self.normalSizeAct)
self.viewMenu.addSeparator()
self.viewMenu.addAction(self.fitToWindowAct)
self.helpMenu = QMenu("&Help", self)
self.helpMenu.addAction(self.aboutAct)
self.helpMenu.addAction(self.aboutQtAct)
self.menuBar().addMenu(self.fileMenu)
self.menuBar().addMenu(self.viewMenu)
self.menuBar().addMenu(self.helpMenu)
if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
@ce-dric
Copy link
Author

ce-dric commented Oct 7, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment