【没钱买ps,PyQt自己写】Day 13 - 使用 QVBoxLayout, QscrollArea 制作出卷轴,以高解析度检视图片 (基於 QImage 使用 OpenCV)

看完这篇文章你会得到的成果图

前言

我们接下来的讨论,会基於读者已经先读过我 day5 文章 的架构下去进行程序设计
如果还不清楚我程序设计的逻辑 (UI.py、controller.py、start.py 分别在干麻)
建议先阅读 day5 文章後再来阅读此文。

https://www.wongwonggoods.com/python/pyqt5-5/

此篇文章的范例程序码 github

https://github.com/howarder3/ironman2021_PyQt5_photoshop/tree/main/day13_scroll_area

以 Qlabel 在 PyQt 中显示图片

这篇是延续 Day 12 显示图片 zoom in, zoom out 功能的後续开发,
只有 zoom in, zoom out 有时还不足以应付我们处理细节,
因此我们需要一个卷轴,帮助我们能更自由的移动图片。

UI 设计部份 (UI.py)

新增卷轴栏位

  1. 我们先新增一个 Vertical Layout (QVBoxLayout) 位於 Layout 当中,决定好图片可显示的范围。
  2. 然後在此 Vertical Layout 里面再新增一个 Scroll Area (QscrollArea) 位於 container 当中,作为可以移动的卷轴范围。
  3. 在此 Scroll Area (QscrollArea) 当中,再新增一个 Qlabel。作为图片显示使用。

  • 注意顺序,先新增 Vertical Layout,叠加上 Scroll Area,再叠加上 Qlabel

  • 注意这些物件彼此之间的阶层关系,一样我们可以先修改一些物件名称,方便我们等等使用

UI 优化:显示目前图片的解析度

我们在介面的右下角新增能够显示目前图片的解析度的 Qlabel,
新增这个功能主要是能方便我们能够确定现在图片已经被我们缩放到什麽程度了。

读者们可以开始自行设计自己的介面罗,以上为我的示范。

转换成 UI.py

一样的编译指令,我们加上 -x (也可不加),
我们就可以先检视看看转换後的视窗是不是跟我们想像的一样。

转换 day13.ui -> UI.py

pyuic5 -x day13.ui -o UI.py

执行看看 UI.py 画面是否如同我们想像

一样,这程序只有介面 (视觉上的呈现),没有任何互动功能

  • 看看我们制作出来的介面
python UI.py

这样我们的介面就大致出来罗!

controller 设计部份 (controller.py)

修改 UI.py 的一些程序码,达成在 QtDesigner 中做不到的事情

我们先观察一下刚刚在 QtDesigner 中的物件阶层关系,

其中红色框框的地方有多出一个我们不要的东西,scrollAreaWidgetContents,
这个东西在 QtDesigner 中预设是会与 QscrollArea 一起被建立,
但实际上因为我们已经很清楚我们需要的是 Qlabel 显示的图片,
因此我们直接去改 UI.py 里面的一些内容。

程序码中修改与 scrollAreaWidgetContents 相关的内容

我们可以透过搜寻功能帮助我们快速找到相关的段落,这些都是要删掉的

self.scrollAreaWidgetContents = QtWidgets.QWidget()
self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 667, 427))
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.label = QtWidgets.QLabel(self.scrollAreaWidgetContents)
self.label.setGeometry(QtCore.QRect(0, 0, 1920, 1080))
self.label.setObjectName("label")
self.scrollArea.setWidget(self.scrollAreaWidgetContents)

我们观察一下,

  • 基本上前三行都是 scrollAreaWidgetContents 的定义,我们都用不到,直接删。
  • self.label = QtWidgets.QLabel(self.scrollAreaWidgetContents),
    是藉由 self.scrollAreaWidgetContents 定义出 self.label 的属性,
    我们不想要这个属性,但 self.label 是 QLabel 的属性仍需要被宣告,
    因此我们将他改为 self.label = QtWidgets.QLabel(),单纯只宣告他是 QLabel()
  • 後两行关於 self.label 的定义不需要修改,符合原先的定义即可
  • 最後一行的 self.scrollArea.setWidget(self.scrollAreaWidgetContents),因为我们已经去除了 self.scrollAreaWidgetContents 这个元素,改以 Qlabel 显示的图片直接置入 self.scrollArea 当中,因此我们修改成 self.scrollArea.setWidget(self.label)。

修改结果

  • 上面的部份修改完後,结果如下:
# self.scrollAreaWidgetContents = QtWidgets.QWidget()
# self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 800, 400))
# self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.label = QtWidgets.QLabel()
self.label.setGeometry(QtCore.QRect(0, 0, 0, 0))
self.label.setObjectName("label")
self.scrollArea.setWidget(self.label)

