使用Python实现图片处理工具

 更新时间:2025年01月10日 09:27:56   作者:winfredzhang  
这篇文章主要介绍了如何基于 wxPython 和 Pillow (PIL) 的简单图片处理工具,可以支持图片选择,旋转,合并和压缩等功能,感兴趣的小伙伴可以了解下

本文将详细分析一款基于 wxPython 和 Pillow (PIL) 的简单图片处理工具,包括核心功能的实现与代码的设计思路。这款工具支持图片选择、旋转、合并、压缩,并且具有友好的图形用户界面(GUI)。

全部代码

import wx
from PIL import Image
import os
from io import BytesIO

class ImageProcessorFrame(wx.Frame):
    def __init__(self):
        super().__init__(parent=None, title='图片处理工具')
        self.selected_images = []
        self.current_image = None
        self.current_pil_image = None  # 存储PIL Image对象
        self.init_ui()
        
    def init_ui(self):
        panel = wx.Panel(self)
        main_sizer = wx.BoxSizer(wx.HORIZONTAL)
        
        # 左侧控制面板
        left_sizer = wx.BoxSizer(wx.VERTICAL)
        
        # 创建按钮
        select_btn = wx.Button(panel, label='选择图片')
        rotate_btn = wx.Button(panel, label='旋转')
        merge_btn = wx.Button(panel, label='合并')
        compress_btn = wx.Button(panel, label='压缩')
        
        # 创建列表框显示选择的图片
        self.list_box = wx.ListBox(panel, size=(200, 300))
        
        # 添加组件到左侧sizer
        left_sizer.Add(select_btn, 0, wx.ALL | wx.EXPAND, 5)
        left_sizer.Add(self.list_box, 1, wx.ALL | wx.EXPAND, 5)
        left_sizer.Add(rotate_btn, 0, wx.ALL | wx.EXPAND, 5)
        left_sizer.Add(merge_btn, 0, wx.ALL | wx.EXPAND, 5)
        left_sizer.Add(compress_btn, 0, wx.ALL | wx.EXPAND, 5)
        
        # 右侧图片显示区域
        right_sizer = wx.BoxSizer(wx.VERTICAL)
        self.image_display = wx.StaticBitmap(panel, size=(600, 400))
        right_sizer.Add(self.image_display, 1, wx.EXPAND | wx.ALL, 5)
        
        # 将左右两侧添加到主sizer
        main_sizer.Add(left_sizer, 0, wx.EXPAND | wx.ALL, 5)
        main_sizer.Add(right_sizer, 1, wx.EXPAND | wx.ALL, 5)
        
        # 绑定事件
        select_btn.Bind(wx.EVT_BUTTON, self.on_select)
        rotate_btn.Bind(wx.EVT_BUTTON, self.on_rotate)
        merge_btn.Bind(wx.EVT_BUTTON, self.on_merge)
        compress_btn.Bind(wx.EVT_BUTTON, self.on_compress)
        self.list_box.Bind(wx.EVT_LISTBOX, self.on_select_image)
        
        panel.SetSizer(main_sizer)
        self.SetSize((900, 600))
        self.Centre()
        
    def update_image_display(self, pil_image):
        """更新图片显示"""
        if pil_image:
            try:
                # 确保图片是RGB模式
                if pil_image.mode != 'RGB':
                    pil_image = pil_image.convert('RGB')
                
                # 获取显示区域的大小
                display_size = self.image_display.GetSize()
                image_size = pil_image.size
                
                # 计算缩放比例
                ratio = min(display_size[0]/image_size[0], 
                          display_size[1]/image_size[1])
                new_size = (int(image_size[0] * ratio), 
                          int(image_size[1] * ratio))
                
                # 调整图片大小
                resized_image = pil_image.resize(new_size, Image.Resampling.LANCZOS)
                
                # 转换为wx.Bitmap
                image_buffer = BytesIO()
                resized_image.save(image_buffer, format='PNG')
                image_buffer.seek(0)  # 重置缓冲区指针到开始位置
                wx_image = wx.Image(image_buffer, type=wx.BITMAP_TYPE_PNG)
                wx_bitmap = wx_image.ConvertToBitmap()
                
                # 更新显示
                self.image_display.SetBitmap(wx_bitmap)
                self.current_image = wx_bitmap
                self.current_pil_image = pil_image
                
                # 刷新显示
                self.image_display.Refresh()
            except Exception as e:
                wx.MessageBox(f'处理图片时出错: {str(e)}', '错误', wx.OK | wx.ICON_ERROR)
            
            # 计算缩放比例
            ratio = min(display_size[0]/image_size[0], 
                       display_size[1]/image_size[1])
            new_size = (int(image_size[0] * ratio), 
                       int(image_size[1] * ratio))
            
            # 调整图片大小
            resized_image = pil_image.resize(new_size, Image.Resampling.LANCZOS)
            
            # 转换为wx.Bitmap
            image_buffer = BytesIO()
            resized_image.save(image_buffer, format='PNG')
            image_buffer.seek(0)  # 重置缓冲区指针到开始位置
            wx_image = wx.Image(image_buffer, type=wx.BITMAP_TYPE_PNG)
            wx_bitmap = wx_image.ConvertToBitmap()
            
            # 更新显示
            self.image_display.SetBitmap(wx_bitmap)
            self.current_image = wx_bitmap
            self.current_pil_image = pil_image
            
            # 刷新显示
            self.image_display.Refresh()
        
    def on_select(self, event):
        with wx.FileDialog(self, "选择图片文件", wildcard="图片文件 (*.jpg;*.png)|*.jpg;*.png",
                         style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE) as fileDialog:
            
            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return
                
            paths = fileDialog.GetPaths()
            self.selected_images.extend(paths)
            self.list_box.Set([os.path.basename(path) for path in self.selected_images])
            
    def on_select_image(self, event):
        """当在列表中选择图片时触发"""
        selection = self.list_box.GetSelection()
        if selection != wx.NOT_FOUND:
            try:
                image_path = self.selected_images[selection]
                # 使用二进制模式读取图片
                with Image.open(image_path) as img:
                    # 创建一个副本以确保图片被完全加载
                    pil_image = img.copy()
                    self.update_image_display(pil_image)
            except Exception as e:
                wx.MessageBox(f'无法打开图片: {str(e)}', '错误', 
                            wx.OK | wx.ICON_ERROR)
                
    def on_rotate(self, event):
        """旋转当前显示的图片"""
        if self.current_pil_image:
            # 顺时针旋转90度
            try:
                rotated_image = self.current_pil_image.rotate(-90, expand=True)
                self.update_image_display(rotated_image)
            except Exception as e:
                wx.MessageBox(f'旋转图片时出错: {str(e)}', '错误', wx.OK | wx.ICON_ERROR)
        else:
            wx.MessageBox('请先选择一张图片', '提示', 
                         wx.OK | wx.ICON_INFORMATION)
            
    def on_merge(self, event):
        if len(self.selected_images) < 2:
            wx.MessageBox('请至少选择两张图片', '提示', 
                         wx.OK | wx.ICON_INFORMATION)
            return
            
        max_width = 0
        total_height = 0
        
        images = []
        for img_path in self.selected_images:
            img = Image.open(img_path)
            max_width = max(max_width, img.width)
            total_height += img.height
            images.append(img)
            
        merged_image = Image.new('RGB', (max_width, total_height))
        
        current_height = 0
        for img in images:
            if img.width < max_width:
                x_offset = (max_width - img.width) // 2
            else:
                x_offset = 0
                
            merged_image.paste(img, (x_offset, current_height))
            current_height += img.height
            
        save_path = os.path.join(os.path.dirname(self.selected_images[0]), 
                                'merged.jpg')
        merged_image.save(save_path)
        wx.MessageBox(f'图片已合并保存至: {save_path}', '成功', 
                     wx.OK | wx.ICON_INFORMATION)
        
    def on_compress(self, event):
        merged_path = os.path.join(os.path.dirname(self.selected_images[0]), 
                                 'merged.jpg')
        if not os.path.exists(merged_path):
            wx.MessageBox('请先合并图片', '提示', wx.OK | wx.ICON_INFORMATION)
            return
            
        img = Image.open(merged_path)
        width = int(img.width * 0.5)
        height = int(img.height * 0.5)
        compressed_img = img.resize((width, height), Image.Resampling.LANCZOS)
        
        save_path = os.path.join(os.path.dirname(merged_path), 'compressed.jpg')
        compressed_img.save(save_path, quality=85, optimize=True)
        wx.MessageBox(f'压缩后的图片已保存至: {save_path}', '成功', 
                     wx.OK | wx.ICON_INFORMATION)

