Skip to content

Instantly share code, notes, and snippets.

@bczhc
Created December 11, 2025 08:00
Show Gist options
  • Select an option

  • Save bczhc/ab52f4f33ccdb6cd579f00fb2e1b0566 to your computer and use it in GitHub Desktop.

Select an option

Save bczhc/ab52f4f33ccdb6cd579f00fb2e1b0566 to your computer and use it in GitHub Desktop.
用ChatGPT写的垃圾性能图片查看器,在标题栏显示ev100值 #vibecoding #photography #image
import os
import sys
import tkinter as tk
from tkinter import Label, messagebox
from PIL import Image, ImageTk
import piexif
class ImageViewer:
def _on_mousewheel(self, event):
# Windows/macOS: event.delta positive = up, negative = down
# Linux: Button-4 = up, Button-5 = down
delta = 0
if hasattr(event, 'delta') and event.delta != 0:
delta = event.delta
else:
if event.num == 4:
delta = 1
elif event.num == 5:
delta = -1
if delta > 0:
self.prev_image()
else:
self.next_image()
def __init__(self, root, target_path):
self.root = root
self.target_path = target_path
# Gather files (non-recursive), sort them, then decide which one to show
if os.path.isdir(target_path):
folder = target_path
files = [f for f in os.listdir(folder) if f.lower().endswith('.jpg')]
files.sort()
if not files:
messagebox.showerror("错误", f"目录 {folder} 中没有找到 jpg 文件")
root.destroy()
return
self.files = files
self.folder = folder
self.index = 0 # 目录输入时显示排序后的第一个
else:
folder = os.path.dirname(target_path) or '.'
files = [f for f in os.listdir(folder) if f.lower().endswith('.jpg')]
files.sort()
if not files:
messagebox.showerror("错误", f"{folder} 中没有找到 jpg 文件")
root.destroy()
return
basename = os.path.basename(target_path)
if basename not in files:
# 传入的文件不是 jpg 或不在同一目录的文件列表内
messagebox.showwarning("警告", f"指定文件 {basename} 未在目录 {folder} 的 jpg 列表中,默认使用第一个文件")
self.index = 0
else:
self.index = files.index(basename)
self.files = files
self.folder = folder
# 窗口尺寸为屏幕大小的一半
screen_w = root.winfo_screenwidth()
screen_h = root.winfo_screenheight()
root.geometry(f"{screen_w//2}x{screen_h//2}")
self.label = Label(root)
self.label.pack(fill=tk.BOTH, expand=True)
root.bind('<Left>', lambda e: self.prev_image())
root.bind('<Right>', lambda e: self.next_image())
root.bind('<Configure>', lambda e: self._on_configure())
root.bind('q', lambda e: root.destroy())
root.bind('<MouseWheel>', lambda e: self._on_mousewheel(e)) # Windows/macOS
root.bind('<Button-4>', lambda e: self._on_mousewheel(e)) # Linux scroll up
root.bind('<Button-5>', lambda e: self._on_mousewheel(e)) # Linux scroll down
self.original_img = None
self.exif = {}
self.tk_img = None
self.show_image()
def load_image(self):
path = os.path.join(self.folder, self.files[self.index])
try:
self.original_img = Image.open(path)
try:
self.exif = piexif.load(path)
except Exception:
self.exif = {}
except Exception as e:
messagebox.showerror("错误", f"不能打开图片: {path} {e}")
self.original_img = None
self.exif = {}
except Exception as e:
messagebox.showerror("错误", f"不能打开图片: {path}\n{e}")
self.original_img = None
self.exif = {}
def compute_ev100(self):
try:
if not self.exif or "Exif" not in self.exif:
return "N/A"
ex = self.exif["Exif"]
def to_float(v):
try:
if isinstance(v, tuple) and len(v) == 2:
num, den = v
return num / den if den else None
return float(v)
except:
return None
t = to_float(ex.get(piexif.ExifIFD.ExposureTime))
n = to_float(ex.get(piexif.ExifIFD.FNumber))
iso = ex.get(piexif.ExifIFD.ISOSpeedRatings)
if isinstance(iso, (list, tuple)):
iso = iso[0]
try:
iso = float(iso)
except:
iso = None
if not t or not n or not iso or t <= 0 or iso <= 0:
return "N/A"
import math
ev100 = math.log2((n * n) / t) - math.log2(iso / 100.0)
return f"{ev100:.2f}"
except Exception:
return "N/A"
# EXIF tags (numeric): 33434 ExposureTime, 33437 FNumber, 34855 ISOSpeedRatings
t = exif.get(33434)
n = exif.get(33437)
iso = exif.get(34855)
# EXIF values sometimes are rational tuples
def to_float(v):
if v is None:
return None
if isinstance(v, tuple) and len(v) == 2 and v[1] != 0:
return float(v[0]) / float(v[1])
try:
return float(v)
except Exception:
return None
t = to_float(t)
n = to_float(n)
iso = to_float(iso)
if t is None or n is None or iso is None or t <= 0 or iso <= 0:
return "N/A"
import math
ev100 = math.log2((n * n) / t) - math.log2(iso / 100.0)
return f"{ev100:.2f}"
except Exception:
return "N/A"
def update_title(self):
name = self.files[self.index]
ev = self.compute_ev100()
self.root.title(f"{self.index+1}/{len(self.files)} {name} EV100={ev}")
def show_image(self):
self.load_image()
self.update_title()
self.resize_image()
def resize_image(self):
if self.original_img is None:
return
# 获取当前可用显示区域
w = self.label.winfo_width()
h = self.label.winfo_height()
if w < 10 or h < 10:
return
# 按比例缩放,使图片完全适应区域(letterbox),保持长宽比
img = self.original_img.copy()
img_ratio = img.width / img.height
area_ratio = w / h
if img_ratio > area_ratio:
# 以宽度为准
new_w = w
new_h = int(w / img_ratio)
else:
# 以高度为准
new_h = h
new_w = int(h * img_ratio)
img = img.resize((max(1, new_w), max(1, new_h)), Image.LANCZOS)
self.tk_img = ImageTk.PhotoImage(img)
self.label.config(image=self.tk_img)
def _on_configure(self):
# 窗口大小改变时更新图片显示和标题(标题里也显示当前图片索引)
self.resize_image()
self.update_title()
def prev_image(self):
if not self.files:
return
self.index = (self.index - 1) % len(self.files)
self.show_image()
def next_image(self):
if not self.files:
return
self.index = (self.index + 1) % len(self.files)
self.show_image()
if __name__ == '__main__':
root = tk.Tk()
target_path = sys.argv[1] if len(sys.argv) > 1 else '.'
viewer = ImageViewer(root, target_path)
# 如果初始化失败(例如没有图片),ImageViewer 会调用 root.destroy()
try:
root.mainloop()
except Exception:
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment