范例 - 打开图片转档储存
这篇教学会将 PyQt6 结合 Pillow 第三方函数库,实作一个可以打开并预览图片,并调整图片尺寸,转存为其他文件格式的功能。
快速导览:
因为 Google Colab 不支援 PyQt6,所以请使用本机环境 ( 参考:使用 Python 虚拟环境 ) 或使用 Anaconda Jupyter 进行实作 ( 参考:使用 Anaconda )。
安装 Pillow
输入下列指令安装 Pillow,根据个人环境使用 pip 或 pip3,如果使用 Anaconda Jupyter,已经内建 Pillow 函数库。
pip install Pillow
预计划面功能与效果
要实作一个“打开图片转档储存”的功能,大概需要有下列几种要素:
- 开新文件、另存文件和关闭的按钮。
- 预览图片的空白画布。
- 调整图片尺寸的调整元件。
- 选择储存格式为 JPG 时的压缩比调整滑杆。
透过这几种要素的组合,预期可以得到下图一样的效果,接下来就准备按照下图进行实作:
打开并显示图片
参考“QFileDialog 选择文件对话视窗”和“QLabel 标签”教学范例,在画面中放入显示图片的画布 ( QPixmap 元件 ),以及三颗各具功能 ( 打开图片、另存图片、关闭 )的按钮,点击打开图片的按钮之后,会先弹出文件选取视窗,选择文件后会出现对话视窗进行再次询问,确认后就会将图片打开在 QPixmap 的画布里,同时额外将图片储存在看不见的 Pillow 图片元件中。
因为打开在 QPixmap 里的图片长宽已经发生变化,加上最后需要使用 Pillow,所以额外产生一个 Pillow 图片元件放置原始图片信息。
from PyQt6 import QtWidgets
from PyQt6.QtGui import *
from PyQt6.QtCore import *
import sys
from PIL import Image
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
MainWindow.setObjectName("MainWindow")
MainWindow.setWindowTitle("oxxo.studio")
MainWindow.resize(480, 360)
canvas = QPixmap(360,360) # 建立 QPixmap 畫布元件
canvas.fill(QColor('#ffffff')) # 預設填滿白色
label = QtWidgets.QLabel(MainWindow) # 建立 QLabel
label.setGeometry(0, 0, 360, 360) # 設定大小和位置
label.setPixmap(canvas) # 放入 QPixmap
mbox = QtWidgets.QMessageBox(MainWindow) # 建立對話視窗元件
# 開新檔案的函式
def newFile():
global img # 新增全域變數 img
# 開啟檔案時限制為 jpg、png 和 gif 的圖片格式
filePath , filetype = QtWidgets.QFileDialog.getOpenFileName(filter='IMAGE(*.jpg *.png *.gif)')
# 如果開啟檔案
if filePath:
ret = mbox.question(MainWindow, 'question', '確定開新檔案?') # 彈出對話視窗詢問是否開啟
# 如果點選 yes
if ret == mbox.StandardButton.Yes:
img = Image.open(filePath) # 建立 Pillow 圖片元件紀錄圖片資訊
canvas.load(filePath) # 將圖片開啟到 QPixmap 畫布裡
label.setPixmap(canvas)
MainWindow.update() # 更新視窗內容
else:
return
# 關閉視窗的函式
def closeFile():
app.quit()
btn_open = QtWidgets.QPushButton(MainWindow) # 開啟圖片按鈕
btn_open.setText('開啟圖片')
btn_open.setGeometry(370, 10, 100, 30)
btn_open.clicked.connect(newFile) # 點擊按鈕的動作
btn_save = QtWidgets.QPushButton(MainWindow) # 另存圖片按鈕
btn_save.setText('另儲圖片')
btn_save.setGeometry(370, 40, 100, 30)
btn_close = QtWidgets.QPushButton(MainWindow) # 關閉視窗的按鈕
btn_close.setText('關閉')
btn_close.setGeometry(370, 320, 100, 30)
btn_close.clicked.connect(closeFile) # 點擊按鈕的動作
MainWindow.show()
sys.exit(app.exec())
加入尺寸调整功能
延续上述的程序,在主程序里加入下面的程序码,执行后就会出现可以调整尺寸数值的元件。
label_size = QtWidgets.QLabel(MainWindow) # 加入說明文字 QLabel
label_size.setGeometry(370, 70, 100, 30)
label_size.setText('尺寸改變')
label_size.setAlignment(Qt.AlignmentFlag.AlignCenter)
size =100 # 預設尺寸 100%
# 改變尺寸的函式
def changeSize():
global size
size = box_size.value() # 取得數值調整元件的數值
box_size = QtWidgets.QSpinBox(MainWindow) # 建立數值調整元件
box_size.setGeometry(390, 100, 60, 30) # 設定位置
box_size.setRange(0,200) # 設定調整範圍
box_size.setValue(size) # 設定預設值
box_size.valueChanged.connect(changeSize) # 數值改變時連動的函式
加入 JPG 压缩质量滑杆
延续上述的程序,在主程序里加入下面的程序码,执行后就会出现存档格式的选项,当选择 JPG 时可以设定压缩质量 ( 默认 75 ),如果选择 PNG 则会停用 JPG 压缩质量的调整功能。
label_format = QtWidgets.QLabel(MainWindow) # 說明文字 QLabel
label_format .setGeometry(370, 130, 100, 30)
label_format .setText('儲存格式')
label_format .setAlignment(Qt.AlignmentFlag.AlignCenter)
format = 'JPG' # 預設壓縮格式
# 改變壓縮格式時的函式
def changeFormat():
global format
format = box_format.currentText() # 取得選擇的壓縮格式
if format == 'JPG':
label_jpg.setDisabled(False) # 如果是 JPG,啟用 JPG 壓縮說明文字
label_jpg_val.setDisabled(False) # 如果是 JPG,啟用壓縮數值顯示
slider.setDisabled(False) # 如果是 JPG,啟用滑桿
else:
label_jpg.setDisabled(True) # 如果不是 JPG,停用 JPG 壓縮說明文字
label_jpg_val.setDisabled(True) # 如果不是 JPG,停用壓縮數值顯示
slider.setDisabled(True) # 如果不是 JPG,停用滑桿
box_format = QtWidgets.QComboBox(MainWindow) # 格式下拉選單
box_format .addItems(['JPG','PNG']) # 兩種選擇格式
box_format .setGeometry(370,160,100,30)
box_format .currentIndexChanged.connect(changeFormat) # 切換時連動函式
label_jpg = QtWidgets.QLabel(MainWindow) # JPG 壓縮說明文字
label_jpg.setGeometry(370, 190, 100, 30)
label_jpg.setText('JPG 壓縮品質')
label_jpg.setAlignment(Qt.AlignmentFlag.AlignCenter)
val = 75 # 預設壓縮數值
label_jpg_val = QtWidgets.QLabel(MainWindow) # 壓縮數值顯示
label_jpg_val.setGeometry(370, 210, 100, 30)
label_jpg_val.setText(str(val))
label_jpg_val.setAlignment(Qt.AlignmentFlag.AlignCenter)
# 顯示壓縮數值的函式
def show():
global val
val = slider.value() # 取得滑桿數值
label_jpg_val.setText(str(slider.value())) # 顯示滑桿數值
slider = QtWidgets.QSlider(MainWindow) # 建立滑桿元件
slider.setOrientation(Qt.Orientation.Horizontal) # 水平顯示
slider.setGeometry(370,230,100,30)
slider.setRange(0, 100) # 調整範圍
slider.setValue(val) # 預設值
slider.valueChanged.connect(show) # 連動壓縮數值顯示函式
完成存档功能
最后加入另存新档的函数,并将另存新档的按钮连动该函数,存档时弹出文件视窗,并透过 Pillow 函数库进行存档作业。
# 存檔函式
def saveFile():
global format, val, img, size
# 如果格式為 JPG
if format == 'JPG':
# 限制只能存 JPG
filePath, filterType = QtWidgets.QFileDialog.getSaveFileName(filter='JPG(*.jpg)')
# 如果確定存檔
if filePath:
# 根據尺寸調整數值設定尺寸
nw = int ( img.size[0] * size/100 )
nh = int ( img.size[1] * size/100 )
img2 = img.resize((nw, nh))
# 使用 Pillow 函式庫存檔
img2.save(filePath, quality=val, subsampling=0)
else:
# 限制只能存 PNG
filePath, filterType = QtWidgets.QFileDialog.getSaveFileName(filter='PNG(*.png)')
if filePath:
# 根據尺寸調整數值設定尺寸
nw = int ( img.size[0] * size/100 )
nh = int ( img.size[1] * size/100 )
img2 = img.resize((nw, nh))
# 使用 Pillow 函式庫存檔
img2.save(filePath, 'png')
def closeFile():
app.quit()
mbox = QtWidgets.QMessageBox(MainWindow)
btn_open = QtWidgets.QPushButton(MainWindow)
btn_open.setText('開啟圖片')
btn_open.setGeometry(370, 10, 100, 30)
btn_open.clicked.connect(newFile)
btn_save = QtWidgets.QPushButton(MainWindow)
btn_save.setText('另儲圖片')
btn_save.setGeometry(370, 40, 100, 30)
btn_save.clicked.connect(saveFile) # 連動存檔函式
完整程序码
一般写法:
from PyQt6 import QtWidgets
from PyQt6.QtGui import *
from PyQt6.QtCore import *
import sys
from PIL import Image
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
MainWindow.setObjectName("MainWindow")
MainWindow.setWindowTitle("oxxo.studio")
MainWindow.resize(480, 360)
canvas = QPixmap(360,360) # 建立 QPixmap 畫布元件
canvas.fill(QColor('#ffffff')) # 預設填滿白色
label = QtWidgets.QLabel(MainWindow) # 建立 QLabel
label.setGeometry(0, 0, 360, 360) # 設定大小和位置
label.setPixmap(canvas) # 放入 QPixmap
mbox = QtWidgets.QMessageBox(MainWindow) # 建立對話視窗元件
# 開新檔案的函式
def newFile():
global img # 新增全域變數 img
# 開啟檔案時限制為 jpg、png 和 gif 的圖片格式
filePath , filetype = QtWidgets.QFileDialog.getOpenFileName(filter='IMAGE(*.jpg *.png *.gif)')
# 如果開啟檔案
if filePath:
ret = mbox.question(MainWindow, 'question', '確定開新檔案?') # 彈出對話視窗詢問是否開啟
# 如果點選 yes
if ret == mbox.StandardButton.Yes:
img = Image.open(filePath) # 建立 Pillow 圖片元件紀錄圖片資訊
canvas.load(filePath)
label.setPixmap(canvas) # 將圖片開啟到 QPixmap 畫布裡
MainWindow.update() # 更新視窗內容
else:
return
# 關閉視窗的函式
def closeFile():
app.quit()
btn_open = QtWidgets.QPushButton(MainWindow) # 開啟圖片按鈕
btn_open.setText('開啟圖片')
btn_open.setGeometry(370, 10, 100, 30)
btn_open.clicked.connect(newFile) # 點擊按鈕的動作
btn_save = QtWidgets.QPushButton(MainWindow) # 另存圖片按鈕
btn_save.setText('另儲圖片')
btn_save.setGeometry(370, 40, 100, 30)
btn_close = QtWidgets.QPushButton(MainWindow) # 關閉視窗的按鈕
btn_close.setText('關閉')
btn_close.setGeometry(370, 320, 100, 30)
btn_close.clicked.connect(closeFile) # 點擊按鈕的動作
label_size = QtWidgets.QLabel(MainWindow) # 加入說明文字 QLabel
label_size.setGeometry(370, 70, 100, 30)
label_size.setText('尺寸改變')
label_size.setAlignment(Qt.AlignmentFlag.AlignCenter)
size =100 # 預設尺寸 100%
# 改變尺寸的函式
def changeSize():
global size
size = box_size.value() # 取得數值調整元件的數值
box_size = QtWidgets.QSpinBox(MainWindow) # 建立數值調整元件
box_size.setGeometry(390, 100, 60, 30) # 設定位置
box_size.setRange(0,200) # 設定調整範圍
box_size.setValue(size) # 設定預設值
box_size.valueChanged.connect(changeSize) # 數值改變時連動的函式
label_format = QtWidgets.QLabel(MainWindow) # 說明文字 QLabel
label_format .setGeometry(370, 130, 100, 30)
label_format .setText('儲存格式')
label_format .setAlignment(Qt.AlignmentFlag.AlignCenter)
format = 'JPG' # 預設壓縮格式
# 改變壓縮格式時的函式
def changeFormat():
global format
format = box_format.currentText() # 取得選擇的壓縮格式
if format == 'JPG':
label_jpg.setDisabled(False) # 如果是 JPG,啟用 JPG 壓縮說明文字
label_jpg_val.setDisabled(False) # 如果是 JPG,啟用壓縮數值顯示
slider.setDisabled(False) # 如果是 JPG,啟用滑桿
else:
label_jpg.setDisabled(True) # 如果不是 JPG,停用 JPG 壓縮說明文字
label_jpg_val.setDisabled(True) # 如果不是 JPG,停用壓縮數值顯示
slider.setDisabled(True) # 如果不是 JPG,停用滑桿
box_format = QtWidgets.QComboBox(MainWindow) # 格式下拉選單
box_format .addItems(['JPG','PNG']) # 兩種選擇格式
box_format .setGeometry(370,160,100,30)
box_format .currentIndexChanged.connect(changeFormat) # 切換時連動函式
label_jpg = QtWidgets.QLabel(MainWindow) # JPG 壓縮說明文字
label_jpg.setGeometry(370, 190, 100, 30)
label_jpg.setText('JPG 壓縮品質')
label_jpg.setAlignment(Qt.AlignmentFlag.AlignCenter)
val = 75 # 預設壓縮數值
label_jpg_val = QtWidgets.QLabel(MainWindow) # 壓縮數值顯示
label_jpg_val.setGeometry(370, 210, 100, 30)
label_jpg_val.setText(str(val))
label_jpg_val.setAlignment(Qt.AlignmentFlag.AlignCenter)
# 顯示壓縮數值的函式
def show():
global val
val = slider.value() # 取得滑桿數值
label_jpg_val.setText(str(slider.value())) # 顯示滑桿數值
slider = QtWidgets.QSlider(MainWindow) # 建立滑桿元件
slider.setOrientation(Qt.Orientation.Horizontal) # 水平顯示
slider.setGeometry(370,230,100,30)
slider.setRange(0, 100) # 調整範圍
slider.setValue(val) # 預設值
slider.valueChanged.connect(show) # 連動壓縮數值顯示函式
# 存檔函式
def saveFile():
global format, val, img, size
# 如果格式為 JPG
if format == 'JPG':
# 限制只能存 JPG
filePath, filterType = QtWidgets.QFileDialog.getSaveFileName(filter='JPG(*.jpg)')
# 如果確定存檔
if filePath:
# 根據尺寸調整數值設定尺寸
nw = int ( img.size[0] * size/100 )
nh = int ( img.size[1] * size/100 )
img2 = img.resize((nw, nh))
# 使用 Pillow 函式庫存檔
img2.save(filePath, quality=val, subsampling=0)
else:
# 限制只能存 PNG
filePath, filterType = QtWidgets.QFileDialog.getSaveFileName(filter='PNG(*.png)')
if filePath:
# 根據尺寸調整數值設定尺寸
nw = int ( img.size[0] * size/100 )
nh = int ( img.size[1] * size/100 )
img2 = img.resize((nw, nh))
# 使用 Pillow 函式庫存檔
img2.save(filePath, 'png')
def closeFile():
app.quit()
mbox = QtWidgets.QMessageBox(MainWindow)
btn_open = QtWidgets.QPushButton(MainWindow)
btn_open.setText('開啟圖片')
btn_open.setGeometry(370, 10, 100, 30)
btn_open.clicked.connect(newFile)
btn_save = QtWidgets.QPushButton(MainWindow)
btn_save.setText('另儲圖片')
btn_save.setGeometry(370, 40, 100, 30)
btn_save.clicked.connect(saveFile) # 連動存檔函式
MainWindow.show()
sys.exit(app.exec())
使用 class 的写法 ( 需要注意将 show 改成 showVal,size 改成 sizeVal,避免和默认的方法冲突 ):
from PyQt6 import QtWidgets
from PyQt6.QtGui import *
from PyQt6.QtCore import *
import sys
from PIL import Image
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('oxxo.studio')
self.resize(480, 360)
self.ui()
self.resizeUi()
self.formatUi()
def ui(self):
self.canvas = QPixmap(360,360)
self.canvas.fill(QColor('#ffffff'))
self.label = QtWidgets.QLabel(self)
self.label.setGeometry(0, 0, 360, 360)
self.label.setPixmap(self.canvas)
self.mbox = QtWidgets.QMessageBox(self)
self.btn_open = QtWidgets.QPushButton(self)
self.btn_open.setText('開啟圖片')
self.btn_open.setGeometry(370, 10, 100, 30)
self.btn_open.clicked.connect(self.newFile)
self.btn_save = QtWidgets.QPushButton(self)
self.btn_save.setText('另儲圖片')
self.btn_save.setGeometry(370, 40, 100, 30)
self.btn_save.clicked.connect(self.saveFile)
self.btn_close = QtWidgets.QPushButton(self)
self.btn_close.setText('關閉')
self.btn_close.setGeometry(370, 320, 100, 30)
self.btn_close.clicked.connect(self.closeFile)
def resizeUi(self):
self.imgsize =100
self.label_size = QtWidgets.QLabel(self)
self.label_size.setGeometry(370, 70, 100, 30)
self.label_size.setText('尺寸改變')
self.label_size.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.box_size = QtWidgets.QSpinBox(self)
self.box_size.setGeometry(390, 100, 60, 30)
self.box_size.setRange(0,200)
self.box_size.setValue(self.imgsize)
self.box_size.valueChanged.connect(self.changeSize)
def formatUi(self):
self.label_format = QtWidgets.QLabel(self)
self.label_format .setGeometry(370, 130, 100, 30)
self.label_format .setText('儲存格式')
self.label_format .setAlignment(Qt.AlignmentFlag.AlignCenter)
self.format = 'JPG'
self.box_format = QtWidgets.QComboBox(self)
self.box_format .addItems(['JPG','PNG'])
self.box_format .setGeometry(370,160,100,30)
self.box_format .currentIndexChanged.connect(self.changeFormat)
self.label_jpg = QtWidgets.QLabel(self)
self.label_jpg.setGeometry(370, 190, 100, 30)
self.label_jpg.setText('JPG 壓縮品質')
self.label_jpg.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.val = 75
self.label_jpg_val = QtWidgets.QLabel(self)
self.label_jpg_val.setGeometry(370, 210, 100, 30)
self.label_jpg_val.setText(str(self.val))
self.label_jpg_val.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.slider = QtWidgets.QSlider(self)
self.slider.setOrientation(Qt.Orientation.Horizontal)
self.slider.setGeometry(370,230,100,30)
self.slider.setRange(0, 100)
self.slider.setValue(self.val)
self.slider.valueChanged.connect(self.showVal)
def newFile(self):
filePath , filetype = QtWidgets.QFileDialog.getOpenFileName(filter='IMAGE(*.jpg *.png *.gif)')
if filePath:
ret = self.mbox.question(self, 'question', '確定開新檔案?')
if ret == self.mbox.StandardButton.Yes:
self.img = Image.open(filePath)
self.canvas.load(filePath)
self.label.setPixmap(self.canvas) # 將圖片開啟到 QPixmap 畫布裡
self.update()
else:
return
def saveFile(self):
if self.format == 'JPG':
filePath, filterType = QtWidgets.QFileDialog.getSaveFileName(filter='JPG(*.jpg)')
if filePath:
nw = int ( self.img.size[0] * self.imgsize/100 )
nh = int ( self.img.size[1] * self.imgsize/100 )
img2 = self.img.resize((nw, nh))
img2.save(filePath, quality=self.val, subsampling=0)
else:
filePath, filterType = QtWidgets.QFileDialog.getSaveFileName(filter='PNG(*.png)')
if filePath:
nw = int ( self.img.size[0] * self.imgsize/100 )
nh = int ( self.img.size[1] * self.imgsize/100 )
img2 = self.img.resize((nw, nh))
img2.save(filePath, 'png')
def closeFile(self):
app.quit()
def changeSize(self):
self.imgsize = self.box_size.value()
def changeFormat(self):
self.format = self.box_format.currentText()
if self.format == 'JPG':
self.label_jpg.setDisabled(False)
self.label_jpg_val.setDisabled(False)
self.slider.setDisabled(False)
else:
self.label_jpg.setDisabled(True)
self.label_jpg_val.setDisabled(True)
self.slider.setDisabled(True)
def showVal(self):
self.val = self.slider.value()
self.label_jpg_val.setText(str(self.slider.value()))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
Form = MyWidget()
Form.show()
sys.exit(app.exec())
微信扫码关注
抖音扫码关注