def main():
    app = wx.App()
    frame = ImageProcessorFrame()
    frame.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

功能概述

该工具的主要功能包括:

  • 选择图片:从本地文件中选择图片并显示在列表中。
  • 图片预览:点击列表项可在右侧区域预览图片。
  • 图片旋转:支持顺时针旋转当前显示的图片。
  • 图片合并:将多张图片垂直合并为一张新图片。
  • 图片压缩:对合并后的图片进行尺寸压缩。

代码结构与模块解析

import wx
from PIL import Image
import os
from io import BytesIO

模块说明

wx:提供图形用户界面支持,用于设计窗口、按钮、列表等组件。

Pillow (PIL):Python图像处理库,支持加载、旋转、缩放、保存图片等功能。

os:用于文件路径操作。

io.BytesIO:内存中的二进制流,用于将 PIL 图片转换为 wx.Bitmap 显示。

主类 ImageProcessorFrame

ImageProcessorFrame 继承自 wx.Frame,是程序的主窗口,负责布局、事件绑定和功能处理。

class ImageProcessorFrame(wx.Frame):
    def __init__(self):
        super().__init__(parent=None, title='图片处理工具')
        self.selected_images = []  # 存储已选择的图片路径
        self.current_image = None  # 当前显示的图片(wx.Bitmap)
        self.current_pil_image = None  # 当前显示的 PIL 图片对象
        self.init_ui()