为何不使用 self.scrollAreaWidgetContents?
目前测试的结果是不会成功的显示出卷轴,可能的原因是因为 Qlabel 才有存在超过视窗范围的大小,而 self.scrollAreaWidgetContents 作为容器,并没有办法以超过的大小触发 self.scrollArea 的卷轴事件,因此功能失效。
不过这部份原因目前只是我的猜测,总之卷轴的功能是无法正常运行的。

从 UI.py 中找出物件名称

这次除了 day12 既有的功能之外,我们新增了一些物件,

  • self.btn_zoom_in、self.btn_zoom_out:同 day12 的 zoom in, zoom out 的按钮
  • self.label:显示图片的 Qlabe
  • self.scrollArea:图片缩放的范围
  • self.img_shape:作为 UI 优化新增的 label,我们可以从这里观察目前图片的解析度。

取得名称後,去修改 controller.py

我们继续修改我们 day12 的程序码

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QFileDialog
import cv2

from UI import Ui_MainWindow

class MainWindow_controller(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__() # in python3, super(Class, self).xxx = super().xxx
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.setup_control()


    def setup_control(self):
        # TODO        
        self.img_path = 'cat.jpg'
        self.ui.btn_zoom_in.clicked.connect(self.func_zoom_in) 
        self.ui.btn_zoom_out.clicked.connect(self.func_zoom_out)
        self.ui.scrollArea.setWidgetResizable(True)
        self.ui.label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
        # self.ui.label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) # 将图片置中
        self.display_img()

    def display_img(self):
        self.img = cv2.imread(self.img_path)
        height, width, channel = self.img.shape
        bytesPerline = 3 * width
        self.qimg = QImage(self.img, width, height, bytesPerline, QImage.Format_RGB888).rgbSwapped()
        self.qpixmap = QPixmap.fromImage(self.qimg)
        self.qpixmap_height = self.qpixmap.height()
        self.ui.label.setPixmap(QPixmap.fromImage(self.qimg))
        
    def func_zoom_in(self):
        self.qpixmap_height -= 100
        self.img_resize()

    def func_zoom_out(self):
        self.qpixmap_height += 100
        self.img_resize()

    def img_resize(self):        
        scaled_pixmap = self.qpixmap.scaledToHeight(self.qpixmap_height)
        print(f"current img shape = ({scaled_pixmap.width()}, {scaled_pixmap.height()})")
        self.ui.img_shape.setText(f"current img shape = ({scaled_pixmap.width()}, {scaled_pixmap.height()})")
        self.ui.label.setPixmap(scaled_pixmap)

setup_control() 修改的部份

与 day12 的不同是,我们主要新增了这两行

self.ui.scrollArea.setWidgetResizable(True)
self.ui.label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
# self.ui.label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) # 将图片置中
  • self.ui.scrollArea.setWidgetResizable(True):这行在 Qtdesigner 中也可以设定,预设是 False,我们将他改为 True,让我们的 scrollArea 可以被卷动
  • self.ui.label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop):将我们的图片往左上角对齐,往左上角对齐有两个好处,一个是我们之後如果要进行图像处理,这样算座标会非常方便。

但是如果为了好看,想让图片置中,可以改为以下叙述:

self.ui.label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)

img_resize() 的部份 (原 day12 resize_image())

因为我们新增了 UI 优化的功能,稍微想一下就可以知道,
这段程序码基本上会跟着我们图片变化一起改变,
因此我们把「显示图片现在解析度」的功能新增在此处。

  • print(f"current img shape = ({scaled_pixmap.width()}, {scaled_pixmap.height()})"):取得现在图片高度、宽度并显示在 terminal 当中
  • self.ui.img_shape.setText(f"current img shape = ({scaled_pixmap.width()}, {scaled_pixmap.height()})"):取得现在图片高度、宽度并显示在 Qlabel 当中

执行结果

照我们 day5 的程序架构,我们执行

python start.py

Reference


★ 本文也同步发於我的个人网站(会有内容目录与显示各个小节,阅读起来更流畅):【PyQt5】Day 13 - 使用 QVBoxLayout, QscrollArea 制作出卷轴,以高解析度检视图片 (基於 QImage 使用 OpenCV) PyQt5 scrollable image


<<:  D27: 工程师太师了: 第14话

>>:  Day 13 漏洞分析 - Vulnerability Analysis (unix-privesc-check)

Day 26 | 共享 MobX store with get_it

还记得我们很早之前说过Flutter有一个问题就是嵌套太多层时我们要从下层拿到上层的东西时会变得十分...

使用Google api查询书本资讯 Day 17

这次实作的功能是使用Google Book api 使用textfield输入ISBN码按下Butt...

JS读书笔记30天 - Day28 MVVM概念

MVC与MVVM MVC MVC是一个前後端架构,分为三个部分: 视图(View),为画面显示的地方...

[Day25] SLI , SLO , SLA

今天来介绍云端的管理,常常出现的三个名词,在先前的文章中,我应该也有使用过了一部分。这三个名词长的很...