Python+PyQt5編寫一個(gè)批量圖片添加水印工具(附源碼)
這篇文章完整記錄一個(gè)「批量圖片添加水印」桌面工具的研發(fā)過程:左側(cè)配置水印參數(shù),右側(cè)實(shí)時(shí)預(yù)覽效果,支持單張?zhí)幚砼c文件夾批量處理,適合用作 PyQt5 入門到實(shí)戰(zhàn)的小項(xiàng)目。

1. 需求與界面目標(biāo)
目標(biāo)界面(和你提供的截圖一致的交互形態(tài)):
- 左側(cè)「水印參數(shù)配置」
- 水印文字(可編輯)
- 字體大?。ㄏ袼兀?/li>
- 透明度(0-100)
- 文字顏色(彈窗取色)
- 水印位置(左上/右上/左下/右下/居中)
- 全屏水印模式(忽略位置,按間距平鋪)
- 水印旋轉(zhuǎn)角度
- 水平/垂直間距
- 行數(shù)/列數(shù)(可選,輔助控制密度)
- 兩個(gè)按鈕:處理單個(gè)圖片 / 批量處理文件夾
- 右側(cè)「預(yù)覽」
- 選擇預(yù)覽圖片后,參數(shù)變化即時(shí)刷新預(yù)覽
- 批量處理結(jié)束時(shí)彈出提示:成功處理圖片數(shù)量
1.1 效果截圖
你可以把截圖放到項(xiàng)目目錄下(例如 assets/ui.png),然后在博客里引用:

2. 技術(shù)選型與依賴
2.1 GUI:PyQt5
使用 PyQt5 的原因:
- 組件完善(輸入框、下拉框、取色對(duì)話框、文件選擇對(duì)話框等一應(yīng)俱全)
- QPainter 繪制文字水印非常順手
- 跨平臺(tái)(Windows/macOS/Linux)
2.2 圖片處理:QImage + QPainter
核心思路:把原圖讀入為 QImage,創(chuàng)建一個(gè)同尺寸畫布,在畫布上先繪制原圖,再繪制水印文字,最后保存到目標(biāo)路徑。
3. 工程結(jié)構(gòu)(單文件也能清晰)
這個(gè)項(xiàng)目以單文件實(shí)現(xiàn)為主,代碼結(jié)構(gòu)仍然保持「可擴(kuò)展」:
WatermarkSettings:水印配置數(shù)據(jù)模型(dataclass)MainWindow:界面與交互_build_ui():搭界面_wire_signals():綁定信號(hào),實(shí)現(xiàn)“改參數(shù)即刷新預(yù)覽”_apply_watermark():把水印繪制到圖片上_process_single_image():?jiǎn)螆D處理_process_folder():文件夾批量處理
4. 界面實(shí)現(xiàn):左配右預(yù)覽
4.1 左側(cè)參數(shù)配置區(qū)
左側(cè)用一個(gè) QGroupBox("水印參數(shù)配置") 包起來,內(nèi)部用 QVBoxLayout 縱向堆疊每一行配置項(xiàng),每一行再用 QHBoxLayout 實(shí)現(xiàn)“標(biāo)簽 + 控件”的排列。
典型控件選擇:
- 文本:
QLineEdit - 數(shù)值:
QSpinBox(整數(shù))、QDoubleSpinBox(旋轉(zhuǎn)角度) - 下拉:
QComboBox - 開關(guān):
QCheckBox - 取色:
QColorDialog
對(duì)應(yīng)實(shí)現(xiàn)位置:
MainWindow._build_ui():創(chuàng)建控件并組裝布局
4.2 右側(cè)預(yù)覽區(qū)
右側(cè)同樣用 QGroupBox("預(yù)覽") 包起來,核心是一個(gè) QLabel 作為畫布:
- 通過
QPixmap.fromImage()將QImage轉(zhuǎn)為QPixmap - 使用
scaled(..., Qt.KeepAspectRatio, Qt.SmoothTransformation)自適應(yīng)縮放
預(yù)覽邏輯在:
MainWindow._update_preview()- 并在窗口
resizeEvent里觸發(fā)刷新,保證拖動(dòng)窗口大小時(shí)預(yù)覽不變形
5. 關(guān)鍵交互:參數(shù)變化即刷新預(yù)覽
思路很簡(jiǎn)單:把所有“會(huì)影響預(yù)覽”的控件信號(hào)都綁定到 _update_preview()。
在代碼里使用了一個(gè)小技巧:遍歷控件列表,按控件可能擁有的信號(hào)類型去連接:
- 文本框:
textChanged - SpinBox:
valueChanged - ComboBox:
currentTextChanged - CheckBox:
toggled
對(duì)應(yīng)實(shí)現(xiàn)位置:MainWindow._wire_signals()
這樣新增控件也方便:只要把控件加進(jìn)列表,就能自動(dòng)獲得“聯(lián)動(dòng)預(yù)覽”能力。
6. 水印渲染核心:QPainter 怎么畫文字水印
水印繪制主要分兩類:
- 單個(gè)水?。ò次恢美L制一次)
- 全屏水印(按間距平鋪繪制多次)
它們都共享同一段“準(zhǔn)備畫筆”的邏輯:
- 轉(zhuǎn)成
QImage.Format_ARGB32,確保支持透明度混合 painter.setOpacity(opacity/100)設(shè)置整體透明度- 設(shè)置字體像素大小
font.setPixelSize(font_size) QPen(QColor)設(shè)置文字顏色
對(duì)應(yīng)實(shí)現(xiàn)位置:MainWindow._apply_watermark()
6.1 單個(gè)水印:位置 + 旋轉(zhuǎn)
單個(gè)水印的關(guān)鍵點(diǎn):
- 先用
QFontMetricsF測(cè)量文字矩形(用于右下等位置的對(duì)齊) - 通過
painter.translate(...)把坐標(biāo)系移動(dòng)到文字中心 painter.rotate(angle)旋轉(zhuǎn)- 在“旋轉(zhuǎn)后的坐標(biāo)系”里繪制文字
對(duì)應(yīng)實(shí)現(xiàn)位置:MainWindow._draw_single()
6.2 全屏平鋪水?。浩戒伔秶c密度控制
平鋪水印的關(guān)鍵點(diǎn):
- 把坐標(biāo)系移動(dòng)到圖片中心再旋轉(zhuǎn),這樣平鋪更自然
- 使用
spacing_x / spacing_y控制密度 - 可選
rows / cols:當(dāng)用戶指定行列數(shù)時(shí),用圖片寬高反推間距 - 平鋪范圍用
[-w, w]、[-h, h],保證旋轉(zhuǎn)后邊角仍覆蓋
對(duì)應(yīng)實(shí)現(xiàn)位置:MainWindow._draw_tiled()
7. 處理單圖與批量處理:文件選擇與輸出規(guī)則
7.1 處理單圖
流程:
- 彈出文件選擇框選圖
- 讀取
QImage - 調(diào)用水印渲染
- 保存為同目錄
*_watermarked.ext - 彈窗提示 “已生成 1 張圖片”
對(duì)應(yīng)實(shí)現(xiàn)位置:MainWindow._process_single_image()
7.2 批量處理文件夾
流程:
- 選擇文件夾
- 遞歸掃描圖片文件擴(kuò)展名:
.jpg/.jpeg/.png/.bmp/.webp - 輸出目錄:
<選擇的文件夾>/watermarked_output/ - 逐張保存,同名覆蓋輸出目錄下同名文件
- 彈窗提示成功處理數(shù)量
對(duì)應(yīng)實(shí)現(xiàn)位置:
iter_image_files()MainWindow._process_folder()
8. 如何運(yùn)行
8.1 安裝依賴
pip install PyQt5
8.2 啟動(dòng)程序
在項(xiàng)目目錄執(zhí)行:
python main.py
9. 常見問題(FAQ)
9.1 為什么有些圖片保存失?。?/h3>
可能原因:
- 圖片路徑包含特殊字符導(dǎo)致保存權(quán)限不足(建議輸出到有寫權(quán)限的目錄)
- 圖片格式不支持寫入(已支持常見格式;少見格式可先轉(zhuǎn)為 PNG/JPG)
9.2 透明度為什么感覺不明顯?
透明度是對(duì)“整個(gè)繪制操作”生效的,與背景顏色、圖片亮度有關(guān)。深色 圖上淺色文字會(huì)更明顯;如果不明顯,可以:
- 提高字體大小
- 調(diào)整顏色對(duì)比
- 提高透明度(更接近 100)
9.3 全屏水印密度怎么控制?
優(yōu)先級(jí):
- 若
行數(shù)/列數(shù)> 0,則用它們反推間距(更直觀) - 否則用
水平間距/垂直間距直接控制
10. 可擴(kuò)展方向
如果你想把它升級(jí)成“更像產(chǎn)品”的工具,推薦從這些方向迭代:
- 支持圖片縮放后再加水?。ɡ巛敵鼋y(tǒng)一寬度)
- 支持輸出質(zhì)量/壓縮率(JPG quality)
- 支持水印陰影/描邊(增強(qiáng)可讀性)
- 支持導(dǎo)出到自定義目錄,而不是固定
watermarked_output - 批量處理加進(jìn)度條與取消按鈕(避免大文件夾卡?。?/li>
- 支持圖片 EXIF 方向糾正(部分手機(jī)照片會(huì)旋轉(zhuǎn))
11. 打包成可執(zhí)行程序(Windows)
如果你想發(fā)給沒有 Python 環(huán)境的同學(xué)使用,最常見做法是用 PyInstaller 打包。
11.1 安裝 PyInstaller
pip install pyinstaller
11.2 一鍵打包(無控制臺(tái)窗口)
在項(xiàng)目目錄執(zhí)行:
pyinstaller -F -w main.py --name 圖片水印批量工具
打包完成后可執(zhí)行文件通常在 dist/圖片水印批量工具.exe。
11.3 常見打包問題
如果運(yùn)行時(shí)報(bào)缺少 Qt 插件(例如 platform plugin),可以嘗試升級(jí) PyInstaller,或使用:
pyinstaller -F -w main.py --name 圖片水印批量工具 --collect-all PyQt5
12. 關(guān)鍵代碼入口
- 程序入口:
main()->MainWindow() - 預(yù)覽刷新:
_update_preview() - 水印渲染:
_apply_watermark()->_draw_single()/_draw_tiled() - 批量掃描:
iter_image_files()
如果你準(zhǔn)備把項(xiàng)目拆成“UI + 業(yè)務(wù) + 工具函數(shù)”的結(jié)構(gòu),也可以在后續(xù)把水印渲染獨(dú)立成一個(gè)模塊(例如 watermark.py),界面只負(fù)責(zé)讀參數(shù)和調(diào)用即可。
13.完整代碼
import os
import sys
from dataclasses import dataclass
from typing import Iterable, Optional
from PyQt5.QtCore import Qt, QPointF
from PyQt5.QtGui import QColor, QFont, QFontMetricsF, QImage, QPainter, QPen, QPixmap
from PyQt5.QtWidgets import (
QApplication,
QCheckBox,
QColorDialog,
QComboBox,
QDoubleSpinBox,
QFileDialog,
QGroupBox,
QHBoxLayout,
QLabel,
QMainWindow,
QMessageBox,
QPushButton,
QSizePolicy,
QSpinBox,
QVBoxLayout,
QWidget,
)
@dataclass(frozen=True)
class WatermarkSettings:
text: str
font_size: int
opacity: int
color: QColor
position: str
fullscreen: bool
rotation: float
spacing_x: int
spacing_y: int
rows: int
cols: int
SUPPORTED_EXTS = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
def iter_image_files(folder: str) -> Iterable[str]:
for root, _, files in os.walk(folder):
for name in files:
ext = os.path.splitext(name)[1].lower()
if ext in SUPPORTED_EXTS:
yield os.path.join(root, name)
class MainWindow(QMainWindow):
def __init__(self) -> None:
super().__init__()
self.setWindowTitle("圖片水印批量工具")
self.resize(1100, 650)
self._current_image_path: Optional[str] = None
self._color = QColor(120, 0, 120)
self._build_ui()
self._wire_signals()
self._update_color_button()
self._update_preview()
def _build_ui(self) -> None:
root = QWidget(self)
self.setCentralWidget(root)
main_layout = QHBoxLayout(root)
self.group_config = QGroupBox("水印參數(shù)配置", root)
cfg_layout = QVBoxLayout(self.group_config)
row1 = QHBoxLayout()
row1.addWidget(QLabel("水印文字:", self.group_config))
self.input_text = QLabel(self.group_config)
self.input_text.setTextInteractionFlags(Qt.TextSelectableByMouse)
self.text_value = QLabel(self.group_config)
self.text_value.hide()
self.text_edit = None
from PyQt5.QtWidgets import QLineEdit
self.text_edit = QLineEdit(self.group_config)
self.text_edit.setText("@小莊-Python辦公")
row1.addWidget(self.text_edit, 1)
cfg_layout.addLayout(row1)
row2 = QHBoxLayout()
row2.addWidget(QLabel("字體大?。?, self.group_config))
self.spin_font = QSpinBox(self.group_config)
self.spin_font.setRange(6, 400)
self.spin_font.setValue(40)
row2.addWidget(self.spin_font)
row2.addSpacing(20)
row2.addWidget(QLabel("透明度(0-100):", self.group_config))
self.spin_opacity = QSpinBox(self.group_config)
self.spin_opacity.setRange(0, 100)
self.spin_opacity.setValue(80)
row2.addWidget(self.spin_opacity)
row2.addStretch(1)
cfg_layout.addLayout(row2)
row3 = QHBoxLayout()
row3.addWidget(QLabel("文字顏色:", self.group_config))
self.btn_color = QPushButton("選擇顏色", self.group_config)
self.btn_color.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.color_swatch = QLabel(self.group_config)
self.color_swatch.setFixedSize(34, 22)
self.color_swatch.setFrameShape(QLabel.Box)
row3.addWidget(self.btn_color)
row3.addWidget(self.color_swatch)
row3.addStretch(1)
cfg_layout.addLayout(row3)
row4 = QHBoxLayout()
row4.addWidget(QLabel("水印位置:", self.group_config))
self.combo_pos = QComboBox(self.group_config)
self.combo_pos.addItems(["左上", "右上", "左下", "右下", "居中"])
self.combo_pos.setCurrentText("右下")
row4.addWidget(self.combo_pos)
row4.addStretch(1)
cfg_layout.addLayout(row4)
row5 = QHBoxLayout()
self.chk_fullscreen = QCheckBox("全屏水印模式(忽略位置設(shè)置)", self.group_config)
self.chk_fullscreen.setChecked(False)
row5.addWidget(self.chk_fullscreen)
row5.addStretch(1)
cfg_layout.addLayout(row5)
row6 = QHBoxLayout()
row6.addWidget(QLabel("水印旋轉(zhuǎn)角度:", self.group_config))
self.spin_rotation = QDoubleSpinBox(self.group_config)
self.spin_rotation.setRange(-180.0, 180.0)
self.spin_rotation.setDecimals(1)
self.spin_rotation.setSingleStep(1.0)
self.spin_rotation.setValue(30.0)
row6.addWidget(self.spin_rotation)
row6.addStretch(1)
cfg_layout.addLayout(row6)
row7 = QHBoxLayout()
row7.addWidget(QLabel("水平間距(像素):", self.group_config))
self.spin_spacing_x = QSpinBox(self.group_config)
self.spin_spacing_x.setRange(20, 5000)
self.spin_spacing_x.setValue(360)
row7.addWidget(self.spin_spacing_x)
row7.addSpacing(20)
row7.addWidget(QLabel("垂直間距(像素):", self.group_config))
self.spin_spacing_y = QSpinBox(self.group_config)
self.spin_spacing_y.setRange(20, 5000)
self.spin_spacing_y.setValue(200)
row7.addWidget(self.spin_spacing_y)
row7.addStretch(1)
cfg_layout.addLayout(row7)
row8 = QHBoxLayout()
row8.addWidget(QLabel("行數(shù):", self.group_config))
self.spin_rows = QSpinBox(self.group_config)
self.spin_rows.setRange(0, 200)
self.spin_rows.setValue(0)
row8.addWidget(self.spin_rows)
row8.addSpacing(20)
row8.addWidget(QLabel("列數(shù):", self.group_config))
self.spin_cols = QSpinBox(self.group_config)
self.spin_cols.setRange(0, 200)
self.spin_cols.setValue(1)
row8.addWidget(self.spin_cols)
row8.addStretch(1)
cfg_layout.addLayout(row8)
cfg_layout.addStretch(1)
btn_row = QHBoxLayout()
self.btn_single = QPushButton("處理單個(gè)圖片", self.group_config)
self.btn_folder = QPushButton("批量處理文件夾", self.group_config)
btn_row.addWidget(self.btn_single)
btn_row.addWidget(self.btn_folder)
cfg_layout.addLayout(btn_row)
main_layout.addWidget(self.group_config, 0)
self.group_preview = QGroupBox("預(yù)覽", root)
prev_layout = QVBoxLayout(self.group_preview)
self.preview = QLabel(self.group_preview)
self.preview.setAlignment(Qt.AlignCenter)
self.preview.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.preview.setMinimumSize(520, 520)
self.preview.setStyleSheet("background: #1f1f1f; color: #dddddd;")
prev_layout.addWidget(self.preview, 1)
self.btn_choose_preview = QPushButton("選擇預(yù)覽圖片", self.group_preview)
prev_layout.addWidget(self.btn_choose_preview, 0, Qt.AlignRight)
main_layout.addWidget(self.group_preview, 1)
def _wire_signals(self) -> None:
self.btn_color.clicked.connect(self._choose_color)
self.btn_choose_preview.clicked.connect(self._choose_preview_image)
self.btn_single.clicked.connect(self._process_single_image)
self.btn_folder.clicked.connect(self._process_folder)
for w in [
self.text_edit,
self.spin_font,
self.spin_opacity,
self.combo_pos,
self.chk_fullscreen,
self.spin_rotation,
self.spin_spacing_x,
self.spin_spacing_y,
self.spin_rows,
self.spin_cols,
]:
if hasattr(w, "textChanged"):
w.textChanged.connect(self._update_preview)
if hasattr(w, "valueChanged"):
w.valueChanged.connect(self._update_preview)
if hasattr(w, "currentTextChanged"):
w.currentTextChanged.connect(self._update_preview)
if hasattr(w, "toggled"):
w.toggled.connect(self._update_preview)
def _settings(self) -> WatermarkSettings:
return WatermarkSettings(
text=self.text_edit.text().strip(),
font_size=int(self.spin_font.value()),
opacity=int(self.spin_opacity.value()),
color=QColor(self._color),
position=self.combo_pos.currentText(),
fullscreen=self.chk_fullscreen.isChecked(),
rotation=float(self.spin_rotation.value()),
spacing_x=int(self.spin_spacing_x.value()),
spacing_y=int(self.spin_spacing_y.value()),
rows=int(self.spin_rows.value()),
cols=int(self.spin_cols.value()),
)
def _choose_color(self) -> None:
color = QColorDialog.getColor(self._color, self, "選擇水印顏色")
if color.isValid():
self._color = color
self._update_color_button()
self._update_preview()
def _update_color_button(self) -> None:
self.color_swatch.setStyleSheet(
f"background-color: {self._color.name()}; border: 1px solid #444;"
)
def _choose_preview_image(self) -> None:
path, _ = QFileDialog.getOpenFileName(
self,
"選擇圖片",
"",
"Images (*.png *.jpg *.jpeg *.bmp *.webp);;All Files (*)",
)
if not path:
return
self._current_image_path = path
self._update_preview()
def _process_single_image(self) -> None:
src, _ = QFileDialog.getOpenFileName(
self,
"選擇要處理的圖片",
"",
"Images (*.png *.jpg *.jpeg *.bmp *.webp);;All Files (*)",
)
if not src:
return
img = QImage(src)
if img.isNull():
QMessageBox.warning(self, "錯(cuò)誤", "圖片讀取失敗")
return
dst = self._suggest_output_path(src)
ok = self._save_watermarked(img, dst)
if not ok:
QMessageBox.warning(self, "錯(cuò)誤", "圖片保存失敗")
return
self._current_image_path = src
self._update_preview()
QMessageBox.information(self, "批量完成", "處理成功,已生成 1 張圖片")
def _process_folder(self) -> None:
folder = QFileDialog.getExistingDirectory(self, "選擇要批量處理的文件夾", "")
if not folder:
return
out_dir = os.path.join(folder, "watermarked_output")
os.makedirs(out_dir, exist_ok=True)
count = 0
first_image = None
for src in iter_image_files(folder):
if os.path.commonpath([src, out_dir]) == out_dir:
continue
if first_image is None:
first_image = src
img = QImage(src)
if img.isNull():
continue
base = os.path.basename(src)
dst = os.path.join(out_dir, base)
if self._save_watermarked(img, dst):
count += 1
if first_image:
self._current_image_path = first_image
self._update_preview()
QMessageBox.information(self, "批量完成", f"批量處理結(jié)束!\n共成功處理 {count} 張圖片")
def _suggest_output_path(self, src: str) -> str:
root, ext = os.path.splitext(src)
return f"{root}_watermarked{ext}"
def _save_watermarked(self, src_img: QImage, dst: str) -> bool:
settings = self._settings()
if not settings.text:
return False
out = self._apply_watermark(src_img, settings)
os.makedirs(os.path.dirname(dst), exist_ok=True)
return out.save(dst)
def _apply_watermark(self, src_img: QImage, settings: WatermarkSettings) -> QImage:
if src_img.format() != QImage.Format_ARGB32:
base = src_img.convertToFormat(QImage.Format_ARGB32)
else:
base = QImage(src_img)
out = QImage(base.size(), QImage.Format_ARGB32)
out.fill(Qt.transparent)
painter = QPainter(out)
painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing)
painter.drawImage(0, 0, base)
font = QFont()
font.setPixelSize(settings.font_size)
painter.setFont(font)
pen = QPen(settings.color)
painter.setPen(pen)
painter.setOpacity(max(0.0, min(1.0, settings.opacity / 100.0)))
if settings.fullscreen:
self._draw_tiled(painter, out.width(), out.height(), settings)
else:
self._draw_single(painter, out.width(), out.height(), settings)
painter.end()
return out
def _draw_single(self, painter: QPainter, w: int, h: int, settings: WatermarkSettings) -> None:
metrics = QFontMetricsF(painter.font())
rect = metrics.boundingRect(settings.text)
margin = 20.0
if settings.position == "左上":
x = margin
y = margin + rect.height()
elif settings.position == "右上":
x = w - margin - rect.width()
y = margin + rect.height()
elif settings.position == "左下":
x = margin
y = h - margin
elif settings.position == "右下":
x = w - margin - rect.width()
y = h - margin
else:
x = (w - rect.width()) / 2.0
y = (h + rect.height()) / 2.0
painter.save()
painter.translate(x + rect.width() / 2.0, y - rect.height() / 2.0)
painter.rotate(settings.rotation)
painter.drawText(QPointF(-rect.width() / 2.0, rect.height() / 2.0), settings.text)
painter.restore()
def _draw_tiled(self, painter: QPainter, w: int, h: int, settings: WatermarkSettings) -> None:
metrics = QFontMetricsF(painter.font())
rect = metrics.boundingRect(settings.text)
spacing_x = max(20, settings.spacing_x)
spacing_y = max(20, settings.spacing_y)
if settings.cols > 0:
spacing_x = max(20, int(w / max(1, settings.cols)))
if settings.rows > 0:
spacing_y = max(20, int(h / max(1, settings.rows)))
painter.save()
painter.translate(w / 2.0, h / 2.0)
painter.rotate(settings.rotation)
start_x = -w
end_x = w
start_y = -h
end_y = h
x = start_x
while x <= end_x:
y = start_y
while y <= end_y:
painter.drawText(QPointF(x, y), settings.text)
y += spacing_y
x += spacing_x
painter.restore()
def _update_preview(self) -> None:
if not self._current_image_path:
self.preview.setText("請(qǐng)選擇預(yù)覽圖片")
return
img = QImage(self._current_image_path)
if img.isNull():
self.preview.setText("預(yù)覽圖片讀取失敗")
return
settings = self._settings()
if not settings.text:
rendered = img
else:
rendered = self._apply_watermark(img, settings)
pix = QPixmap.fromImage(rendered)
target = self.preview.size()
if target.width() <= 1 or target.height() <= 1:
self.preview.setPixmap(pix)
return
self.preview.setPixmap(pix.scaled(target, Qt.KeepAspectRatio, Qt.SmoothTransformation))
def resizeEvent(self, event) -> None:
super().resizeEvent(event)
self._update_preview()
def main() -> int:
app = QApplication(sys.argv)
w = MainWindow()
w.show()
return app.exec_()
if __name__ == "__main__":
raise SystemExit(main())
以上就是Python+PyQt5編寫一個(gè)批量圖片添加水印工具(附源碼)的詳細(xì)內(nèi)容,更多關(guān)于Python圖片添加水印的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python如何在ubuntu中更改Python和pip指向
這篇文章主要介紹了Python如何在ubuntu中更改Python和pip指向問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
利用Python?爬取股票實(shí)時(shí)數(shù)據(jù)詳情
這篇文章主要介紹了利用Python?爬取股票實(shí)時(shí)數(shù)據(jù)詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-08-08
python實(shí)現(xiàn)壁紙批量下載代碼實(shí)例
本篇文章通過代碼實(shí)例給大家分享了用python實(shí)現(xiàn)壁紙批量下載這個(gè)功能,有興趣的朋友參考下吧。2018-01-01
Python實(shí)現(xiàn)爬取騰訊招聘網(wǎng)崗位信息
這篇文章主要介紹了如何用python爬取騰訊招聘網(wǎng)崗位信息保存到表格,并做成簡(jiǎn)單可視化。文中的示例代碼對(duì)學(xué)習(xí)Python有一定的幫助,感興趣的可以了解一下2022-01-01
pandas中DataFrame字典互轉(zhuǎn)的實(shí)現(xiàn)
在數(shù)據(jù)處理和分析中,Pandas是一個(gè)非常強(qiáng)大的Python庫,本文主要介紹了pandas中DataFrame字典互轉(zhuǎn)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-04-04
11個(gè)并不被常用但對(duì)開發(fā)非常有幫助的Python庫
這篇文章主要介紹了11個(gè)并不被常用但對(duì)開發(fā)非常有幫助的Python庫,這些庫大都被放在Github上開源、并且經(jīng)過一段時(shí)間的編寫和維護(hù),對(duì)Python開發(fā)有一定的幫助,需要的朋友可以參考下2015-03-03