界面初始化

init_ui 方法负责创建和布局界面组件,包括按钮、列表框和图片显示区域。

def init_ui(self):
    panel = wx.Panel(self)
    main_sizer = wx.BoxSizer(wx.HORIZONTAL)  # 主布局,水平分布

    # 左侧控制面板
    left_sizer = wx.BoxSizer(wx.VERTICAL)
    select_btn = wx.Button(panel, label='选择图片')
    rotate_btn = wx.Button(panel, label='旋转')
    merge_btn = wx.Button(panel, label='合并')
    compress_btn = wx.Button(panel, label='压缩')
    self.list_box = wx.ListBox(panel, size=(200, 300))

    left_sizer.Add(select_btn, 0, wx.ALL | wx.EXPAND, 5)
    left_sizer.Add(self.list_box, 1, wx.ALL | wx.EXPAND, 5)
    left_sizer.Add(rotate_btn, 0, wx.ALL | wx.EXPAND, 5)
    left_sizer.Add(merge_btn, 0, wx.ALL | wx.EXPAND, 5)
    left_sizer.Add(compress_btn, 0, wx.ALL | wx.EXPAND, 5)

    # 右侧图片显示区域
    right_sizer = wx.BoxSizer(wx.VERTICAL)
    self.image_display = wx.StaticBitmap(panel, size=(600, 400))
    right_sizer.Add(self.image_display, 1, wx.EXPAND | wx.ALL, 5)

    # 将左右两侧添加到主布局
    main_sizer.Add(left_sizer, 0, wx.EXPAND | wx.ALL, 5)
    main_sizer.Add(right_sizer, 1, wx.EXPAND | wx.ALL, 5)

    # 绑定按钮事件
    select_btn.Bind(wx.EVT_BUTTON, self.on_select)
    rotate_btn.Bind(wx.EVT_BUTTON, self.on_rotate)
    merge_btn.Bind(wx.EVT_BUTTON, self.on_merge)
    compress_btn.Bind(wx.EVT_BUTTON, self.on_compress)
    self.list_box.Bind(wx.EVT_LISTBOX, self.on_select_image)

    panel.SetSizer(main_sizer)
    self.SetSize((900, 600))
    self.Centre()

