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

Python基于wxPython開發(fā)一個圖片PDF生成器

 更新時間:2025年11月20日 08:52:31   作者:winfredzhang  
本文將詳細(xì)解析一個基于wxPython開發(fā)的圖片PDF生成器應(yīng)用程序,該程序能夠批量處理圖片,支持旋轉(zhuǎn)、剪切等編輯功能,并按照指定順序?qū)D片導(dǎo)出為PDF文件,需要的朋友可以參考下

前言

本文將詳細(xì)解析一個基于wxPython開發(fā)的圖片PDF生成器應(yīng)用程序。該程序能夠批量處理圖片,支持旋轉(zhuǎn)、剪切等編輯功能,并按照指定順序?qū)D片導(dǎo)出為PDF文件。通過本文,你將學(xué)習(xí)到wxPython GUI開發(fā)、PIL圖像處理、ReportLab PDF生成等技術(shù)要點。
C:\pythoncode\new\pics2pdf.py

項目概述

功能特性

  1. 圖片管理:瀏覽文件夾并加載所有圖片文件
  2. 圖片預(yù)覽:實時預(yù)覽選中的圖片
  3. 圖片編輯
    • 旋轉(zhuǎn)功能(支持90度左旋/右旋)
    • 剪切功能(鼠標(biāo)拖拽選擇區(qū)域)
    • 重置功能(恢復(fù)原始狀態(tài))
  4. 列表管理:在兩個列表間移動圖片,調(diào)整順序
  5. PDF導(dǎo)出:按指定順序?qū)D片導(dǎo)出為PDF文件

技術(shù)棧

  • wxPython:GUI界面框架
  • PIL/Pillow:圖像處理庫
  • ReportLab:PDF生成庫
  • Python標(biāo)準(zhǔn)庫:文件操作、數(shù)據(jù)結(jié)構(gòu)

代碼架構(gòu)分析

整體結(jié)構(gòu)

程序采用面向?qū)ο笤O(shè)計,主要包含兩個類:

  1. ImagePDFFrame:主窗口類,負(fù)責(zé)整體UI和核心功能
  2. CropDialog:剪切對話框類,負(fù)責(zé)圖片剪切功能
ImagePDFFrame (主窗口)
├── UI布局
│   ├── 左側(cè):圖片列表 + 選擇文件夾按鈕
│   ├── 中間:添加/移除按鈕
│   ├── 右側(cè):待導(dǎo)出列表 + 順序調(diào)整按鈕
│   └── 預(yù)覽區(qū):圖片顯示 + 編輯按鈕
├── 數(shù)據(jù)管理
│   ├── image_folder:當(dāng)前文件夾路徑
│   ├── image_files:圖片文件列表
│   ├── image_modifications:圖片編輯記錄
│   └── current_preview_image:當(dāng)前預(yù)覽圖片
└── 功能模塊
    ├── 文件加載
    ├── 圖片預(yù)覽
    ├── 圖片編輯
    └── PDF生成

核心代碼詳解

1. 初始化與UI布局

def __init__(self):
    super().__init__(None, title="圖片PDF生成器", size=(1400, 800))
    
    self.image_folder = ""
    self.image_files = []
    self.current_preview_image = None
    self.current_preview_filename = None
    self.image_modifications = {}  # 關(guān)鍵數(shù)據(jù)結(jié)構(gòu):存儲圖片編輯信息

數(shù)據(jù)結(jié)構(gòu)設(shè)計亮點

image_modifications 字典的設(shè)計非常巧妙:

{
    'photo1.jpg': {
        'rotation': 90,      # 旋轉(zhuǎn)角度(0, 90, 180, 270)
        'crop': (x1, y1, x2, y2)  # 剪切區(qū)域坐標(biāo)
    },
    'photo2.jpg': {
        'rotation': 0,
        'crop': None
    }
}

這種設(shè)計的優(yōu)勢:

  • 每張圖片的編輯狀態(tài)獨立存儲
  • 可隨時查詢和應(yīng)用編輯
  • 支持撤銷和重置操作

2. UI布局:BoxSizer的應(yīng)用

wxPython使用Sizer進行布局管理,代碼中使用了wx.BoxSizer

main_sizer = wx.BoxSizer(wx.HORIZONTAL)  # 水平主布局

# 左側(cè)垂直布局
left_sizer = wx.BoxSizer(wx.VERTICAL)
left_sizer.Add(btn_select, 0, wx.ALL | wx.EXPAND, 5)
left_sizer.Add(self.listbox1, 1, wx.ALL | wx.EXPAND, 5)  # 比例為1,自動擴展

