当谈到图像查看和管理时,我们往往会使用一些工具软件,比如Windows自带的照片查看器或者第三方工具。那如果你想要一个更加强大和定制化的图像查看器呢?这时候就需要自己动手写一个程序了。
C:\pythoncode\new\ShowSqliteImage.py
这里我们将介绍一个使用Python和wxPython编写的图像查看器应用程序。它不仅可以查看图像,还支持缩放、旋转等基本操作,最酷的是它可以连接SQLite数据库,从中读取图像并计算图像的MD5哈希值,用于查找重复图像。
程序的界面非常简洁,顶部有一个文本框用于选择SQLite数据库文件,中间是一个列表框列出数据库中的所有图像名称。选择一个图像名称后,就会在右侧区域显示该图像。下方有几个按钮,可以对图像进行旋转、放大、缩小和重置等操作。还有一个独特的"Compare MD5"按钮,点击它就会计算当前图像的MD5哈希值,并在数据库中搜索是否有重复的图像。
代码的编写利用了wxPython这个跨平台的GUI库,使用Python的PIL库来处理图像。SQLite则用于存储图像数据和元数据。代码结构清晰,功能实现也很巧妙,是一个不错的wxPython编程实例。
完整代码:
import wx
import sqlite3
import os
import hashlib
from datetime import datetime
import io
from PIL import Image, ImageOpsclass ImageViewerApp(wx.Frame):def __init__(self, parent, title):super(ImageViewerApp, self).__init__(parent, title=title, size=(1000, 700))self.panel = wx.Panel(self)self.db_path = ""self.original_image = Noneself.init_ui()self.Centre()self.Show()def init_ui(self):vbox = wx.BoxSizer(wx.VERTICAL)# Database selectionhbox1 = wx.BoxSizer(wx.HORIZONTAL)self.db_path_text = wx.TextCtrl(self.panel)db_path_btn = wx.Button(self.panel, label='Select Database')db_path_btn.Bind(wx.EVT_BUTTON, self.on_select_database)hbox1.Add(self.db_path_text, proportion=1, flag=wx.EXPAND|wx.ALL, border=5)hbox1.Add(db_path_btn, flag=wx.ALL, border=5)vbox.Add(hbox1, flag=wx.EXPAND)hbox2 = wx.BoxSizer(wx.HORIZONTAL)# List of image namesself.image_list = wx.ListBox(self.panel)self.image_list.Bind(wx.EVT_LISTBOX, self.on_select_image)hbox2.Add(self.image_list, proportion=1, flag=wx.EXPAND|wx.ALL, border=5)# Image display area and controlsright_panel = wx.Panel(self.panel)right_sizer = wx.BoxSizer(wx.VERTICAL)self.image_display = wx.StaticBitmap(right_panel)right_sizer.Add(self.image_display, proportion=1, flag=wx.EXPAND|wx.ALL, border=5)btn_sizer = wx.BoxSizer(wx.HORIZONTAL)rotate_btn = wx.Button(right_panel, label='Rotate')rotate_btn.Bind(wx.EVT_BUTTON, self.on_rotate_image)btn_sizer.Add(rotate_btn, flag=wx.ALL, border=5)zoom_in_btn = wx.Button(right_panel, label='Zoom In')zoom_in_btn.Bind(wx.EVT_BUTTON, self.on_zoom_in_image)btn_sizer.Add(zoom_in_btn, flag=wx.ALL, border=5)zoom_out_btn = wx.Button(right_panel, label='Zoom Out')zoom_out_btn.Bind(wx.EVT_BUTTON, self.on_zoom_out_image)btn_sizer.Add(zoom_out_btn, flag=wx.ALL, border=5)reset_btn = wx.Button(right_panel, label='Reset')reset_btn.Bind(wx.EVT_BUTTON, self.on_reset_image)btn_sizer.Add(reset_btn, flag=wx.ALL, border=5)compare_btn = wx.Button(right_panel, label='Compare MD5')compare_btn.Bind(wx.EVT_BUTTON, self.on_compare_md5)btn_sizer.Add(compare_btn, flag=wx.ALL, border=5)right_sizer.Add(btn_sizer, flag=wx.ALL|wx.CENTER, border=10)right_panel.SetSizer(right_sizer)hbox2.Add(right_panel, proportion=2, flag=wx.EXPAND|wx.ALL, border=5)vbox.Add(hbox2, proportion=1, flag=wx.EXPAND)self.panel.SetSizer(vbox)def on_select_database(self, event):with wx.FileDialog(self, "Choose SQLite database file", wildcard="SQLite files (*.db)|*.db|All files (*.*)|*.*", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:if fileDialog.ShowModal() == wx.ID_CANCEL:returnself.db_path = fileDialog.GetPath()self.db_path_text.SetValue(self.db_path)self.load_image_names()def load_image_names(self):if not self.db_path:wx.MessageBox('Database path is required', 'Error', wx.OK | wx.ICON_ERROR)returnconn = sqlite3.connect(self.db_path)cursor = conn.cursor()cursor.execute("SELECT picname FROM pics")rows = cursor.fetchall()self.image_list.Clear()for row in rows:self.image_list.Append(row[0])conn.close()def on_select_image(self, event):selected_image = self.image_list.GetString(self.image_list.GetSelection())conn = sqlite3.connect(self.db_path)cursor = conn.cursor()cursor.execute("SELECT pic, picmd5, picdate FROM pics WHERE picname = ?", (selected_image,))row = cursor.fetchone()if row:pic_data, picmd5, picdate = rowself.original_image = Image.open(io.BytesIO(pic_data))self.display_image(self.original_image)self.SetTitle(f"MD5: {picmd5} | Date: {picdate}")conn.close()def display_image(self, image):wx_image = wx.Image(image.size[0], image.size[1])wx_image.SetData(image.convert("RGB").tobytes())bitmap = wx.Bitmap(wx_image)self.image_display.SetBitmap(bitmap)self.panel.Layout()def on_rotate_image(self, event):if self.original_image:self.original_image = self.original_image.rotate(90, expand=True)self.display_image(self.original_image)def on_zoom_in_image(self, event):if self.original_image:width, height = self.original_image.sizeself.original_image = self.original_image.resize((width + int(width * 0.1), height + int(height * 0.1)), Image.ANTIALIAS)self.display_image(self.original_image)def on_zoom_out_image(self, event):if self.original_image:width, height = self.original_image.sizeself.original_image = self.original_image.resize((width - int(width * 0.1), height - int(height * 0.1)), Image.ANTIALIAS)self.display_image(self.original_image)def on_reset_image(self, event):if self.original_image:self.load_image_names()self.original_image = Noneself.image_display.SetBitmap(wx.NullBitmap)self.SetTitle("Image Viewer")def on_compare_md5(self, event):if self.original_image:img_byte_arr = io.BytesIO()self.original_image.save(img_byte_arr, format='PNG')md5_hash = hashlib.md5(img_byte_arr.getvalue()).hexdigest()conn = sqlite3.connect(self.db_path)cursor = conn.cursor()cursor.execute("SELECT picname FROM pics WHERE picmd5 = ?", (md5_hash,))row = cursor.fetchone()if row:wx.MessageBox(f"Image with MD5 {md5_hash} found: {row[0]}", 'MD5 Match', wx.OK | wx.ICON_INFORMATION)else:wx.MessageBox(f"No image with MD5 {md5_hash} found.", 'MD5 Match', wx.OK | wx.ICON_INFORMATION)conn.close()if __name__ == '__main__':app = wx.App(False)frame = ImageViewerApp(None, "Image Viewer")app.MainLoop()
好的,我们来看一下这个图像查看器的主要代码部分:
def on_select_image(self, event):# 获取选中的图像名称selected_image = self.image_list.GetString(self.image_list.GetSelection())conn = sqlite3.connect(self.db_path)cursor = conn.cursor()# 从数据库中查询该图像的数据和元数据cursor.execute("SELECT pic, picmd5, picdate FROM pics WHERE picname = ?", (selected_image,))row = cursor.fetchone()if row:pic_data, picmd5, picdate = row# 从字节数据创建PIL Image对象self.original_image = Image.open(io.BytesIO(pic_data))self.display_image(self.original_image)self.SetTitle(f"MD5: {picmd5} | Date: {picdate}")conn.close()def display_image(self, image):# 将PIL Image对象转换为wxPython可显示的位图wx_image = wx.Image(image.size[0], image.size[1])wx_image.SetData(image.convert("RGB").tobytes())bitmap = wx.Bitmap(wx_image)self.image_display.SetBitmap(bitmap)self.panel.Layout()
这部分代码是在选择了一个图像后执行的。首先从列表框获取选中的图像名称,然后连接到SQLite数据库,使用SQL查询语句从pics表中获取该图像名称对应的图像数据(pic字段)、MD5哈希值(picmd5)和日期(picdate)。
接着使用PIL库的Image.open()方法从字节数据创建一个Image对象,就可以对该图像进行后续的操作了。display_image()函数则负责将PIL Image对象转换为wxPython可以显示的位图,并设置到GUI的StaticBitmap控件上。
def on_rotate_image(self, event):if self.original_image:self.original_image = self.original_image.rotate(90, expand=True)self.display_image(self.original_image)
这段代码实现了图像旋转功能。利用PIL的Image.rotate()方法可以将图像按指定角度旋转,expand=True表示可以扩展输出尺寸以适应旋转后的图像。
def on_compare_md5(self, event):if self.original_image:img_byte_arr = io.BytesIO()self.original_image.save(img_byte_arr, format='PNG')md5_hash = hashlib.md5(img_byte_arr.getvalue()).hexdigest()conn = sqlite3.connect(self.db_path)cursor = conn.cursor()cursor.execute("SELECT picname FROM pics WHERE picmd5 = ?", (md5_hash,))row = cursor.fetchone()if row:wx.MessageBox(f"Image with MD5 {md5_hash} found: {row[0]}", 'MD5 Match', wx.OK | wx.ICON_INFORMATION)else:wx.MessageBox(f"No image with MD5 {md5_hash} found.", 'MD5 Match', wx.OK | wx.ICON_INFORMATION)conn.close()
这是一个非常巧妙的功能,可以根据计算出的MD5哈希值在数据库中查找是否有重复的图像。首先将当前图像保存到字节IO流中,然后使用hashlib计算该字节流的MD5哈希值。接着连接数据库,执行SQL查询语句查找pics表中是否有相同MD5哈希值的记录。根据查询结果,弹出不同的MessageBox提示信息。
结果如下:
通过这些代码,我们可以看到作者利用了Python的多个强大库和SQLite数据库,实现了一个既实用又有创意的小程序。代码写得很优雅,值得我们学习和借鉴。