布局细节:

  • 左侧为按钮和图片列表框。
  • 右侧为图片显示区域。
  • 使用 wx.BoxSizer 管理布局,保证界面响应式。

按钮绑定:

  • on_select:选择图片并添加到列表。
  • on_rotate:旋转当前图片。
  • on_merge:合并图片。
  • on_compress:压缩图片。

核心功能实现

图片选择

def on_select(self, event):
    with wx.FileDialog(self, "选择图片文件", wildcard="图片文件 (*.jpg;*.png)|*.jpg;*.png",
                       style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE) as fileDialog:

        if fileDialog.ShowModal() == wx.ID_CANCEL:
            return

        paths = fileDialog.GetPaths()  # 获取选择的文件路径
        self.selected_images.extend(paths)  # 添加到已选择列表
        self.list_box.Set([os.path.basename(path) for path in self.selected_images])  # 显示文件名

功能:

打开文件对话框,支持多选。

将选择的图片路径存储到 self.selected_images,并更新列表框显示。

图片显示与预览

def update_image_display(self, pil_image):
    if pil_image:
        try:
            if pil_image.mode != 'RGB':
                pil_image = pil_image.convert('RGB')

            display_size = self.image_display.GetSize()
            image_size = pil_image.size
            ratio = min(display_size[0]/image_size[0], display_size[1]/image_size[1])
            new_size = (int(image_size[0] * ratio), int(image_size[1] * ratio))

            resized_image = pil_image.resize(new_size, Image.Resampling.LANCZOS)

            image_buffer = BytesIO()
            resized_image.save(image_buffer, format='PNG')
            wx_image = wx.Image(image_buffer, type=wx.BITMAP_TYPE_PNG)
            wx_bitmap = wx_image.ConvertToBitmap()

            self.image_display.SetBitmap(wx_bitmap)
            self.current_image = wx_bitmap
            self.current_pil_image = pil_image
            self.image_display.Refresh()
        except Exception as e:
            wx.MessageBox(f'处理图片时出错: {str(e)}', '错误', wx.OK | wx.ICON_ERROR)

功能:

将 PIL 图片调整为适合显示区域的大小。

转换为 wx.Bitmap 后显示在 StaticBitmap 中。

图片旋转

def on_rotate(self, event):
    if self.current_pil_image:
        try:
            rotated_image = self.current_pil_image.rotate(-90, expand=True)
            self.update_image_display(rotated_image)
        except Exception as e:
            wx.MessageBox(f'旋转图片时出错: {str(e)}', '错误', wx.OK | wx.ICON_ERROR)
    else:
        wx.MessageBox('请先选择一张图片', '提示', wx.OK | wx.ICON_INFORMATION)

功能:

使用 Pillow.Image 的 rotate 方法实现顺时针旋转。

图片合并

def on_merge(self, event):
    if len(self.selected_images) < 2:
        wx.MessageBox('请至少选择两张图片', '提示', wx.OK | wx.ICON_INFORMATION)
        return

    max_width = 0
    total_height = 0
    images = []
    for img_path in self.selected_images:
        img = Image.open(img_path)
        max_width = max(max_width, img.width)
        total_height += img.height
        images.append(img)

    merged_image = Image.new('RGB', (max_width, total_height))
    current_height = 0
    for img in images:
        x_offset = (max_width - img.width) // 2
        merged_image.paste(img, (x_offset, current_height))
        current_height += img.height

    save_path = os.path.join(os.path.dirname(self.selected_images[0]), 'merged.jpg')
    merged_image.save(save_path)
    wx.MessageBox(f'图片已合并保存至: {save_path}', '成功', wx.OK | wx.ICON_INFORMATION)

