最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python+PyQt5開發(fā)一個全屏水印批量添加工具

 更新時間:2025年09月23日 10:07:04   作者:創(chuàng)客白澤  
本文詳細(xì)介紹了如何使用Python和PyQt5開發(fā)一個功能強大的全屏水印批量添加工具,支持PDF和多種圖片格式,具備完整的GUI界面和批量處理能力,希望對大家有所幫助

概述

在數(shù)字化時代,保護知識產(chǎn)權(quán)和防止內(nèi)容盜用變得尤為重要。水印技術(shù)作為一種有效的內(nèi)容保護手段,被廣泛應(yīng)用于文檔、圖片和視頻等多媒體內(nèi)容中。今天我們要介紹的是一款基于Python和PyQt5開發(fā)的全屏水印批量添加工具,它能夠為PDF文檔和多種圖片格式快速添加自定義水印。

這款工具不僅支持基本的文字水印功能,還提供了豐富的自定義選項,包括字體選擇、顏色調(diào)整、透明度設(shè)置、旋轉(zhuǎn)角度和密度控制等。通過多線程技術(shù),實現(xiàn)了高效的批量處理能力,大大提升了工作效率。

功能特性

核心功能

  • 批量處理:支持同時處理多個PDF或圖片文件
  • 多格式支持:PDF、JPG、PNG、BMP、GIF、TIFF、WEBP等格式
  • 全屏水印:自動在整個頁面/圖片上平鋪水印內(nèi)容
  • 高度自定義:字體、顏色、大小、角度、透明度、密度均可調(diào)整

技術(shù)特點

  • 多線程處理:后臺線程處理,UI不卡頓
  • 實時進度:顯示處理進度和已處理文件列表
  • 實時預(yù)覽:實時查看水印效果
  • 高效性能:優(yōu)化的算法確保處理速度

展示效果

PDF全屏水印效果:

圖片全屏水印效果:

系統(tǒng)架構(gòu)

軟件步驟說明

1. 環(huán)境準(zhǔn)備

首先需要安裝必要的Python庫:

pip install PyPDF2 reportlab pillow PyQt5

2. 使用步驟

1.選擇文件夾:指定輸入和輸出文件夾

2.設(shè)置水印參數(shù)

  • 輸入水印文字內(nèi)容
  • 選擇字體和顏色
  • 調(diào)整大小、角度和透明度
  • 設(shè)置水印密度

3.預(yù)覽效果:實時查看水印樣式

4.開始處理:選擇處理PDF或圖片文件

5.查看結(jié)果:在輸出文件夾查看處理后的文件

3. 參數(shù)說明

參數(shù)說明推薦值
字體大小水印文字的大小20-40px
旋轉(zhuǎn)角度水印的旋轉(zhuǎn)角度30-45°
透明度水印的不透明度0.1-0.3
密度水印的分布密度4-8

代碼解析

核心類結(jié)構(gòu)

class WatermarkWorker(QThread):
    """處理PDF和圖片水印的工作線程"""
    progress = pyqtSignal(int)
    finished = pyqtSignal(bool, str)
    file_processed = pyqtSignal(str)
    
    def __init__(self, input_folder, output_folder, watermark_text, font_size, 
                 font_path, color, angle, opacity, density, file_type):
        # 初始化參數(shù)
        super().__init__()
        
    def run(self):
        # 主處理邏輯
        pass
        
    def add_watermark_to_pdf(self, input_path, output_path):
        # PDF水印處理
        pass
        
    def add_watermark_to_image(self, input_path, output_path):
        # 圖片水印處理
        pass

PDF水印處理原理

PDF水印處理使用了PyPDF2ReportLab兩個庫:

  • 讀取PDF:使用PdfReader讀取原始PDF文件
  • 創(chuàng)建水印:使用ReportLab在內(nèi)存中生成水印PDF
  • 合并水印:將水印PDF與原始PDF每一頁合并
  • 保存結(jié)果:使用PdfWriter寫入新的PDF文件
def add_watermark_to_pdf(self, input_path, output_path):
    try:
        # 讀取PDF
        reader = PdfReader(input_path)
        writer = PdfWriter()
        
        # 獲取PDF頁面尺寸
        first_page = reader.pages[0]
        page_width = float(first_page.mediabox.width)
        page_height = float(first_page.mediabox.height)
        
        # 創(chuàng)建水印
        watermark_pdf = self.create_watermark_pdf(page_width, page_height)
        
        # 為每一頁添加水印
        for page in reader.pages:
            page.merge_page(watermark_pdf.pages[0])
            writer.add_page(page)
        
        # 保存PDF
        with open(output_path, 'wb') as output_file:
            writer.write(output_file)
        
        return True
        
    except Exception as e:
        print(f"Error processing PDF {input_path}: {str(e)}")
        return False

圖片水印處理原理

圖片水印處理使用Pillow庫:

  • 打開圖片:使用Image.open()讀取圖片
  • 創(chuàng)建水印層:新建一個透明圖層用于繪制水印
  • 繪制水印:根據(jù)密度參數(shù)計算水印位置并繪制
  • 合并圖層:將水印層與原始圖片合并
  • 保存結(jié)果:根據(jù)原格式保存圖片
