当谈到图像查看和管理时,我们往往会使用一些工具软件,比如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, ImageOps
class 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 = None
self.init_ui()
self.Centre()
self.Show()
def init_ui(self):
vbox = wx.BoxSizer(wx.VERTICAL)
# Database selection
hbox1 = 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 names
self.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 controls
right_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:
return
self.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)
return
conn = 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 = row
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):
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.size
self.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.size
self.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 = None
self.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数据库,实现了一个既实用又有创意的小程序。代码写得很优雅,值得我们学习和借鉴。