功能:

  • 计算合并后图片的总尺寸。
  • 使用 Image.new 创建空白图片。
  • 将每张图片逐一粘贴。

图片压缩

def on_compress(self, event):
    merged_path = os.path.join(os.path.dirname(self.selected_images[0]), 'merged.jpg')
    if not os.path.exists(merged_path):
        wx.MessageBox('请先合并图片', '提示', wx.OK | wx.ICON_INFORMATION)
        return

    img = Image.open(merged_path)
    width = int(img.width * 0.5)
    height = int(img.height * 0.5)
    compressed_img = img.resize((width, height), Image.Resampling.LANCZOS)

    save_path = os.path.join(os.path.dirname(merged_path), 'compressed.jpg')
    compressed_img.save(save_path, quality=85, optimize=True)
    wx.MessageBox(f'压缩后的图片已保存至: {save_path}', '成功', wx.OK | wx.ICON_INFORMATION)

功能:

将合并后的图片尺寸缩小为原来的 50%。

设置压缩质量为 85%,并保存优化后的图片。

运行结果

到此这篇关于使用Python实现图片处理工具的文章就介绍到这了,更多相关Python图片处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 深入理解python中的atexit模块

    深入理解python中的atexit模块

    atexit模块很简单,只定义了一个register函数用于注册程序退出时的回调函数,我们可以在这个回调函数中做一些资源清理的操作。下面这篇文章主要介绍了python中atexit模块的相关资料,需要的朋友可以参考下。
    2017-03-03
  • Python使用Selenium实现按文本查找元素

    Python使用Selenium实现按文本查找元素

    本文我们将通过示例为大家详细介绍如何在Python中使用selenium通过文本查找元素的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下
    2023-11-11
  • 基于Python实现语音合成小工具

    基于Python实现语音合成小工具

    TTS(Text To Speech)是一种语音合成技术,可以让机器将输入文本以语音的方式播放出来,实现机器说话的效果。本文将使用pyttsx3库作为示范,编写一个语音合成小工具,感兴趣的可以了解一下
    2022-12-12
  • Python监控服务器实用工具psutil使用解析

    Python监控服务器实用工具psutil使用解析

    这篇文章主要介绍了Python监控服务器实用工具psutil使用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • django认证系统实现自定义权限管理的方法

    django认证系统实现自定义权限管理的方法

    这篇文章主要介绍了django认证系统实现自定义权限管理的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-07-07
  • 创建Python Docker镜像的详细步骤

    创建Python Docker镜像的详细步骤

    Python和Docker是两个极其流行的技术,结合它们可以创建强大的应用程序,Docker允许将应用程序及其依赖项打包到一个独立的容器中,而Python则提供了丰富的库和工具来开发应用程序,本文将提供如何创建Python Docker镜像的全面指南,,需要的朋友可以参考下
    2023-12-12
  • Python3读取文件常用方法实例分析

    Python3读取文件常用方法实例分析

    这篇文章主要介绍了Python3读取文件常用方法,以实例形式较为详细的分析了Python一次性读取、逐行读取及读取文件一部分的实现技巧,需要的朋友可以参考下
    2015-05-05
  • Python pandas DataFrame数据拼接方法

    Python pandas DataFrame数据拼接方法

    我们都知道在使用pandas处理数据的时候,往往会需要合并两个或者多个DataFrame的操作,下面这篇文章主要给大家介绍了关于Python pandas DataFrame数据拼接方法的相关资料,需要的朋友可以参考下
    2022-07-07
  • python机器学习sklearn实现识别数字

    python机器学习sklearn实现识别数字

    本文主要介绍了python机器学习sklearn实现识别数字,主要简述如何通过sklearn模块来进行预测和学习,最后再以图表这种更加直观的方式展现出来,感兴趣的可以了解一下
    2022-03-03
  • python感知机实现代码

    python感知机实现代码

    这篇文章主要为大家详细介绍了python感知机实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01

最新评论