main_sizer.Add(left_sizer, 1, wx.EXPAND)

布局參數(shù)解析

  • proportion(第二個參數(shù)):
    • 0:固定大小,不隨窗口縮放
    • 1:按比例分配剩余空間
  • flag(第三個參數(shù)):
    • wx.ALL:四周都有邊距
    • wx.EXPAND:填充可用空間
  • border(第四個參數(shù)):邊距像素值

3. 文件加載機制

def load_images(self):
    self.image_files = []
    self.listbox1.Clear()
    self.image_modifications = {}
    
    # 支持的圖片格式
    image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff')
    
    try:
        for filename in os.listdir(self.image_folder):
            if filename.lower().endswith(image_extensions):
                self.image_files.append(filename)
                self.listbox1.Append(filename)
                # 初始化每張圖片的編輯狀態(tài)
                self.image_modifications[filename] = {'rotation': 0, 'crop': None}

技術(shù)要點

  1. 文件過濾:使用lower()endswith()實現(xiàn)大小寫不敏感的文件擴展名匹配
  2. 狀態(tài)初始化:加載圖片時同時初始化編輯狀態(tài)字典
  3. 異常處理:使用try-except捕獲文件操作異常

4. 圖片預(yù)覽:多選ListBox的處理

這是本項目的一個重要技術(shù)點:

def on_select_image(self, event):
    selections = self.listbox1.GetSelections()  # 獲取所有選中項
    if selections:
        # 只預(yù)覽第一個選中的圖片
        filename = self.listbox1.GetString(selections[0])
        self.show_preview(filename)

為什么要用GetSelections()?

ListBox設(shè)置了wx.LB_EXTENDED樣式(多選模式):

self.listbox1 = wx.ListBox(panel, style=wx.LB_EXTENDED)

在多選模式下:

  • ? GetSelection():會拋出斷言錯誤
  • ? GetSelections():返回所有選中項的索引列表

這是wxPython的一個常見陷阱,必須根據(jù)ListBox的模式選擇正確的方法。

5. 圖片預(yù)覽實現(xiàn)

def show_preview(self, filename):
    try:
        image_path = os.path.join(self.image_folder, filename)
        img = Image.open(image_path)
        
        # 應(yīng)用已保存的編輯
        if filename in self.image_modifications:
            mods = self.image_modifications[filename]
            
            # 應(yīng)用旋轉(zhuǎn)
            if mods['rotation'] != 0:
                img = img.rotate(-mods['rotation'], expand=True)
            
            # 應(yīng)用剪切
            if mods['crop']:
                img = img.crop(mods['crop'])
        
        self.current_preview_image = img.copy()
        self.current_preview_filename = filename
        
        # 縮放適應(yīng)預(yù)覽區(qū)域
        preview_size = (450, 500)
        img.thumbnail(preview_size, Image.Resampling.LANCZOS)
        
        # PIL圖像轉(zhuǎn)wxImage
        width, height = img.size
        if img.mode != 'RGB':
            img = img.convert('RGB')  # 確保RGB模式
        
        wx_image = wx.Image(width, height)
        wx_image.SetData(img.tobytes())
        
        # 顯示
        self.image_preview.SetBitmap(wx_image.ConvertToBitmap())
        self.image_preview.Refresh()

關(guān)鍵技術(shù)點

PIL旋轉(zhuǎn)rotate(-angle, expand=True)

  • 負(fù)角度:因為PIL的坐標(biāo)系與界面旋轉(zhuǎn)方向相反
  • expand=True:自動擴展畫布以容納旋轉(zhuǎn)后的圖像

PIL剪切crop((x1, y1, x2, y2))

  • 坐標(biāo)是左上角(x1, y1)和右下角(x2, y2)

圖像模式轉(zhuǎn)換

if img.mode != 'RGB':
    img = img.convert('RGB')

某些圖片格式(如PNG的RGBA、灰度圖)需要轉(zhuǎn)換為RGB才能在wxPython中顯示

PIL與wxPython的橋接

wx_image = wx.Image(width, height)
wx_image.SetData(img.tobytes())  # 字節(jié)數(shù)據(jù)傳遞
bitmap = wx_image.ConvertToBitmap()

6. 圖片旋轉(zhuǎn)功能

