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

基于Python實現(xiàn)一個圖片水印批量添加工具

 更新時間:2025年10月27日 08:55:13   作者:xcLeigh  
本文詳細介紹了使用 Python開發(fā) 給圖片加水印的工具,該工具基于 Pillow 和 tkinter 庫構(gòu)建,可解決單圖處理耗時、專業(yè)軟件操作復(fù)雜的問題,文中提供完整可運行代碼,并給出參數(shù)校驗、字體兼容、常見報錯解決等實用內(nèi)容,需要的朋友可以參考下

平時處理圖片時,你是不是總遇到這樣的麻煩:想給一批圖片加水印,單張?zhí)幚硖M時間,用專業(yè)軟件又覺得小題大做?今天就帶大家親手做一個實用的Python小工具,既能選單個圖片加水印,也能批量處理整個文件夾的圖片,小白跟著步驟走也能搞定!

一、工具核心功能與準備工作

先明確下這個工具能幫我們解決什么問題,以及動手前需要準備哪些東西。

1. 核心功能

咱們做的這個工具,主打“靈活”和“高效”,具體能實現(xiàn)這3個功能:

  • 單圖處理:選一張圖片,手動調(diào)整水印位置、透明度,預(yù)覽效果后再保存
  • 批量處理:選一個文件夾,一鍵給里面所有圖片(支持jpg、png、jpeg格式)加統(tǒng)一水印
  • 自定義設(shè)置:自己改水印文字、字體大小、顏色,還能調(diào)水印的透明度,避免遮擋圖片內(nèi)容

2. 環(huán)境準備

做這個工具不用復(fù)雜的環(huán)境,只要裝2個Python庫就行,新手直接按步驟來:

  1. 先確認電腦裝了Python(建議3.7及以上版本,沒裝的話去官網(wǎng)下載,記得勾“Add Python to PATH”)
  2. 打開命令提示符(Windows按Win+R,輸cmd;Mac打開終端),輸入下面兩行命令,安裝需要的庫:
    • 安裝處理圖片的庫:pip install pillow
    • 安裝做圖形界面的庫(讓工具更直觀):pip install tkinter
      (注:tkinter在Python3里通常是自帶的,要是安裝報錯,直接跳過這步試試,大概率能正常用)

二、代碼拆解與實現(xiàn)(附完整代碼)

我把整個工具的代碼分成了3個部分,每部分都標了注釋,大家可以跟著理解,也能直接復(fù)制用。

1. 導(dǎo)入需要的庫

先把后面要用到的工具包導(dǎo)進來,就像做飯前把調(diào)料準備好一樣:

from PIL import Image, ImageDraw, ImageFont  # 處理圖片和添加文字水印
import tkinter as tk  # 做圖形界面
from tkinter import filedialog, messagebox, ttk  # 界面里的文件選擇、提示框等組件
import os  # 處理文件夾和文件路徑

2. 核心功能函數(shù)(加水印的關(guān)鍵)

這部分是工具的“心臟”,負責(zé)實現(xiàn)圖片讀取、水印添加、保存等核心操作,我拆成了3個函數(shù),每個函數(shù)干一件事,邏輯更清楚:

(1)單個圖片加水印函數(shù)

def add_watermark_to_single_image():
    # 1. 讓用戶選擇要處理的單個圖片
    img_path = filedialog.askopenfilename(
        title="選一張要加水印的圖片",
        filetypes=[("圖片文件", "*.jpg;*.png;*.jpeg"), ("所有文件", "*.*")]
    )
    if not img_path:  # 要是用戶沒選圖片,直接退出函數(shù)
        return

    # 2. 獲取用戶設(shè)置的水印參數(shù)(文字、大小、顏色、透明度)
    watermark_text = entry_text.get().strip()
    if not watermark_text:  # 沒填水印文字的話,彈出提示
        messagebox.showwarning("提示", "請先輸入水印文字哦!")
        return

    try:
        font_size = int(entry_font_size.get().strip())
        opacity = int(entry_opacity.get().strip())
        # 簡單判斷參數(shù)是否合理,避免出錯
        if font_size <= 0 or opacity < 0 or opacity > 100:
            raise ValueError
    except ValueError:
        messagebox.showerror("錯誤", "字體大小要填正整數(shù),透明度要在0-100之間哦!")
        return

    # 3. 讀取圖片,處理PNG透明格式(避免透明圖片加水印后背景變黑色)
    img = Image.open(img_path).convert("RGBA")
    # 創(chuàng)建一個和圖片一樣大的透明圖層,用來放水印
    watermark_layer = Image.new("RGBA", img.size, (255, 255, 255, 0))
    draw = ImageDraw.Draw(watermark_layer)

    # 4. 加載字體(這里用系統(tǒng)默認字體,Windows和Mac路徑不一樣,做了兼容)
    try:
        if os.name == "nt":  # Windows系統(tǒng)
            font = ImageFont.truetype("arial.ttf", font_size)
        else:  # Mac或Linux系統(tǒng)
            font = ImageFont.truetype("/Library/Fonts/Arial.ttf", font_size)
    except IOError:
        # 要是沒找到Arial字體,就用默認字體,雖然丑點但不影響用
        font = ImageFont.load_default(size=font_size)

    # 5. 計算水印位置(默認放在右下角,距離邊緣20像素,也可以自己改這里的數(shù)值)
    text_width, text_height = draw.textbbox((0, 0), watermark_text, font=font)[2:]
    img_width, img_height = img.size
    x = img_width - text_width - 20  # 右邊距20
    y = img_height - text_height - 20  # 下邊距20

    # 6. 添加水?。ㄌ幚硗该鞫龋?
    # 把用戶輸入的0-100透明度,轉(zhuǎn)成PIL需要的0-255范圍
    alpha = int(255 * (opacity / 100))
    # 這里默認水印是黑色,想改顏色的話,把(0,0,0,alpha)里的前三個數(shù)換成RGB值
    draw.text((x, y), watermark_text, font=font, fill=(0, 0, 0, alpha))

    # 7. 合并圖片和水印圖層,保存結(jié)果
    result = Image.alpha_composite(img, watermark_layer)
    # 處理PNG轉(zhuǎn)JPG的情況(JPG不支持透明,要加白色背景)
    if img_path.lower().endswith(('.jpg', '.jpeg')):
        result = result.convert("RGB")
    
    # 讓用戶選保存路徑
    save_path = filedialog.asksaveasfilename(
        defaultextension=os.path.splitext(img_path)[1],
        filetypes=[("圖片文件", "*.jpg;*.png;*.jpeg"), ("所有文件", "*.*")],
        title="保存加水印后的圖片"
    )
    if save_path:
        result.save(save_path)
        messagebox.showinfo("成功", f"圖片已保存到:\n{save_path}")