def add_watermark_to_image(self, input_path, output_path):
    try:
        # 打開圖片
        image = Image.open(input_path).convert('RGBA')
        
        # 創(chuàng)建水印圖層
        watermark_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
        draw = ImageDraw.Draw(watermark_layer)
        
        # 獲取字體
        font = self.get_pil_font()
        
        # 計算水印間距和位置
        spacing_x = image.width / self.density
        spacing_y = image.height / self.density
        
        # 繪制水印
        for x in range(0, int(image.width // spacing_x) + 1):
            for y in range(0, int(image.height // spacing_y) + 1):
                # 計算位置并繪制水印
                pass
        
        # 合并水印到原圖
        watermarked_image = Image.alpha_composite(image, watermark_layer)
        watermarked_image.save(output_path)
        
        return True
        
    except Exception as e:
        print(f"Error processing image {input_path}: {str(e)}")
        return False

多線程處理機制

為了避免界面卡頓,使用了QThread進行后臺處理:

class WatermarkWorker(QThread):
    """處理PDF和圖片水印的工作線程"""
    progress = pyqtSignal(int)  # 進度信號
    finished = pyqtSignal(bool, str)  # 完成信號
    file_processed = pyqtSignal(str)  # 文件處理完成信號
    
    def run(self):
        try:
            # 獲取文件列表
            if self.file_type == 'pdf':
                files = [f for f in os.listdir(self.input_folder) if f.lower().endswith('.pdf')]
            else:
                # 獲取圖片文件
                pass
            
            # 處理每個文件
            for i, filename in enumerate(files):
                if self.canceled:
                    break
                
                # 處理文件...
                
                # 發(fā)送進度信號
                progress = int((i + 1) / total_files * 100)
                self.progress.emit(progress)
            
            self.finished.emit(not self.canceled, "處理完成")
            
        except Exception as e:
            self.finished.emit(False, f"發(fā)生錯誤: {str(e)}")

字體處理策略

為了解決中文字體顯示問題,實現(xiàn)了智能字體選擇機制:

def get_pil_font(self):
    """獲取PIL字體對象"""
    try:
        if self.font_path and os.path.exists(self.font_path):
            return ImageFont.truetype(self.font_path, self.font_size)
        else:
            # 嘗試使用系統(tǒng)默認(rèn)中文字體
            windows_fonts = [
                "C:/Windows/Fonts/simhei.ttf",  # 黑體
                "C:/Windows/Fonts/simsun.ttc",  # 宋體
                "C:/Windows/Fonts/msyh.ttc",    # 微軟雅黑
            ]
            
            for font_path in windows_fonts:
                if os.path.exists(font_path):
                    return ImageFont.truetype(font_path, self.font_size)
            
            # 如果找不到中文字體,使用默認(rèn)字體
            return ImageFont.load_default()
    except:
        return ImageFont.load_default()

性能優(yōu)化策略

1. 內(nèi)存優(yōu)化

  • 使用流式處理,避免一次性加載所有文件到內(nèi)存
  • 及時釋放不再需要的資源

2. 處理速度優(yōu)化

  • 多線程處理,充分利用多核CPU
  • 算法優(yōu)化,減少不必要的計算

3. 用戶體驗優(yōu)化

  • 實時預(yù)覽功能
  • 進度反饋機制
  • 錯誤處理和恢復(fù)機制

擴展功能

已實現(xiàn)功能

  • 批量處理PDF和圖片文件
  • 自定義水印文字和樣式
  • 實時預(yù)覽效果
  • 多線程處理
  • 進度顯示和文件列表

未來可擴展功能

  • 圖片水印支持(上傳Logo圖片)
  • 水印模板保存和加載
  • 命令行界面支持
  • 云端處理功能
  • 批量重命名功能
  • 更多文件格式支持

源碼下載

import os
import sys
import math
import io
from PyPDF2 import PdfReader, PdfWriter
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib.colors import HexColor, Color
from reportlab.lib.utils import ImageReader
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from PIL import Image, ImageDraw, ImageFont, ImageOps

from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QLabel, QLineEdit, QPushButton, QSpinBox, QDoubleSpinBox,
                             QColorDialog, QFileDialog, QComboBox, QSlider, QGroupBox,
                             QProgressBar, QMessageBox, QCheckBox, QFrame,
                             QTextEdit, QFontComboBox, QListWidget, QListWidgetItem, QGridLayout)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize, QRectF, QPointF
from PyQt5.QtGui import QColor, QFont, QPalette, QIcon, QPixmap, QFontDatabase, QPainter


class WatermarkWorker(QThread):
    """處理PDF和圖片水印的工作線程"""
    progress = pyqtSignal(int)
    finished = pyqtSignal(bool, str)
    file_processed = pyqtSignal(str)
    
    def __init__(self, input_folder, output_folder, watermark_text, font_size, 
                 font_path, color, angle, opacity, density, file_type):
        super().__init__()
        self.input_folder = input_folder
        self.output_folder = output_folder
        self.watermark_text = watermark_text
        self.font_size = font_size
        self.font_path = font_path
        self.color = color
        self.angle = angle
        self.opacity = opacity
        self.density = density
        self.file_type = file_type  # 'pdf' 或 'image'
        self.canceled = False
    
    def run(self):
        try:
            if self.file_type == 'pdf':
                # 獲取所有PDF文件
                files = [f for f in os.listdir(self.input_folder) if f.lower().endswith('.pdf')]
            else:
                # 獲取所有圖片文件
                image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.webp', '.tif']
                files = [f for f in os.listdir(self.input_folder) 
                        if any(f.lower().endswith(ext) for ext in image_extensions)]
            
            total_files = len(files)
            
            if total_files == 0:
                self.finished.emit(False, f"未找到{self.file_type.upper()}文件")
                return
            
            # 處理每個文件
            for i, filename in enumerate(files):
                if self.canceled:
                    break
                
                input_path = os.path.join(self.input_folder, filename)
                output_path = os.path.join(self.output_folder, filename)
                
                # 添加水印 
                if self.file_type == 'pdf':
                    success = self.add_watermark_to_pdf(input_path, output_path)
                else:
                    success = self.add_watermark_to_image(input_path, output_path)
                
                if not success:
                    self.finished.emit(False, f"處理文件 {filename} 時出錯")
                    return
                
                # 發(fā)送文件處理完成信號
                self.file_processed.emit(filename)
                
                # 更新進度
                progress = int((i + 1) / total_files * 100)
                self.progress.emit(progress)
            
            self.finished.emit(not self.canceled, "處理完成" if not self.canceled else "已取消")
            
        except Exception as e:
            self.finished.emit(False, f"發(fā)生錯誤: {str(e)}")
    
    def cancel(self):
        self.canceled = True
    
    def add_watermark_to_pdf(self, input_path, output_path):
        try:
            # 讀取PDF
            reader = PdfReader(input_path)
            writer = PdfWriter()
            
            # 獲取PDF頁面尺寸
            first_page = reader.pages[0]
            page_width = float(first_page.mediabox.width)
            page_height = float(first_page.mediabox.height)
            
            # 創(chuàng)建水印
            watermark_pdf = self.create_watermark_pdf(page_width, page_height)
            
            # 為每一頁添加水印
            for page in reader.pages:
                # 合并水印
                page.merge_page(watermark_pdf.pages[0])
                writer.add_page(page)
            
            # 保存PDF
            with open(output_path, 'wb') as output_file:
                writer.write(output_file)
            
            return True
            
        except Exception as e:
            print(f"Error processing PDF {input_path}: {str(e)}")
            return False
    
    def add_watermark_to_image(self, input_path, output_path):
        try:
            # 打開圖片
            image = Image.open(input_path).convert('RGBA')
            
            # 創(chuàng)建水印圖層
            watermark_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
            draw = ImageDraw.Draw(watermark_layer)
            
            # 獲取字體
            font = self.get_pil_font()
            
            # 計算水印間距
            spacing_x = image.width / self.density
            spacing_y = image.height / self.density
            
            # 計算偏移量,使水印居中分布
            offset_x = spacing_x / 2
            offset_y = spacing_y / 2
            
            # 設(shè)置顏色和透明度
            rgba_color = (self.color.red(), self.color.green(), self.color.blue(), 
                         int(self.opacity * 255))
            
            # 在整個圖片上繪制水印
            for x in range(0, int(image.width // spacing_x) + 1):
                for y in range(0, int(image.height // spacing_y) + 1):
                    pos_x = offset_x + x * spacing_x
                    pos_y = offset_y + y * spacing_y
                    
                    # 創(chuàng)建文本圖像并旋轉(zhuǎn)
                    text_image = Image.new('RGBA', (image.width, image.height), (0, 0, 0, 0))
                    text_draw = ImageDraw.Draw(text_image)
                    
                    # 獲取文本尺寸
                    bbox = draw.textbbox((0, 0), self.watermark_text, font=font)
                    text_width = bbox[2] - bbox[0]
                    text_height = bbox[3] - bbox[1]
                    
                    # 繪制文本
                    text_draw.text((pos_x - text_width/2, pos_y - text_height/2), 
                                  self.watermark_text, font=font, fill=rgba_color)
                    
                    # 旋轉(zhuǎn)文本
                    rotated_text = text_image.rotate(self.angle, expand=False, center=(pos_x, pos_y))
                    
                    # 合并到水印圖層
                    watermark_layer = Image.alpha_composite(watermark_layer, rotated_text)
            
            # 合并水印到原圖
            watermarked_image = Image.alpha_composite(image, watermark_layer)
            
            # 保存圖片(根據(jù)原格式保存)
            if image.mode != 'RGB' and output_path.lower().endswith(('.jpg', '.jpeg')):
                watermarked_image = watermarked_image.convert('RGB')
            
            watermarked_image.save(output_path)
            
            return True
            
        except Exception as e:
            print(f"Error processing image {input_path}: {str(e)}")
            return False
    
    def get_pil_font(self):
        """獲取PIL字體對象"""
        try:
            if self.font_path and os.path.exists(self.font_path):
                return ImageFont.truetype(self.font_path, self.font_size)
            else:
                # 嘗試使用系統(tǒng)默認(rèn)字體
                try:
                    # Windows系統(tǒng)常見中文字體
                    windows_fonts = [
                        "C:/Windows/Fonts/simhei.ttf",  # 黑體
                        "C:/Windows/Fonts/simsun.ttc",  # 宋體
                        "C:/Windows/Fonts/msyh.ttc",    # 微軟雅黑
                    ]
                    
                    for font_path in windows_fonts:
                        if os.path.exists(font_path):
                            return ImageFont.truetype(font_path, self.font_size)
                    
                    # 如果找不到中文字體,使用默認(rèn)字體
                    return ImageFont.load_default()
                except:
                    return ImageFont.load_default()
        except:
            return ImageFont.load_default()
    
    def create_watermark_pdf(self, page_width, page_height):
        """創(chuàng)建水印PDF"""
        # 創(chuàng)建內(nèi)存中的PDF
        packet = io.BytesIO()
        can = canvas.Canvas(packet, pagesize=(page_width, page_height))
        
        # 設(shè)置顏色和透明度
        r, g, b = self.color.red()/255, self.color.green()/255, self.color.blue()/255
        can.setFillColor(Color(r, g, b, alpha=self.opacity))
        
        # 計算水印間距
        spacing_x = page_width / self.density
        spacing_y = page_height / self.density
        
        # 計算偏移量,使水印居中分布
        offset_x = spacing_x / 2
        offset_y = spacing_y / 2
        
        # 處理文本水印
        # 嘗試注冊字體
        try:
            if self.font_path and os.path.exists(self.font_path):
                # 注冊字體
                font_name = os.path.basename(self.font_path).split('.')[0]
                pdfmetrics.registerFont(TTFont(font_name, self.font_path))
                can.setFont(font_name, self.font_size)
            else:
                # 嘗試使用系統(tǒng)默認(rèn)中文字體
                try:
                    # Windows系統(tǒng)常見中文字體
                    windows_fonts = [
                        "C:/Windows/Fonts/simhei.ttf",  # 黑體
                        "C:/Windows/Fonts/simsun.ttc",  # 宋體
                        "C:/Windows/Fonts/msyh.ttc",    # 微軟雅黑
                    ]
                    
                    for font_path in windows_fonts:
                        if os.path.exists(font_path):
                            font_name = os.path.basename(font_path).split('.')[0]
                            pdfmetrics.registerFont(TTFont(font_name, font_path))
                            can.setFont(font_name, self.font_size)
                            break
                    else:
                        # 如果沒有找到中文字體,使用默認(rèn)字體
                        can.setFont("Helvetica", self.font_size)
                except:
                    can.setFont("Helvetica", self.font_size)
        except Exception as e:
            print(f"字體注冊失敗: {e}")
            can.setFont("Helvetica", self.font_size)
        
        # 在整個頁面上繪制文本水印
        for x in range(0, int(page_width // spacing_x) + 1):
            for y in range(0, int(page_height // spacing_y) + 1):
                pos_x = offset_x + x * spacing_x
                pos_y = offset_y + y * spacing_y
                
                can.saveState()
                can.translate(pos_x, pos_y)
                can.rotate(self.angle)
                can.drawCentredString(0, 0, self.watermark_text)
                can.restoreState()
        
        can.save()
        
        # 移動到數(shù)據(jù)開頭
        packet.seek(0)
        return PdfReader(packet)


class PDFWatermarkTool(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PDF和圖片全屏水印批量添加工具")
        self.setGeometry(100, 100, 1000, 800)
        
        # 初始化變量
        self.input_folder = ""
        self.output_folder = ""
        self.color = QColor(74, 144, 226)  # 默認(rèn)藍色
        self.selected_font_path = ""  # 存儲選定的字體文件路徑
        self.worker = None
        
        # 設(shè)置應(yīng)用程序樣式
        self.setup_styles()
        self.init_ui()
    
    def setup_styles(self):
        """設(shè)置應(yīng)用程序樣式"""
        self.setStyleSheet("""
            QMainWindow {
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #f8f9fa, stop: 1 #e9ecef);
                font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
            }
            
            QGroupBox {
                font-weight: bold;
                font-size: 12px;
                border: 2px solid #dee2e6;
                border-radius: 8px;
                margin-top: 1ex;
                padding-top: 10px;
                background: white;
            }
            
            QGroupBox::title {
                subcontrol-origin: margin;
                subcontrol-position: top center;
                padding: 0 8px;
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #6c757d, stop: 1 #495057);
                color: white;
                border-radius: 4px;
            }
            
            QPushButton {
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #6c757d, stop: 1 #495057);
                color: white;
                border: none;
                border-radius: 6px;
                padding: 8px 16px;
                font-weight: bold;
            }
            
            QPushButton:hover {
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #5a6268, stop: 1 #3d4348);
            }
            
            QPushButton:pressed {
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #495057, stop: 1 #343a40);
            }
            
            QPushButton:disabled {
                background: #adb5bd;
                color: #6c757d;
            }
            
            QLineEdit, QSpinBox, QDoubleSpinBox, QFontComboBox {
                border: 2px solid #ced4da;
                border-radius: 6px;
                padding: 6px;
                background: white;
                selection-background-color: #4a90e2;
            }
            
            QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QFontComboBox:focus {
                border-color: #4a90e2;
            }
            
            QSlider::groove:horizontal {
                border: 1px solid #ced4da;
                height: 8px;
                background: #e9ecef;
                border-radius: 4px;
            }
            
            QSlider::handle:horizontal {
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #4a90e2, stop: 1 #357abd);
                border: 1px solid #5c7cfa;
                width: 18px;
                margin: -2px 0;
                border-radius: 9px;
            }
            
            QProgressBar {
                border: 2px solid #dee2e6;
                border-radius: 6px;
                text-align: center;
                background: white;
            }
            
            QProgressBar::chunk {
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #20c997, stop: 1 #198754);
                border-radius: 4px;
            }
            
            QTextEdit {
                border: 2px solid #ced4da;
                border-radius: 6px;
                background: white;
                font-family: 'Consolas', 'Courier New', monospace;
            }
            
            QLabel {
                color: #2b2d42;
            }
        """)
    
    def init_ui(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        main_layout = QVBoxLayout(central_widget)
        main_layout.setSpacing(15)
        main_layout.setContentsMargins(20, 20, 20, 20)
        
        # 添加標(biāo)題和說明
        title_label = QLabel("??? PDF和圖片全屏水印批量添加工具")
        title_label.setStyleSheet("""
            font-size: 18px;
            font-weight: bold;
            color: #2b2d42;
            padding: 10px;
            background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                      stop: 0 #4a90e2, stop: 1 #357abd);
            border-radius: 8px;
            color: white;
        """)
        title_label.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(title_label)
        
        # 文件夾選擇部分
        folder_group = QGroupBox("?? 文件夾選擇")
        folder_layout = QGridLayout()
        folder_layout.setSpacing(10)
        
        self.input_label = QLabel("?? 未選擇輸入文件夾")
        self.input_label.setStyleSheet("""
            padding: 10px;
            background: #e9ecef;
            border-radius: 6px;
            border: 1px solid #ced4da;
            color: #495057;
        """)
        folder_layout.addWidget(self.input_label, 0, 0, 1, 2)
        
        self.input_btn = QPushButton("?? 選擇輸入文件夾")
        self.input_btn.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #4a90e2, stop: 1 #357abd);
                padding: 10px;
            }
        """)
        self.input_btn.clicked.connect(self.select_input_folder)
        folder_layout.addWidget(self.input_btn, 0, 2)
        
        self.output_label = QLabel("?? 未選擇輸出文件夾")
        self.output_label.setStyleSheet("""
            padding: 10px;
            background: #e9ecef;
            border-radius: 6px;
            border: 1px solid #ced4da;
            color: #495057;
        """)
        folder_layout.addWidget(self.output_label, 1, 0, 1, 2)
        
        self.output_btn = QPushButton("?? 選擇輸出文件夾")
        self.output_btn.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #4a90e2, stop: 1 #357abd);
                padding: 10px;
            }
        """)
        self.output_btn.clicked.connect(self.select_output_folder)
        folder_layout.addWidget(self.output_btn, 1, 2)
        
        folder_group.setLayout(folder_layout)
        main_layout.addWidget(folder_group)
        
        # 水印設(shè)置部分
        watermark_group = QGroupBox("?? 水印設(shè)置")
        watermark_layout = QGridLayout()
        watermark_layout.setSpacing(12)
        watermark_layout.setContentsMargins(15, 20, 15, 15)
        
        # 第一行:水印文字
        watermark_layout.addWidget(QLabel("?? 水印文字:"), 0, 0)
        self.watermark_text = QLineEdit("水印文字")
        self.watermark_text.setPlaceholderText("請輸入水印內(nèi)容...")
        watermark_layout.addWidget(self.watermark_text, 0, 1, 1, 2)
        
        # 第二行:字體選擇
        watermark_layout.addWidget(QLabel("?? 字體選擇:"), 1, 0)
        font_select_layout = QHBoxLayout()
        
        self.font_combo = QFontComboBox()
        font_select_layout.addWidget(self.font_combo)
        
        self.font_select_btn = QPushButton("?? 選擇字體文件")
        self.font_select_btn.setStyleSheet("padding: 6px;")
        self.font_select_btn.clicked.connect(self.select_font_file)
        font_select_layout.addWidget(self.font_select_btn)
        
        watermark_layout.addLayout(font_select_layout, 1, 1, 1, 2)
        
        # 字體路徑顯示
        self.font_path_label = QLabel("?? 默認(rèn)使用系統(tǒng)字體")
        self.font_path_label.setStyleSheet("""
            padding: 8px;
            background: #f8f9fa;
            border-radius: 4px;
            border: 1px dashed #ced4da;
            color: #6c757d;
            font-size: 10px;
        """)
        watermark_layout.addWidget(self.font_path_label, 2, 0, 1, 3)
        
        # 第三行:字體大小和顏色
        watermark_layout.addWidget(QLabel("?? 字體大小:"), 3, 0)
        self.font_size = QSpinBox()
        self.font_size.setRange(10, 200)
        self.font_size.setValue(30)
        self.font_size.setSuffix(" px")
        watermark_layout.addWidget(self.font_size, 3, 1)
        
        watermark_layout.addWidget(QLabel("?? 顏色:"), 4, 0)
        self.color_btn = QPushButton()
        self.color_btn.setFixedSize(80, 30)
        self.color_btn.clicked.connect(self.select_color)
        self.update_color_button()
        watermark_layout.addWidget(self.color_btn, 4, 1)
        
        # 第四行:角度和透明度
        watermark_layout.addWidget(QLabel("?? 角度:"), 5, 0)
        self.angle = QSpinBox()
        self.angle.setRange(0, 359)
        self.angle.setValue(45)
        self.angle.setSuffix("°")
        watermark_layout.addWidget(self.angle, 5, 1)
        
        watermark_layout.addWidget(QLabel("??? 透明度:"), 6, 0)
        opacity_layout = QHBoxLayout()
        self.opacity = QDoubleSpinBox()
        self.opacity.setRange(0.01, 1.0)
        self.opacity.setSingleStep(0.01)
        self.opacity.setValue(0.15)
        opacity_layout.addWidget(self.opacity)
        
        self.opacity_slider = QSlider(Qt.Horizontal)
        self.opacity_slider.setRange(1, 100)
        self.opacity_slider.setValue(15)
        self.opacity_slider.valueChanged.connect(self.on_opacity_slider_changed)
        self.opacity.valueChanged.connect(self.on_opacity_spinbox_changed)
        opacity_layout.addWidget(self.opacity_slider)
        watermark_layout.addLayout(opacity_layout, 6, 1)
        
        # 第五行:密度
        watermark_layout.addWidget(QLabel("?? 密度:"), 7, 0)
        density_layout = QHBoxLayout()
        self.density = QSpinBox()
        self.density.setRange(3, 20)
        self.density.setValue(5)
        self.density.setToolTip("數(shù)值越大,水印越密集")
        density_layout.addWidget(self.density)
        
        self.density_slider = QSlider(Qt.Horizontal)
        self.density_slider.setRange(3, 20)
        self.density_slider.setValue(5)
        self.density_slider.valueChanged.connect(self.on_density_slider_changed)
        self.density.valueChanged.connect(self.on_density_spinbox_changed)
        density_layout.addWidget(self.density_slider)
        watermark_layout.addLayout(density_layout, 7, 1)
        
        watermark_group.setLayout(watermark_layout)
        main_layout.addWidget(watermark_group)
        
        # 預(yù)覽和處理區(qū)域
        preview_process_layout = QHBoxLayout()
        
        # 預(yù)覽區(qū)域
        preview_group = QGroupBox("?? 預(yù)覽")
        preview_layout = QVBoxLayout()
        
        self.preview_label = QLabel("?? 水印預(yù)覽區(qū)域")
        self.preview_label.setAlignment(Qt.AlignCenter)
        self.preview_label.setStyleSheet("""
            padding: 30px;
            background: white;
            border: 2px dashed #4a90e2;
            border-radius: 8px;
            min-height: 120px;
            min-width: 200px;
            font-size: 16px;
            color: #495057;
        """)
        self.preview_label.setWordWrap(True)
        preview_layout.addWidget(self.preview_label)
        
        preview_group.setLayout(preview_layout)
        preview_process_layout.addWidget(preview_group, 1)
        
        # 進度區(qū)域
        progress_group = QGroupBox("?? 進度")
        progress_layout = QVBoxLayout()
        
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        self.progress_bar.setFormat("?? 處理中: %p%")
        progress_layout.addWidget(self.progress_bar)
        
        self.status_label = QLabel("? 準(zhǔn)備就緒")
        self.status_label.setStyleSheet("""
            padding: 12px;
            background: #e9ecef;
            border-radius: 6px;
            border: 1px solid #ced4da;
            color: #495057;
            font-weight: bold;
        """)
        progress_layout.addWidget(self.status_label)
        
        self.processed_files = QTextEdit()
        self.processed_files.setMaximumHeight(120)
        self.processed_files.setPlaceholderText("?? 已處理文件列表將顯示在這里...")
        progress_layout.addWidget(self.processed_files)
        
        progress_group.setLayout(progress_layout)
        preview_process_layout.addWidget(progress_group, 1)
        
        main_layout.addLayout(preview_process_layout)
        
        # 按鈕部分
        button_layout = QHBoxLayout()
        button_layout.setSpacing(15)
        
        self.start_pdf_btn = QPushButton("?? 開始添加PDF水印")
        self.start_pdf_btn.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #20c997, stop: 1 #198754);
                padding: 12px;
                font-size: 14px;
            }
        """)
        self.start_pdf_btn.clicked.connect(lambda: self.start_processing('pdf'))
        button_layout.addWidget(self.start_pdf_btn)
        
        self.start_image_btn = QPushButton("?? 開始添加圖片水印")
        self.start_image_btn.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #4a90e2, stop: 1 #357abd);
                padding: 12px;
                font-size: 14px;
            }
        """)
        self.start_image_btn.clicked.connect(lambda: self.start_processing('image'))
        button_layout.addWidget(self.start_image_btn)
        
        self.cancel_btn = QPushButton("? 取消")
        self.cancel_btn.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                          stop: 0 #dc3545, stop: 1 #c82333);
                padding: 12px;
                font-size: 14px;
            }
        """)
        self.cancel_btn.clicked.connect(self.cancel_processing)
        self.cancel_btn.setEnabled(False)
        button_layout.addWidget(self.cancel_btn)
        
        main_layout.addLayout(button_layout)
        
        # 底部信息欄
        footer_label = QLabel("?? 提示: 支持PDF、JPG、PNG、BMP、GIF、TIFF、WEBP等格式 | ??? 由白澤基于PyQt5和Python開發(fā)")
        footer_label.setStyleSheet("""
            padding: 10px;
            background: #495057;
            color: white;
            border-radius: 6px;
            font-size: 11px;
        """)
        footer_label.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(footer_label)
        
        # 連接信號以自動更新預(yù)覽
        self.watermark_text.textChanged.connect(self.update_preview)
        self.font_combo.currentFontChanged.connect(self.update_preview)
        self.font_size.valueChanged.connect(self.update_preview)
        self.color_btn.clicked.connect(self.update_preview)
        self.angle.valueChanged.connect(self.update_preview)
        self.opacity.valueChanged.connect(self.update_preview)
        
        # 初始化預(yù)覽
        self.update_preview()
    
    def select_font_file(self):
        """選擇字體文件"""
        font_file, _ = QFileDialog.getOpenFileName(
            self, "選擇字體文件", "", "字體文件 (*.ttf *.ttc *.otf)"
        )
        if font_file:
            self.selected_font_path = font_file
            self.font_path_label.setText(f"?? 已選擇: {os.path.basename(font_file)}")
            self.update_preview()
    
    def select_input_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "選擇輸入文件夾")
        if folder:
            self.input_folder = folder
            self.input_label.setText(f"?? 輸入文件夾: {folder}")
    
    def select_output_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "選擇輸出文件夾")
        if folder:
            self.output_folder = folder
            self.output_label.setText(f"?? 輸出文件夾: {folder}")
    
    def select_color(self):
        color = QColorDialog.getColor(self.color, self, "選擇水印顏色")
        if color.isValid():
            self.color = color
            self.update_color_button()
            self.update_preview()
    
    def update_color_button(self):
        # 更新顏色按鈕的背景色
        palette = self.color_btn.palette()
        palette.setColor(QPalette.Button, self.color)
        self.color_btn.setPalette(palette)
        self.color_btn.setAutoFillBackground(True)
        self.color_btn.setFlat(True)
        self.color_btn.setText("?? " + self.color.name())
    
    def update_preview(self):
        """更新水印預(yù)覽 - 使用QPixmap創(chuàng)建真實的水印效果"""
        text = self.watermark_text.text()
        if not text:
            self.preview_label.setText("?? 請輸入水印文字...")
            return
        
        # 創(chuàng)建預(yù)覽圖像
        pixmap = QPixmap(300, 150)
        pixmap.fill(Qt.white)
        
        painter = QPainter(pixmap)
        painter.setRenderHint(QPainter.Antialiasing)
        
        # 設(shè)置字體
        font = self.font_combo.currentFont()
        font.setPointSize(self.font_size.value())
        painter.setFont(font)
        
        # 設(shè)置顏色和透明度
        color = self.color
        color.setAlphaF(self.opacity.value())
        painter.setPen(QColor(color))
        
        # 平移和旋轉(zhuǎn)
        painter.translate(150, 75)  # 中心點
        painter.rotate(self.angle.value())
        
        # 繪制文本 - 使用整數(shù)坐標(biāo)
        text_rect = painter.fontMetrics().boundingRect(text)
        x_pos = int(-text_rect.width() / 2)
        y_pos = int(-text_rect.height() / 2)
        
        # 使用QRectF來繪制文本
        text_rect = QRectF(x_pos, y_pos, text_rect.width(), text_rect.height())
        painter.drawText(text_rect, Qt.AlignCenter, text)
        
        painter.end()
        
        # 設(shè)置預(yù)覽圖像
        self.preview_label.setPixmap(pixmap)
        self.preview_label.setText("")  # 清除文本
    
    def on_opacity_slider_changed(self, value):
        """當(dāng)透明度滑塊改變時更新數(shù)值"""
        self.opacity.setValue(value / 100.0)
        self.update_preview()
    
    def on_opacity_spinbox_changed(self, value):
        """當(dāng)透明度數(shù)值改變時更新滑塊"""
        self.opacity_slider.setValue(int(value * 100))
        self.update_preview()
    
    def on_density_slider_changed(self, value):
        """當(dāng)密度滑塊改變時更新數(shù)值"""
        self.density.setValue(value)
    
    def on_density_spinbox_changed(self, value):
        """當(dāng)密度數(shù)值改變時更新滑塊"""
        self.density_slider.setValue(value)
    
    def start_processing(self, file_type):
        # 驗證輸入
        if not self.input_folder or not self.output_folder:
            QMessageBox.warning(self, "?? 警告", "請先選擇輸入和輸出文件夾")
            return
        
        if not self.watermark_text.text().strip():
            QMessageBox.warning(self, "?? 警告", "請輸入水印文字")
            return
        
        # 檢查輸出文件夾是否與輸入文件夾相同
        if self.input_folder == self.output_folder:
            reply = QMessageBox.question(
                self, 
                "? 確認(rèn)", 
                "?? 輸入和輸出文件夾相同,這可能會覆蓋原始文件。是否繼續(xù)?",
                QMessageBox.Yes | QMessageBox.No,
                QMessageBox.No
            )
            if reply == QMessageBox.No:
                return
        
        # 禁用開始按鈕,啟用取消按鈕
        self.start_pdf_btn.setEnabled(False)
        self.start_image_btn.setEnabled(False)
        self.cancel_btn.setEnabled(True)
        self.progress_bar.setValue(0)
        self.status_label.setText(f"?? 正在處理{file_type.upper()}文件...")
        self.processed_files.clear()
        
        # 獲取字體路徑
        font_path = self.selected_font_path
        
        # 創(chuàng)建工作線程
        self.worker = WatermarkWorker(
            self.input_folder,
            self.output_folder,
            self.watermark_text.text(),
            self.font_size.value(),
            font_path,
            self.color,
            self.angle.value(),
            self.opacity.value(),
            self.density.value(),
            file_type
        )
        
        # 連接信號
        self.worker.progress.connect(self.update_progress)
        self.worker.finished.connect(self.processing_finished)
        self.worker.file_processed.connect(self.add_processed_file)
        
        # 啟動線程
        self.worker.start()
    
    def add_processed_file(self, filename):
        """添加已處理文件到列表"""
        current_text = self.processed_files.toPlainText()
        if current_text:
            current_text += "\n"
        current_text += f"? {filename}"
        self.processed_files.setPlainText(current_text)
        # 滾動到底部
        self.processed_files.verticalScrollBar().setValue(
            self.processed_files.verticalScrollBar().maximum()
        )
    
    def cancel_processing(self):
        if self.worker and self.worker.isRunning():
            self.worker.cancel()
            self.status_label.setText("?? 正在取消...")
            self.cancel_btn.setEnabled(False)
    
    def update_progress(self, value):
        self.progress_bar.setValue(value)
        if value == 100:
            self.progress_bar.setFormat("? 完成: %p%")
        else:
            self.progress_bar.setFormat(f"?? 處理中: {value}%")
    
    def processing_finished(self, success, message):
        self.start_pdf_btn.setEnabled(True)
        self.start_image_btn.setEnabled(True)
        self.cancel_btn.setEnabled(False)
        
        if success:
            self.status_label.setText(f"? {message}")
            self.progress_bar.setFormat("? 完成: 100%")
        else:
            self.status_label.setText(f"? {message}")
        
        if not success:
            QMessageBox.warning(self, "? 處理完成", message)
        else:
            QMessageBox.information(self, "? 處理完成", message)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    
    # 設(shè)置應(yīng)用程序圖標(biāo)和樣式
    app.setStyle('Fusion')
    
    window = PDFWatermarkTool()
    window.show()
    sys.exit(app.exec_())

項目結(jié)構(gòu)

pdf-watermark-tool/
├── main.py              # 主程序入口
├── requirements.txt     # 依賴庫列表
├── README.md           # 項目說明
├── fonts/              # 字體文件目錄
├── examples/           # 示例文件
└── tests/              # 測試文件

使用技巧和注意事項

最佳實踐

  • 水印文字選擇:使用簡潔有力的文字,如公司名稱或用戶名
  • 透明度設(shè)置:建議設(shè)置在0.1-0.3之間,既不影響內(nèi)容閱讀又能起到保護作用
  • 密度控制:根據(jù)文件類型調(diào)整密度,文檔類可以密一些,圖片類可以疏一些
  • 字體選擇:選擇清晰易讀的字體,避免使用過于花哨的字體

常見問題解決

  • 中文顯示亂碼:確保選擇了支持中文的字體文件
  • 處理速度慢:減少同時處理的文件數(shù)量或降低水印密度
  • 內(nèi)存不足:分批處理大型文件

總結(jié)

本文詳細(xì)介紹了一個基于PyQt5的全屏水印批量添加工具的開發(fā)和實現(xiàn)過程。通過這個項目,我們不僅學(xué)習(xí)到了:

  • PyQt5 GUI開發(fā):如何創(chuàng)建美觀實用的桌面應(yīng)用程序
  • 多線程編程:如何使用QThread實現(xiàn)后臺處理
  • PDF處理技術(shù):如何使用PyPDF2和ReportLab處理PDF文件
  • 圖像處理技術(shù):如何使用Pillow庫處理圖片水印
  • 字體處理:如何解決中文字體顯示問題

這個工具不僅實用性強,而且代碼結(jié)構(gòu)清晰,易于擴展和維護。讀者可以根據(jù)自己的需求進一步擴展功能,如添加圖片水印支持、增加水印模板功能等。

水印技術(shù)是數(shù)字內(nèi)容保護的重要手段,掌握這項技術(shù)對于開發(fā)者來說具有很高的實用價值。希望本文能夠幫助讀者深入理解水印技術(shù)的實現(xiàn)原理,并開發(fā)出更多有趣實用的應(yīng)用。

以上就是Python+PyQt5開發(fā)一個全屏水印批量添加工具的詳細(xì)內(nèi)容,更多關(guān)于Python水印添加的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • python實現(xiàn)將JSON文件中的數(shù)據(jù)格式化處理

    python實現(xiàn)將JSON文件中的數(shù)據(jù)格式化處理

    JSON是一種輕量級的數(shù)據(jù)交換格式,常用于Web服務(wù)間的數(shù)據(jù)傳輸,Python內(nèi)置了??json??模塊,能夠方便地進行JSON數(shù)據(jù)的解析與格式化,本文將通過具體的Python代碼實例,深入探討如何將JSON文件中的數(shù)據(jù)進行格式化處理,需要的朋友可以參考下
    2024-03-03
  • 修復(fù)CentOS7升級Python到3.6版本后yum不能正確使用的解決方法

    修復(fù)CentOS7升級Python到3.6版本后yum不能正確使用的解決方法

    這篇文章主要介紹了修復(fù)CentOS7升級Python到3.6版本后yum不能使用的問題,本文給大家?guī)砹私鉀Q方法,需要的朋友可以參考下
    2018-01-01
  • Pycharm關(guān)閉控制臺多余窗口的解決辦法

    Pycharm關(guān)閉控制臺多余窗口的解決辦法

    這篇文章主要介紹了Pycharm關(guān)閉控制臺多余窗口的解決辦法,文中通過圖文結(jié)合的方式講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-12-12
  • Django框架中視圖的用法

    Django框架中視圖的用法

    這篇文章介紹了Django框架中視圖的用法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-06-06
  • 使用openCV去除文字中亂入的線條實例

    使用openCV去除文字中亂入的線條實例

    這篇文章主要介紹了使用openCV去除文字中亂入的線條實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-06-06
  • Python模擬登陸網(wǎng)頁的三種方法小結(jié)

    Python模擬登陸網(wǎng)頁的三種方法小結(jié)

    如何使用Python模擬登陸網(wǎng)頁,尤其是在涉及到復(fù)雜的認(rèn)證機制時?這篇文章將詳細(xì)介紹Python模擬登陸網(wǎng)頁的三種方法,以及如何繞過一些常見的安全防護措施,需要的朋友可以參考下
    2024-01-01
  • 使用Anaconda創(chuàng)建Pytorch虛擬環(huán)境的排坑詳細(xì)教程

    使用Anaconda創(chuàng)建Pytorch虛擬環(huán)境的排坑詳細(xì)教程

    PyTorch是一個開源的Python機器學(xué)習(xí)庫,基于Torch,用于自然語言處理等應(yīng)用程序,下面這篇文章主要給大家介紹了關(guān)于使用Anaconda創(chuàng)建Pytorch虛擬環(huán)境的相關(guān)資料,需要的朋友可以參考下
    2022-12-12
  • (手寫)PCA原理及其Python實現(xiàn)圖文詳解

    (手寫)PCA原理及其Python實現(xiàn)圖文詳解

    這篇文章主要介紹了Python來PCA算法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望能給你帶來幫助
    2021-08-08
  • Python多線程即相關(guān)理念詳解

    Python多線程即相關(guān)理念詳解

    這篇文章主要為大家介紹了Python多線程即相關(guān)理念,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-11-11
  • 基于pytorch的RNN實現(xiàn)字符級姓氏文本分類的示例代碼

    基于pytorch的RNN實現(xiàn)字符級姓氏文本分類的示例代碼

    當(dāng)使用基于PyTorch的RNN實現(xiàn)字符級姓氏文本分類時,我們可以使用一個非常簡單的RNN模型來處理輸入的字符序列,并將其應(yīng)用于姓氏分類任務(wù),本文給大家舉了一個基本的示例代碼,需要的朋友可以參考下
    2023-12-12

最新評論

太保市| 辽中县| 清水县| 木里| 西和县| 大同市| 灯塔市| 犍为县| 宽甸| 利川市| 化州市| 新乡市| 中宁县| 永吉县| 灵寿县| 泸水县| 永顺县| 宝丰县| 黄陵县| 宁陕县| 南充市| 余江县| 凌海市| 定安县| 昌图县| 贡嘎县| 奈曼旗| 都匀市| 昌图县| 潞西市| 黄浦区| 普格县| 航空| 綦江县| 句容市| 神木县| 大港区| 资溪县| 南郑县| 皋兰县| 大安市|