def on_rotate_left(self, event):
    if self.current_preview_filename:
        filename = self.current_preview_filename
        # 模360運算保持角度在0-359范圍內(nèi)
        self.image_modifications[filename]['rotation'] = \
            (self.image_modifications[filename]['rotation'] - 90) % 360
        self.show_preview(filename)

def on_rotate_right(self, event):
    if self.current_preview_filename:
        filename = self.current_preview_filename
        self.image_modifications[filename]['rotation'] = \
            (self.image_modifications[filename]['rotation'] + 90) % 360
        self.show_preview(filename)

設(shè)計思路

  • 旋轉(zhuǎn)操作只修改數(shù)據(jù),不直接操作圖片文件
  • 使用模運算% 360確保角度值規(guī)范化
  • 立即調(diào)用show_preview()刷新顯示

7. 圖片剪切功能:自定義對話框

剪切功能的實現(xiàn)較為復(fù)雜,單獨使用了CropDialog類:

class CropDialog(wx.Dialog):
    def __init__(self, parent, image):
        super().__init__(parent, title="剪切圖片", size=(800, 700))
        
        self.original_image = image.copy()
        self.display_image = None
        self.crop_box = None
        self.start_point = None  # 鼠標(biāo)起始點
        self.end_point = None    # 鼠標(biāo)結(jié)束點
        self.scale_factor = 1.0  # 縮放比例

核心機制

a) 圖像縮放與坐標(biāo)映射

def prepare_image(self):
    canvas_size = (750, 550)
    img = self.original_image.copy()
    img.thumbnail(canvas_size, Image.Resampling.LANCZOS)
    
    self.display_image = img
    # 計算縮放比例,用于坐標(biāo)轉(zhuǎn)換
    self.scale_factor = self.original_image.size[0] / img.size[0]

這個scale_factor非常關(guān)鍵:

  • 顯示的是縮放后的圖像
  • 用戶在縮放圖像上選擇區(qū)域
  • 必須將坐標(biāo)轉(zhuǎn)換回原圖尺寸

b) 鼠標(biāo)事件處理

def on_mouse_down(self, event):
    self.start_point = event.GetPosition()
    self.end_point = self.start_point

def on_mouse_move(self, event):
    if event.Dragging() and self.start_point:
        self.end_point = event.GetPosition()
        self.canvas.Refresh()  # 觸發(fā)重繪

def on_mouse_up(self, event):
    if self.start_point:
        self.end_point = event.GetPosition()
        self.canvas.Refresh()

事件流程

  1. 按下鼠標(biāo) → 記錄起始點
  2. 拖動鼠標(biāo) → 更新結(jié)束點并刷新界面
  3. 釋放鼠標(biāo) → 確定最終選擇區(qū)域

c) 自定義繪制

def on_paint(self, event):
    dc = wx.PaintDC(self.canvas)
    
    if self.display_image:
        # 繪制圖片
        width, height = self.display_image.size
        # ... 轉(zhuǎn)換并繪制圖片 ...
        
        # 繪制選擇框
        if self.start_point and self.end_point:
            dc.SetPen(wx.Pen(wx.RED, 2))
            dc.SetBrush(wx.Brush(wx.Colour(255, 0, 0, 50)))  # 半透明紅色
            
            x1, y1 = self.start_point
            x2, y2 = self.end_point
            
            rect_x = min(x1, x2)
            rect_y = min(y1, y2)
            rect_w = abs(x2 - x1)
            rect_h = abs(y2 - y1)
            
            dc.DrawRectangle(rect_x, rect_y, rect_w, rect_h)

DC(Device Context)繪圖

  • wx.PaintDC:專門用于EVT_PAINT事件的繪圖上下文
  • SetPen():設(shè)置線條樣式
  • SetBrush():設(shè)置填充樣式
  • DrawRectangle():繪制矩形

d) 坐標(biāo)轉(zhuǎn)換

def get_crop_box(self):
    if not self.start_point or not self.end_point:
        return None
    
    canvas_width, canvas_height = self.canvas.GetSize()
    img_width, img_height = self.display_image.size
    
    # 計算圖片在canvas中的偏移(居中顯示)
    offset_x = (canvas_width - img_width) // 2
    offset_y = (canvas_height - img_height) // 2
    
    # 轉(zhuǎn)換為圖片坐標(biāo)
    x1 = max(0, min(self.start_point[0], self.end_point[0]) - offset_x)
    y1 = max(0, min(self.start_point[1], self.end_point[1]) - offset_y)
    x2 = min(img_width, max(self.start_point[0], self.end_point[0]) - offset_x)
    y2 = min(img_height, max(self.start_point[1], self.end_point[1]) - offset_y)
    
    # 轉(zhuǎn)換回原始圖片坐標(biāo)(考慮縮放)
    orig_x1 = int(x1 * self.scale_factor)
    orig_y1 = int(y1 * self.scale_factor)
    orig_x2 = int(x2 * self.scale_factor)
    orig_y2 = int(y2 * self.scale_factor)
    
    # 驗證選擇區(qū)域大小
    if orig_x2 - orig_x1 > 10 and orig_y2 - orig_y1 > 10:
        return (orig_x1, orig_y1, orig_x2, orig_y2)
    
    return None