(2)批量處理文件夾圖片函數(shù)

批量處理和單圖邏輯差不多,主要多了“遍歷文件夾”的步驟:

def batch_add_watermark():
    # 1. 讓用戶選要批量處理的文件夾
    folder_path = filedialog.askdirectory(title="選要批量加水印的文件夾")
    if not folder_path:
        return

    # 2. 獲取用戶設(shè)置的水印參數(shù)(和單圖函數(shù)一樣,避免重復(fù)寫代碼)
    watermark_text = entry_text.get().strip()
    if not watermark_text:
        messagebox.showwarning("提示", "請先輸入水印文字哦!")
        return

    try:
        font_size = int(entry_font_size.get().strip())
        opacity = int(entry_opacity.get().strip())
        if font_size <= 0 or opacity < 0 or opacity > 100:
            raise ValueError
    except ValueError:
        messagebox.showerror("錯誤", "字體大小要填正整數(shù),透明度要在0-100之間哦!")
        return

    # 3. 遍歷文件夾里的所有文件,只處理圖片
    # 先定義支持的圖片格式,避免處理非圖片文件
    supported_formats = ('.jpg', '.jpeg', '.png')
    # 統(tǒng)計成功處理的圖片數(shù)量
    success_count = 0

    # 加載字體(和單圖函數(shù)一樣,做了系統(tǒng)兼容)
    try:
        if os.name == "nt":
            font = ImageFont.truetype("arial.ttf", font_size)
        else:
            font = ImageFont.truetype("/Library/Fonts/Arial.ttf", font_size)
    except IOError:
        font = ImageFont.load_default(size=font_size)

    # 4. 逐個處理圖片
    for filename in os.listdir(folder_path):
        # 只處理支持格式的文件
        if filename.lower().endswith(supported_formats):
            img_path = os.path.join(folder_path, filename)
            try:
                # 讀取圖片,和單圖處理邏輯一致
                img = Image.open(img_path).convert("RGBA")
                watermark_layer = Image.new("RGBA", img.size, (255, 255, 255, 0))
                draw = ImageDraw.Draw(watermark_layer)

                # 計算水印位置(同樣默認右下角)
                text_width, text_height = draw.textbbox((0, 0), watermark_text, font=font)[2:]
                img_width, img_height = img.size
                x = img_width - text_width - 20
                y = img_height - text_height - 20

                # 添加水印
                alpha = int(255 * (opacity / 100))
                draw.text((x, y), watermark_text, font=font, fill=(0, 0, 0, alpha))

                # 合并圖層,保存圖片
                result = Image.alpha_composite(img, watermark_layer)
                if filename.lower().endswith(('.jpg', '.jpeg')):
                    result = result.convert("RGB")
                
                # 保存路徑:在原文件夾下加“_watermarked”后綴
                name, ext = os.path.splitext(filename)
                save_path = os.path.join(folder_path, f"{name}_watermarked{ext}")
                result.save(save_path)
                success_count += 1
            except Exception as e:
                # 遇到錯誤不崩潰,只是提示哪個文件處理失敗
                messagebox.showerror("處理失敗", f"文件 {filename} 處理出錯:\n{str(e)}")

    # 批量處理結(jié)束后,提示結(jié)果
    messagebox.showinfo("批量處理完成", f"總共處理了 {success_count} 張圖片\n結(jié)果保存在原文件夾,文件名帶“_watermarked”后綴")

(3)創(chuàng)建圖形界面函數(shù)

有了核心功能,再做個簡單的界面,不用記命令,點鼠標就能操作:

def create_gui():
    # 1. 初始化窗口
    root = tk.Tk()
    root.title("Python圖片水印工具")
    root.geometry("500x300")  # 窗口大小,也可以自己改
    root.resizable(True, True)  # 允許拉伸窗口

    # 2. 創(chuàng)建標簽和輸入框(按網(wǎng)格布局,整齊好看)
    # 水印文字設(shè)置
    ttk.Label(root, text="水印文字:").grid(row=0, column=0, padx=10, pady=15, sticky="w")
    global entry_text
    entry_text = ttk.Entry(root, width=40)
    entry_text.grid(row=0, column=1, padx=10, pady=15)
    entry_text.insert(0, "我的圖片")  # 默認文字,可修改

    # 字體大小設(shè)置
    ttk.Label(root, text="字體大?。?).grid(row=1, column=0, padx=10, pady=5, sticky="w")
    global entry_font_size
    entry_font_size = ttk.Entry(root, width=40)
    entry_font_size.grid(row=1, column=1, padx=10, pady=5)
    entry_font_size.insert(0, "20")  # 默認20號字,可修改

    # 透明度設(shè)置
    ttk.Label(root, text="水印透明度(0-100):").grid(row=2, column=0, padx=10, pady=5, sticky="w")
    global entry_opacity
    entry_opacity = ttk.Entry(root, width=40)
    entry_opacity.grid(row=2, column=1, padx=10, pady=5)
    entry_opacity.insert(0, "50")  # 默認半透明,可修改

    # 3. 創(chuàng)建功能按鈕
    # 單圖處理按鈕
    btn_single = ttk.Button(root, text="處理單個圖片", command=add_watermark_to_single_image)
    btn_single.grid(row=3, column=0, padx=20, pady=20, sticky="ew")

    # 批量處理按鈕
    btn_batch = ttk.Button(root, text="批量處理文件夾", command=batch_add_watermark)
    btn_batch.grid(row=3, column=1, padx=20, pady=20, sticky="ew")

    # 4. 運行窗口
    root.mainloop()

3. 主程序入口

最后加一句代碼,讓腳本運行時自動打開界面:

if __name__ == "__main__":
    create_gui()

三、升級版圖片水印工具:支持多參數(shù)自定義配置

在前一版工具的基礎(chǔ)上,我們新增了文字顏色選擇、水印位置自由切換、重復(fù)水印次數(shù)設(shè)置等功能,讓水印效果更靈活可控。以下是完整的升級版代碼,包含所有參數(shù)配置功能。

3.1 功能升級說明

  • 新增文字顏色選擇:支持通過顏色選擇器自定義水印文字顏色
  • 水印位置可選:提供9個常用位置(如左上角、中心、右下角等)
  • 重復(fù)水印次數(shù):可設(shè)置水印在圖片中重復(fù)出現(xiàn)的行數(shù)和列數(shù)(如3x3網(wǎng)格分布)
  • 保留原功能:兼容單圖/批量處理、字體大小/透明度調(diào)整

3.2 完整代碼實現(xiàn)(含參數(shù)配置)

from PIL import Image, ImageDraw, ImageFont
import tkinter as tk
from tkinter import filedialog, messagebox, ttk, colorchooser
import os
import math


class WatermarkTool:
    def __init__(self, root):
        # 初始化主窗口
        self.root = root
        self.root.title("圖片水印批量工具")
        self.root.geometry("700x550")
        self.root.resizable(True, True)
        self.root.configure(bg="#f0f0f0")  # 淺灰背景

        # 初始化參數(shù)
        self.current_color = (0, 0, 0)  # 默認黑色
        self.fullscreen_mode = False  # 全屏水印模式開關(guān)

        # 設(shè)置全局樣式
        self.setup_styles()

        # 創(chuàng)建界面
        self.create_widgets()

    def setup_styles(self):
        """配置ttk樣式,統(tǒng)一美化界面"""
        style = ttk.Style()
        style.theme_use("clam")  # 使用clam主題增強跨平臺一致性

        # 標題樣式
        style.configure("Title.TLabel", 
                       font=("微軟雅黑", 12, "bold"),
                       background="#f0f0f0",
                       foreground="#333333")

        # 標簽樣式
        style.configure("Label.TLabel",
                       font=("微軟雅黑", 10),
                       background="#f0f0f0",
                       foreground="#555555",
                       padding=5)

        # 輸入框樣式
        style.configure("TEntry",
                       font=("微軟雅黑", 10),
                       padding=5,
                       fieldbackground="#ffffff",
                       borderwidth=1,
                       focusthickness=2,
                       focuscolor="#4a90d9")

        # 按鈕樣式
        style.configure("TButton",
                       font=("微軟雅黑", 10, "bold"),
                       padding=8,
                       background="#4a90d9",
                       foreground="#ffffff")
        style.map("TButton",
                 background=[("active", "#357abd"), ("pressed", "#2a6099")])

        # 顏色預(yù)覽樣式(不依賴height,用padding控制大小)
        self.color_style = ttk.Style()
        self.color_style.configure("Color.TLabel", 
                                  background="#000000",  # 默認黑色
                                  borderwidth=1,
                                  relief="solid",
                                  padding=(10, 5))  # 用內(nèi)邊距控制寬高(水平10,垂直5)

        # 分組框標題樣式(解決-font錯誤)
        style.configure("TLabelFrame.Label",
                       font=("微軟雅黑", 10, "bold"),
                       foreground="#333333")

    def create_widgets(self):
        """創(chuàng)建界面組件,采用卡片式布局"""
        # 主容器(帶邊距)
        main_frame = ttk.Frame(self.root, padding=20)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 參數(shù)配置卡片(白色背景,陰影效果)
        config_card = ttk.LabelFrame(main_frame, text="水印參數(shù)配置", padding=15)
        config_card.pack(fill=tk.X, pady=(0, 20))

        # 1. 水印文字
        ttk.Label(config_card, text="水印文字:", style="Label.TLabel").grid(
            row=0, column=0, padx=10, pady=10, sticky="w")
        self.entry_text = ttk.Entry(config_card, width=50)
        self.entry_text.grid(row=0, column=1, columnspan=3, padx=10, pady=10, sticky="ew")
        self.entry_text.insert(0, "我的圖片")

        # 2. 字體大小 + 透明度(橫向排列)
        ttk.Label(config_card, text="字體大小:", style="Label.TLabel").grid(
            row=1, column=0, padx=10, pady=10, sticky="w")
        self.entry_font_size = ttk.Entry(config_card, width=10)
        self.entry_font_size.grid(row=1, column=1, padx=(10, 30), pady=10, sticky="w")
        self.entry_font_size.insert(0, "20")

        ttk.Label(config_card, text="透明度(0-100):", style="Label.TLabel").grid(
            row=1, column=2, padx=10, pady=10, sticky="w")
        self.entry_opacity = ttk.Entry(config_card, width=10)
        self.entry_opacity.grid(row=1, column=3, padx=10, pady=10, sticky="w")
        self.entry_opacity.insert(0, "50")

        # 3. 文字顏色選擇(修復(fù)-height錯誤:用padding控制預(yù)覽框大?。?
        ttk.Label(config_card, text="文字顏色:", style="Label.TLabel").grid(
            row=2, column=0, padx=10, pady=10, sticky="w")
        color_frame = ttk.Frame(config_card)
        color_frame.grid(row=2, column=1, columnspan=3, padx=10, pady=10, sticky="w")
        
        # 移除-height參數(shù),依賴樣式中的padding控制大小
        self.color_label = ttk.Label(color_frame, style="Color.TLabel")
        self.color_label.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(color_frame, text="選擇顏色", command=self.choose_color).pack(side=tk.LEFT)

        # 4. 水印位置選擇
        ttk.Label(config_card, text="水印位置:", style="Label.TLabel").grid(
            row=3, column=0, padx=10, pady=10, sticky="w")
        self.position_var = tk.StringVar(value="右下")
        positions = ["左上", "中上", "右上", "左中", "中心", "右中", "左下", "中下", "右下"]
        self.position_combo = ttk.Combobox(
            config_card, textvariable=self.position_var, values=positions, width=10, state="readonly"
        )
        self.position_combo.grid(row=3, column=1, padx=10, pady=10, sticky="w")

        # 5. 重復(fù)分布(行數(shù)+列數(shù))
        ttk.Label(config_card, text="重復(fù)分布:", style="Label.TLabel").grid(
            row=4, column=0, padx=10, pady=10, sticky="w")
        
        ttk.Label(config_card, text="行數(shù):", style="Label.TLabel").grid(
            row=4, column=1, padx=(10, 5), pady=10, sticky="e")
        self.entry_rows = ttk.Entry(config_card, width=5)
        self.entry_rows.grid(row=4, column=2, padx=(0, 20), pady=10, sticky="w")
        self.entry_rows.insert(0, "1")

        ttk.Label(config_card, text="列數(shù):", style="Label.TLabel").grid(
            row=4, column=2, padx=(20, 5), pady=10, sticky="e")
        self.entry_cols = ttk.Entry(config_card, width=5)
        self.entry_cols.grid(row=4, column=3, padx=10, pady=10, sticky="w")
        self.entry_cols.insert(0, "1")

        # 6. 全屏水印模式(新增功能)
        fullscreen_frame = ttk.Frame(config_card)
        fullscreen_frame.grid(row=5, column=0, columnspan=4, padx=10, pady=10, sticky="w")
        
        self.fullscreen_check = ttk.Checkbutton(
            fullscreen_frame, text="全屏水印模式(忽略位置和行列設(shè)置)",
            command=self.toggle_fullscreen_mode
        )
        self.fullscreen_check.pack(side=tk.LEFT)

        # 7. 全屏水印參數(shù)(僅在全屏模式下啟用)
        self.fullscreen_params_frame = ttk.Frame(config_card)
        self.fullscreen_params_frame.grid(row=6, column=0, columnspan=4, padx=10, pady=5, sticky="w")
        self.disable_frame(self.fullscreen_params_frame)  # 默認禁用(自定義禁用方法)

        ttk.Label(self.fullscreen_params_frame, text="水印旋轉(zhuǎn)角度:", style="Label.TLabel").pack(side=tk.LEFT, padx=5)
        self.entry_rotation = ttk.Entry(self.fullscreen_params_frame, width=5)
        self.entry_rotation.pack(side=tk.LEFT, padx=5)
        self.entry_rotation.insert(0, "30")  # 默認旋轉(zhuǎn)30度

        ttk.Label(self.fullscreen_params_frame, text="水平間距(像素):", style="Label.TLabel").pack(side=tk.LEFT, padx=5)
        self.entry_h_spacing = ttk.Entry(self.fullscreen_params_frame, width=5)
        self.entry_h_spacing.pack(side=tk.LEFT, padx=5)
        self.entry_h_spacing.insert(0, "100")

        ttk.Label(self.fullscreen_params_frame, text="垂直間距(像素):", style="Label.TLabel").pack(side=tk.LEFT, padx=5)
        self.entry_v_spacing = ttk.Entry(self.fullscreen_params_frame, width=5)
        self.entry_v_spacing.pack(side=tk.LEFT, padx=5)
        self.entry_v_spacing.insert(0, "80")

        # 按鈕區(qū)域(底部居中)
        btn_frame = ttk.Frame(main_frame)
        btn_frame.pack(fill=tk.X, pady=10)

        ttk.Button(
            btn_frame, text="處理單個圖片", command=self.process_single_image, width=20
        ).pack(side=tk.LEFT, padx=(0, 30), pady=10, anchor=tk.CENTER)

        ttk.Button(
            btn_frame, text="批量處理文件夾", command=self.batch_process_images, width=20
        ).pack(side=tk.LEFT, padx=30, pady=10, anchor=tk.CENTER)

        # 狀態(tài)標簽(底部顯示提示信息)
        self.status_label = ttk.Label(
            main_frame, text="請配置參數(shù)后選擇圖片處理", style="Label.TLabel", anchor=tk.CENTER
        )
        self.status_label.pack(fill=tk.X, pady=10)

        # 讓列自適應(yīng)寬度
        config_card.columnconfigure(1, weight=1)
        config_card.columnconfigure(3, weight=1)

    def disable_frame(self, frame):
        """自定義禁用框架內(nèi)所有組件的方法(替代state=disabled)"""
        for widget in frame.winfo_children():
            widget.config(state="disabled")

    def enable_frame(self, frame):
        """自定義啟用框架內(nèi)所有組件的方法"""
        for widget in frame.winfo_children():
            widget.config(state="normal")

    def toggle_fullscreen_mode(self):
        """切換全屏水印模式,啟用/禁用相關(guān)參數(shù)"""
        self.fullscreen_mode = not self.fullscreen_mode
        if self.fullscreen_mode:
            self.enable_frame(self.fullscreen_params_frame)
            self.update_status("已啟用全屏水印模式")
        else:
            self.disable_frame(self.fullscreen_params_frame)
            self.update_status("已禁用全屏水印模式")

    def choose_color(self):
        """選擇水印顏色并更新預(yù)覽"""
        color = colorchooser.askcolor(title="選擇水印顏色")[0]
        if color:
            self.current_color = (int(color[0]), int(color[1]), int(color[2]))
            hex_color = f"#{int(color[0]):02x}{int(color[1]):02x}{int(color[2]):02x}"
            self.color_style.configure("Color.TLabel", background=hex_color)
            self.update_status(f"已選擇顏色:{hex_color}")

    def get_watermark_params(self):
        """獲取并校驗水印參數(shù)"""
        watermark_text = self.entry_text.get().strip()
        if not watermark_text:
            messagebox.showwarning("提示", "請輸入水印文字")
            return None

        try:
            font_size = int(self.entry_font_size.get().strip())
            opacity = int(self.entry_opacity.get().strip())
            rows = int(self.entry_rows.get().strip())
            cols = int(self.entry_cols.get().strip())
            
            if not (font_size > 0 and 0 <= opacity <= 100 and rows > 0 and cols > 0):
                raise ValueError
        except ValueError:
            messagebox.showerror("參數(shù)錯誤", "請檢查參數(shù):\n- 字體大?。赫麛?shù)\n- 透明度:0-100\n- 行數(shù)/列數(shù):正整數(shù)")
            return None

        # 全屏模式參數(shù)校驗
        fullscreen_params = None
        if self.fullscreen_mode:
            try:
                rotation = int(self.entry_rotation.get().strip())
                h_spacing = int(self.entry_h_spacing.get().strip())
                v_spacing = int(self.entry_v_spacing.get().strip())
                if not (h_spacing > 0 and v_spacing > 0):
                    raise ValueError
                fullscreen_params = (rotation, h_spacing, v_spacing)
            except ValueError:
                messagebox.showerror("參數(shù)錯誤", "全屏模式參數(shù)需為正整數(shù)")
                return None

        return (
            watermark_text, font_size, opacity,
            self.current_color, self.position_var.get(),
            rows, cols, fullscreen_params
        )

    def load_font(self, size):
        """加載字體(兼容中文)"""
        try:
            if os.name == "nt":  # Windows系統(tǒng)
                return ImageFont.truetype("simsun.ttc", size)  # 宋體支持中文
            else:  # Mac/Linux
                return ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", size)  # 蘋方字體
        except IOError:
            self.update_status("未找到指定字體,使用默認字體")
            return ImageFont.load_default(size=size)

    def calculate_position(self, position, img_w, img_h, text_w, text_h):
        """計算水印位置坐標"""
        margin = 20  # 邊緣距離
        pos_map = {
            "左上": (margin, margin),
            "中上": ((img_w - text_w) // 2, margin),
            "右上": (img_w - text_w - margin, margin),
            "左中": (margin, (img_h - text_h) // 2),
            "中心": ((img_w - text_w) // 2, (img_h - text_h) // 2),
            "右中": (img_w - text_w - margin, (img_h - text_h) // 2),
            "左下": (margin, img_h - text_h - margin),
            "中下": ((img_w - text_w) // 2, img_h - text_h - margin),
            "右下": (img_w - text_w - margin, img_h - text_h - margin)
        }
        return pos_map.get(position, (margin, margin))  # 默認左上

    def draw_fullscreen_watermark(self, draw, watermark_text, font, img_width, img_height, fill_color):
        """繪制全屏水?。▋?yōu)化旋轉(zhuǎn)邏輯)"""
        # 獲取全屏模式參數(shù)
        params = self.get_watermark_params()
        if not params:
            return
        fullscreen_params = params[-1]
        rotation, h_spacing, v_spacing = fullscreen_params
        
        # 計算文字基礎(chǔ)尺寸
        text_bbox = draw.textbbox((0, 0), watermark_text, font=font)
        text_width = text_bbox[2] - text_bbox[0]
        text_height = text_bbox[3] - text_bbox[1]
        
        # 計算網(wǎng)格覆蓋范圍(避免邊緣空白)
        grid_width = img_width + text_width * 2
        grid_height = img_height + text_height * 2
        
        # 遍歷網(wǎng)格繪制旋轉(zhuǎn)水印
        for x in range(-text_width, grid_width, h_spacing):
            for y in range(-text_height, grid_height, v_spacing):
                # 創(chuàng)建臨時圖層(尺寸足夠容納旋轉(zhuǎn)后的文字)
                temp_size = max(text_width, text_height) * 2
                temp_layer = Image.new("RGBA", (temp_size, temp_size), (255, 255, 255, 0))
                temp_draw = ImageDraw.Draw(temp_layer)
                
                # 在臨時圖層中心繪制文字
                temp_draw.text(
                    (temp_size//2 - text_width//2, temp_size//2 - text_height//2),
                    watermark_text,
                    font=font,
                    fill=fill_color
                )
                
                # 旋轉(zhuǎn)臨時圖層(expand=True確保完整顯示)
                rotated_layer = temp_layer.rotate(rotation, expand=True)
                rotated_w, rotated_h = rotated_layer.size
                
                # 計算最終繪制位置(文字中心對齊網(wǎng)格點)
                draw_x = x - rotated_w // 2
                draw_y = y - rotated_h // 2
                
                # 繪制旋轉(zhuǎn)后的水?。ㄓ胮aste替代bitmap,避免透明度問題)
                draw._image.paste(rotated_layer, (draw_x, draw_y), rotated_layer)

    def process_single_image(self):
        """處理單個圖片"""
        img_path = filedialog.askopenfilename(
            title="選擇需要加水印的圖片",
            filetypes=[("圖片文件", "*.jpg;*.png;*.jpeg"), ("所有文件", "*.*")]
        )
        if not img_path:
            self.update_status("已取消選擇圖片")
            return

        # 獲取參數(shù)
        params = self.get_watermark_params()
        if not params:
            return
        watermark_text, font_size, opacity, color, position, rows, cols, fullscreen_params = params

        try:
            self.update_status(f"正在處理圖片:{os.path.basename(img_path)}")
            
            # 處理圖片
            img = Image.open(img_path).convert("RGBA")
            watermark_layer = Image.new("RGBA", img.size, (255, 255, 255, 0))
            draw = ImageDraw.Draw(watermark_layer)
            font = self.load_font(font_size)
            alpha = int(255 * (opacity / 100))
            fill_color = (*color, alpha)

            # 全屏水印模式
            if self.fullscreen_mode and fullscreen_params:
                self.draw_fullscreen_watermark(draw, watermark_text, font, img.width, img.height, fill_color)
            else:
                # 普通模式:計算文字尺寸和分布
                text_bbox = draw.textbbox((0, 0), watermark_text, font=font)
                text_width = text_bbox[2] - text_bbox[0]
                text_height = text_bbox[3] - text_bbox[1]
                x_step = math.floor(img.width / (cols - 1)) if cols > 1 else 0
                y_step = math.floor(img.height / (rows - 1)) if rows > 1 else 0

                # 繪制水印
                for i in range(rows):
                    for j in range(cols):
                        base_x, base_y = self.calculate_position(
                            position, img.width, img.height, text_width, text_height
                        )
                        x = base_x + (j * x_step) if cols > 1 else base_x
                        y = base_y + (i * y_step) if rows > 1 else base_y
                        x = max(0, min(x, img.width - text_width))
                        y = max(0, min(y, img.height - text_height))
                        draw.text((x, y), watermark_text, font=font, fill=fill_color)

            # 合并保存
            result = Image.alpha_composite(img, watermark_layer)
            if img_path.lower().endswith(('.jpg', '.jpeg')):
                result = result.convert("RGB")

            save_path = filedialog.asksaveasfilename(
                defaultextension=os.path.splitext(img_path)[1],
                filetypes=[("圖片文件", "*.jpg;*.png;*.jpeg"), ("所有文件", "*.*")],
                title="保存加水印后的圖片"
            )
            if save_path:
                result.save(save_path)
                messagebox.showinfo("成功", f"圖片已保存到:\n{save_path}")
                self.update_status(f"處理完成:{os.path.basename(save_path)}")

        except Exception as e:
            messagebox.showerror("處理失敗", f"錯誤信息:\n{str(e)}")
            self.update_status("處理失敗,請檢查圖片是否正常")

    def batch_process_images(self):
        """批量處理文件夾圖片"""
        folder_path = filedialog.askdirectory(title="選擇批量處理的文件夾")
        if not folder_path:
            self.update_status("已取消選擇文件夾")
            return

        params = self.get_watermark_params()
        if not params:
            return
        watermark_text, font_size, opacity, color, position, rows, cols, fullscreen_params = params

        supported_formats = ('.jpg', '.jpeg', '.png')
        success_count = 0
        font = self.load_font(font_size)
        alpha = int(255 * (opacity / 100))
        fill_color = (*color, alpha)

        self.update_status(f"開始批量處理:{os.path.basename(folder_path)}")

        for filename in os.listdir(folder_path):
            if filename.lower().endswith(supported_formats):
                img_path = os.path.join(folder_path, filename)
                try:
                    img = Image.open(img_path).convert("RGBA")
                    watermark_layer = Image.new("RGBA", img.size, (255, 255, 255, 0))
                    draw = ImageDraw.Draw(watermark_layer)

                    # 全屏水印模式
                    if self.fullscreen_mode and fullscreen_params:
                        self.draw_fullscreen_watermark(draw, watermark_text, font, img.width, img.height, fill_color)
                    else:
                        # 普通模式繪制
                        text_bbox = draw.textbbox((0, 0), watermark_text, font=font)
                        text_width = text_bbox[2] - text_bbox[0]
                        text_height = text_bbox[3] - text_bbox[1]
                        x_step = math.floor(img.width / (cols - 1)) if cols > 1 else 0
                        y_step = math.floor(img.height / (rows - 1)) if rows > 1 else 0

                        for i in range(rows):
                            for j in range(cols):
                                base_x, base_y = self.calculate_position(
                                    position, img.width, img.height, text_width, text_height
                                )
                                x = base_x + (j * x_step) if cols > 1 else base_x
                                y = base_y + (i * y_step) if rows > 1 else base_y
                                x = max(0, min(x, img.width - text_width))
                                y = max(0, min(y, img.height - text_height))
                                draw.text((x, y), watermark_text, font=font, fill=fill_color)

                    # 保存處理結(jié)果
                    result = Image.alpha_composite(img, watermark_layer)
                    if filename.lower().endswith(('.jpg', '.jpeg')):
                        result = result.convert("RGB")

                    name, ext = os.path.splitext(filename)
                    save_path = os.path.join(folder_path, f"{name}_watermarked{ext}")
                    result.save(save_path)
                    success_count += 1
                    self.update_status(f"已處理:{filename}({success_count}張)")

                except Exception as e:
                    messagebox.showerror("單個文件失敗", f"文件 {filename} 處理出錯:\n{str(e)}")

        messagebox.showinfo("批量完成", f"批量處理結(jié)束!\n共成功處理 {success_count} 張圖片")
        self.update_status(f"批量處理完成,成功 {success_count} 張")

    def update_status(self, text):
        """更新底部狀態(tài)提示"""
        self.status_label.config(text=text)
        self.root.update_idletasks()  # 立即刷新界面


if __name__ == "__main__":
    root = tk.Tk()
    app = WatermarkTool(root)
    root.mainloop()

3.3 參數(shù)配置說明

3.3.1. 基礎(chǔ)參數(shù)

  • 水印文字:輸入需要添加的水印內(nèi)容(支持中文)
  • 字體大小:設(shè)置文字大?。ńㄗh10-50之間,根據(jù)圖片尺寸調(diào)整)
  • 透明度:0-100的數(shù)值(0為完全透明,100為完全不透明)

3.3.2. 高級參數(shù)

  • 文字顏色:點擊"選擇顏色"打開調(diào)色板,可自定義任意顏色(默認黑色)
  • 水印位置:下拉選擇9個常用位置(如"中心"會將水印放在圖片正中間)
  • 重復(fù)分布:通過"行數(shù)"和"列數(shù)"設(shè)置水印重復(fù)次數(shù):
    • 1x1:只顯示一個水?。J)
    • 2x2:顯示4個水?。?行2列均勻分布)
    • 3x3:顯示9個水?。ㄟm合大圖片全屏水?。?/li>

3.4 使用示例和效果演示

  1. 制作版權(quán)水印:文字"© 2025 xcLeigh工作室",白色半透明(透明度30),右下角1x1分布
  2. 制作全屏水?。何淖?quot;內(nèi)部資料",灰色(RGB 128,128,128),透明度20,3x3中心分布

批量生成效果

生成水印前的圖片

生成水印后的圖片

四、工具使用步驟(小白友好版)

代碼寫好后,怎么用呢?跟著這4步來,保證能成功:

1. 保存代碼

把上面所有代碼復(fù)制下來,粘貼到記事本里,然后點“文件-另存為”,注意2個細節(jié):

  • 文件名:結(jié)尾必須是.py,比如watermark_tool.py
  • 保存類型:選“所有文件”,編碼選“UTF-8”(避免中文亂碼)

2. 運行工具

找到保存好的watermark_tool.py文件,雙擊它就能打開工具(前提是已經(jīng)裝了Python)。打開后會看到這樣的界面:

  • 上面三個輸入框,分別填“水印文字”“字體大小”“透明度”
  • 下面兩個按鈕,選“處理單個圖片”或“批量處理文件夾”

3. 單圖處理操作

  1. 在輸入框里填好參數(shù)(比如水印文字填“我的旅行照”,字體20,透明度50)
  2. 點“處理單個圖片”,在彈出的窗口里選要加水印的圖片(比如桌面上的photo.jpg
  3. 選好后,會彈出“保存”窗口,選個保存位置(比如還是桌面),點“保存”
  4. 提示“成功”后,去保存位置找圖片,就能看到右下角多了水印

4. 批量處理操作

  1. 同樣先填好參數(shù)(批量處理時,所有圖片會用同一個參數(shù))
  2. 點“批量處理文件夾”,選要處理的文件夾(比如“D盤-圖片集”)
  3. 工具會自動處理文件夾里所有jpg、png圖片,不用手動等
  4. 處理完會提示“完成”,去原文件夾看,每張圖片都會多一個帶“_watermarked”后綴的副本,比如photo_watermarked.jpg

五、常見問題解決(避坑指南)

用的時候可能會遇到小問題,這里整理了3個常見情況,教你怎么解決:

1. 雙擊腳本沒反應(yīng)/報錯“no module named xxx”

原因:沒裝需要的庫,或者Python沒添加到環(huán)境變量。
解決辦法:

  • 按前面“準備工作”的步驟,重新打開cmd/終端,輸入pip install pillowpip install tkinter
  • 要是還沒反應(yīng),右鍵點擊腳本,選“打開方式”,手動選Python.exe(通常在C:\Users\你的用戶名\AppData\Local\Programs\Python\PythonXX文件夾里)

2. 處理PNG圖片后,背景變黑色

原因:PNG圖片有透明背景,保存成JPG時不支持透明,默認會填黑色。
解決辦法:

  • 處理PNG圖片時,保存的時候選“保存類型”為PNG,不要選JPG
  • 要是必須存JPG,可以在代碼里改“填充顏色”:把fill=(0,0,0,alpha)改成fill=(255,255,255,alpha),水印會變成白色,背景也會變成白色

3. 水印文字顯示亂碼/方塊

原因:系統(tǒng)里沒有Arial字體,導(dǎo)致字體加載失敗。
解決辦法:

  • 手動換個系統(tǒng)有的字體,比如Windows里的“微軟雅黑”,把代碼里加載字體的行改成:
    font = ImageFont.truetype("msyh.ttc", font_size)
  • 或者直接用默認字體,雖然不好看,但不會亂碼,代碼里保留font = ImageFont.load_default(size=font_size)就行

六、功能擴展建議(進階玩法)

要是你覺得基礎(chǔ)功能不夠用,還能給工具加這些小功能,難度也不大:

  1. 加水印位置選擇:現(xiàn)在默認右下角,可以加個下拉框,讓用戶選“左上角”“中間”“左下角”等位置
  2. 加圖片水?。翰还饽芗游淖郑€能加圖片水?。ū热缱约旱膌ogo),用Image.open("logo.png")讀取logo,再貼到原圖上
  3. 批量修改保存路徑:現(xiàn)在批量處理只能存在原文件夾,可加個“選擇輸出文件夾”按鈕,把結(jié)果統(tǒng)一存到新文件夾里
  4. 預(yù)覽功能:加水印前先顯示預(yù)覽圖,用戶確認沒問題再保存,避免反復(fù)修改

七、總結(jié)

這個Python水印工具雖然簡單,但特別實用,不管是處理日常照片,還是工作中給素材加水印,都能省不少時間。關(guān)鍵是代碼全在上面,自己能改,想加什么功能就加什么。

新手不用怕,跟著步驟一步步來,先跑通基礎(chǔ)版本,再慢慢改細節(jié),既能學(xué)會Python實用技能,又能做出自己能用的工具,這不比單純看教程有意思多了?

以上就是基于Python實現(xiàn)一個圖片水印批量添加工具的詳細內(nèi)容,更多關(guān)于Python圖片水印批量添加的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Python首次安裝后運行報錯(0xc000007b)的解決方法

    Python首次安裝后運行報錯(0xc000007b)的解決方法

    最近在安裝完P(guān)ython后運行發(fā)現(xiàn)居然報錯了,錯誤代碼是0xc000007b,于是通過往上查找發(fā)現(xiàn)是因為首次安裝Python缺乏VC++庫的原因,下面通過這篇文章看看如何解決這個問題吧。
    2016-10-10
  • python自動化測試之Selenium詳解

    python自動化測試之Selenium詳解

    這篇文章主要為大家詳細介紹了python自動化測試之Selenium,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • Python網(wǎng)絡(luò)爬蟲項目:內(nèi)容提取器的定義

    Python網(wǎng)絡(luò)爬蟲項目:內(nèi)容提取器的定義

    本篇文章主要介紹了Python網(wǎng)絡(luò)爬蟲項目,這能有效的節(jié)省程序員的時間,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2016-10-10
  • python實現(xiàn)信號時域統(tǒng)計特征提取代碼

    python實現(xiàn)信號時域統(tǒng)計特征提取代碼

    今天小編就為大家分享一篇python實現(xiàn)信號時域統(tǒng)計特征提取代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-02-02
  • Python3 pip3 list 出現(xiàn) DEPRECATION 警告的解決方法

    Python3 pip3 list 出現(xiàn) DEPRECATION 警告的解決方法

    今天小編就為大家分享一篇Python3 pip3 list 出現(xiàn) DEPRECATION 警告的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-02-02
  • 時間序列重采樣和pandas的resample方法示例解析

    時間序列重采樣和pandas的resample方法示例解析

    這篇文章主要為大家介紹了時間序列重采樣和pandas的resample方法示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • 使用Python進行接口測試的代碼演示

    使用Python進行接口測試的代碼演示

    在軟件開發(fā)過程中,接口測試是一個至關(guān)重要的環(huán)節(jié),Python作為一種簡潔、靈活且易于學(xué)習(xí)的編程語言,為接口測試提供了強大的支持,本文將介紹如何使用Python進行接口測試,并通過代碼實例演示,需要的朋友可以參考下
    2025-05-05
  • python實現(xiàn)機器人行走效果

    python實現(xiàn)機器人行走效果

    這篇文章主要為大家詳細介紹了python實現(xiàn)機器人行走效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • PyTorch 如何自動計算梯度

    PyTorch 如何自動計算梯度

    這篇文章主要介紹了PyTorch 如何自動計算梯度的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-05-05
  • Python正則表達式語法及re模塊中的常用函數(shù)詳解

    Python正則表達式語法及re模塊中的常用函數(shù)詳解

    這篇文章主要給大家介紹了關(guān)于Python正則表達式語法及re模塊中常用函數(shù)的相關(guān)資料,正則表達式是一種強大的字符串處理工具,可以用于匹配、切分、查找和替換等操作,文章詳細講解了如何使用正則表達式進行匹配,,需要的朋友可以參考下
    2025-04-04

最新評論

富源县| 哈尔滨市| 盐池县| 鹤壁市| 藁城市| 桐梓县| 天门市| 泌阳县| 收藏| 武清区| 娱乐| 永川市| 绿春县| 安徽省| 格尔木市| 根河市| 犍为县| 彭山县| 泾源县| 南雄市| 通海县| 尚志市| 佛山市| 珲春市| 秭归县| 英超| 睢宁县| 沾化县| 陇川县| 壤塘县| 广丰县| 光泽县| 洪洞县| 崇信县| 清水河县| 镇赉县| 航空| 洪洞县| 凤翔县| 昌乐县| 积石山|