坐標(biāo)轉(zhuǎn)換步驟

  1. 減去偏移量:canvas坐標(biāo) → 顯示圖片坐標(biāo)
  2. 邊界檢查:確保坐標(biāo)在圖片范圍內(nèi)
  3. 縮放轉(zhuǎn)換:顯示圖片坐標(biāo) → 原圖坐標(biāo)
  4. 大小驗證:確保選擇區(qū)域不是太小

8. 列表管理功能

def on_add_to_list2(self, event):
    selections = self.listbox1.GetSelections()
    for sel in selections:
        filename = self.listbox1.GetString(sel)
        # 防止重復(fù)添加
        if self.listbox2.FindString(filename) == wx.NOT_FOUND:
            self.listbox2.Append(filename)

def on_remove_from_list2(self, event):
    selections = list(self.listbox2.GetSelections())
    selections.reverse()  # 從后往前刪除,避免索引變化問題
    for sel in selections:
        self.listbox2.Delete(sel)

刪除技巧

為什么要反向刪除?

# 假設(shè)選中索引 [1, 3, 5]
selections = [1, 3, 5]
selections.reverse()  # 變成 [5, 3, 1]

# 正向刪除的問題:
Delete(1)  # 刪除索引1,后面的元素前移
# 現(xiàn)在原來的索引3變成了索引2,但我們要刪除索引3 ?

# 反向刪除:
Delete(5)  # 刪除索引5,不影響前面的元素
Delete(3)  # 刪除索引3,不影響前面的元素
Delete(1)  # 刪除索引1 ?

9. 順序調(diào)整

def on_move_up(self, event):
    selections = self.listbox2.GetSelections()
    if selections and selections[0] > 0:
        selection = selections[0]
        filename = self.listbox2.GetString(selection)
        self.listbox2.Delete(selection)
        self.listbox2.Insert(filename, selection - 1)
        self.listbox2.SetSelection(selection - 1)  # 保持選中狀態(tài)

實現(xiàn)邏輯

  1. 獲取當(dāng)前選中項
  2. 刪除該項
  3. 在新位置插入
  4. 重新選中(提供更好的用戶體驗)

10. PDF生成:核心功能

def create_pdf(self, pdf_path):
    try:
        c = canvas.Canvas(pdf_path, pagesize=A4)
        page_width, page_height = A4
        
        for i in range(self.listbox2.GetCount()):
            filename = self.listbox2.GetString(i)
            image_path = os.path.join(self.image_folder, filename)
            
            # 加載并應(yīng)用編輯
            img = Image.open(image_path)
            
            if filename in self.image_modifications:
                mods = self.image_modifications[filename]
                
                if mods['rotation'] != 0:
                    img = img.rotate(-mods['rotation'], expand=True)
                
                if mods['crop']:
                    img = img.crop(mods['crop'])
            
            # 轉(zhuǎn)換為RGB模式
            if img.mode != 'RGB':
                img = img.convert('RGB')
            
            # 保存到內(nèi)存緩沖區(qū)
            img_buffer = io.BytesIO()
            img.save(img_buffer, format='JPEG')
            img_buffer.seek(0)
            
            img_width, img_height = img.size
            
            # 計算縮放比例,適應(yīng)A4頁面
            width_ratio = (page_width - 40) / img_width
            height_ratio = (page_height - 40) / img_height
            ratio = min(width_ratio, height_ratio)  # 保持寬高比
            
            new_width = img_width * ratio
            new_height = img_height * ratio
            
            # 居中顯示
            x = (page_width - new_width) / 2
            y = (page_height - new_height) / 2
            
            # 添加到PDF
            c.drawImage(ImageReader(img_buffer), x, y, 
                       width=new_width, height=new_height)
            
            # 添加新頁(除最后一頁)
            if i < self.listbox2.GetCount() - 1:
                c.showPage()
        
        c.save()

技術(shù)亮點

a) 內(nèi)存緩沖區(qū)

img_buffer = io.BytesIO()
img.save(img_buffer, format='JPEG')
img_buffer.seek(0)

為什么使用內(nèi)存緩沖區(qū)?

  • ? 不需要創(chuàng)建臨時文件
  • ? 提高性能
  • ? 避免磁盤IO開銷

b) 寬高比保持

width_ratio = (page_width - 40) / img_width
height_ratio = (page_height - 40) / img_height
ratio = min(width_ratio, height_ratio)  # 選擇較小的比例

使用min()確保圖片完整顯示在頁面內(nèi),不會被裁剪。

c) 居中對齊

x = (page_width - new_width) / 2
y = (page_height - new_height) / 2

通過計算剩余空間的一半,實現(xiàn)居中效果。

優(yōu)化建議與擴展思路

1. 性能優(yōu)化

問題:大量圖片加載時可能卡頓

解決方案

# 使用線程加載圖片
import threading

def load_images_async(self):
    def load():
        # 加載邏輯
        wx.CallAfter(self.update_ui)  # 線程安全的UI更新
    
    thread = threading.Thread(target=load)
    thread.start()

2. 縮略圖緩存

優(yōu)化

self.thumbnail_cache = {}  # 添加緩存字典

def get_thumbnail(self, filename):
    if filename not in self.thumbnail_cache:
        img = Image.open(...)
        img.thumbnail((100, 100))
        self.thumbnail_cache[filename] = img
    return self.thumbnail_cache[filename]

3. 批量操作

建議:添加批量旋轉(zhuǎn)、批量剪切功能

def on_batch_rotate(self, event):
    selections = self.listbox2.GetSelections()
    for sel in selections:
        filename = self.listbox2.GetString(sel)
        self.image_modifications[filename]['rotation'] += 90

4. 撤銷/重做功能

實現(xiàn)思路

class CommandHistory:
    def __init__(self):
        self.history = []
        self.current = -1
    
    def add_command(self, command):
        self.history = self.history[:self.current+1]
        self.history.append(command)
        self.current += 1
    
    def undo(self):
        if self.current >= 0:
            self.history[self.current].undo()
            self.current -= 1

5. 配置保存

建議:保存用戶的編輯狀態(tài)

import json

def save_project(self):
    project = {
        'folder': self.image_folder,
        'modifications': self.image_modifications,
        'export_list': [self.listbox2.GetString(i) 
                       for i in range(self.listbox2.GetCount())]
    }
    with open('project.json', 'w') as f:
        json.dump(project, f)

常見問題與解決

Q1: 為什么圖片顯示不出來?

可能原因

  1. 圖片模式不是RGB
  2. 文件路徑編碼問題
  3. 圖片損壞

解決

# 始終轉(zhuǎn)換為RGB
if img.mode != 'RGB':
    img = img.convert('RGB')

# 處理路徑編碼
image_path = os.path.join(self.image_folder, filename)
# 在Windows上可能需要:
# image_path = image_path.encode('utf-8').decode('utf-8')

Q2: PDF生成后圖片質(zhì)量下降?

原因:使用JPEG格式壓縮

解決

# 提高JPEG質(zhì)量
img.save(img_buffer, format='JPEG', quality=95)

# 或使用PNG(文件更大)
img.save(img_buffer, format='PNG')

Q3: 大圖片加載很慢?

解決:使用漸進式加載

# 先顯示低分辨率版本
thumbnail = img.copy()
thumbnail.thumbnail((200, 200))
# 顯示縮略圖...

# 異步加載完整版
threading.Thread(target=lambda: self.load_full_image(img)).start()

運行結(jié)果

以上就是Python基于wxPython開發(fā)一個圖片PDF生成器的詳細(xì)內(nèi)容,更多關(guān)于Python wxPython圖片PDF生成器的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

新乐市| 望都县| 保靖县| 新巴尔虎右旗| 肥乡县| 金秀| 榆社县| 虞城县| 黎川县| 资溪县| 林州市| 龙口市| 武义县| 炉霍县| 阜新市| 皮山县| 莱西市| 定兴县| 华坪县| 五寨县| 礼泉县| 尤溪县| 攀枝花市| 昌吉市| 贵溪市| 新余市| 绥阳县| 博客| 洛南县| 尚义县| 敦化市| 桃源县| 营山县| 佛山市| 鄂州市| 榕江县| 景谷| 高清| 鄯善县| 郧西县| 嘉荫县|