Calculate height and width of GIF/JPG files

博客给出了一个名为ImageSize的函数,用于根据文件路径获取GIF或JPG图像的宽度和高度。针对GIF和JPG格式采用不同方式定位尺寸信息,还给出了GIFFile类克隆ImageSize函数逻辑,并添加了GIF格式额外检查以检测不准确的扩展名或类型。

这个是转载
Function ImageSize(fileName As String) As Variant
   ' Given a source file name (path to the GIF or JPG on disk), return an array containing
   ' the width (1st element) and height (2nd element).
   Dim retVal As Variant
   Dim header As String
   Dim f As Integer
   Dim wHi As Variant
   Dim wLo As Variant
   Dim hHi As Variant
   Dim hLo As Variant
   Dim w As Integer ' width of image
   Dim h As Integer ' height of image
   Dim foundMarker As Integer
   
   Redim retVal(2) As Integer
   Redim retVal(Lbound(retVal)+1)     ' Size it so there's 2 entries
   retVal(Lbound(retVal)) = 0
   retVal(Ubound(retVal)) = 0
   f = Freefile()
   On Error Resume Next
   Open fileName For Input As #f
   On Error Goto 0
   If Err <> 0 Then
      ImageSize = retVal  ' File name incorrect - return zero for both the height and width
      Exit Function
   End If
   If Lcase(Right(fileName, 3)) = "gif" Then
      ' GIF's height and width stored in a fixed location
      header = Input(10, f)
      wHi = Mid(header, 8, 1)
      wLo = Mid(header, 7, 1)
      hHi = Mid(header, 10, 1)
      hLo = Mid(header, 9, 1)
      w = Asc(wHi) * 256 + Asc(wLo)
      h = Asc(hHi) * 256 + Asc(hLo)
   Elseif Lcase(Right(fileName, 3)) = "jpg" Then
      ' JPG's stored in a variable location. The code has been verified with JFIF
      ' file format (the most common format)
      On Error Goto EndOfFile     ' In case we run over the file for some reason
      header = Input(2, f)
      If header = Chr$(255) & Chr$(216) Then   ' Must start with hex FF D8
         foundMarker = False   ' Look for the marker that will contain the height and width
         While Not foundMarker
            header = Input(2, f)    ' Grab the next marker
            ' Look for the marker (in hex) FF C0, FF C1, FF C2, or FF C3
            If header = Chr$(255) & Chr$(192) Or header = Chr$(255) & Chr$(193) _
            Or header = Chr$(255) & Chr$(194) Or header = Chr$(255) & Chr$(195) Then
               ' Next two bytes are the length, then a single byte that can be ignored.
               header = Input(3, f)
               ' Next two bytes are the height of the image
               header = Input(2, f)
               hHi = Asc(Midbp(header, 1, 1))
               hLo = Asc(Midbp(header, 2, 1))
               h = hHi * 256 + hLo
               ' Next two bytes are the width of the image
               header = Input(2, f)
               wHi = Asc(Midbp(header, 1, 1))
               wLo = Asc(Midbp(header, 2, 1))
               w = wHi * 256 + wLo
               foundMarker = True     ' Exit the while loop
            Else   ' It's not one of the special markers - skip over it
               header = Input(2, f)   ' Next two bytes are the marker length
               wHi = Asc(Midbp(header, 1, 1))
               wLo = Asc(Midbp(header, 2, 1))
               w = wHi * 256 + wLo
               header = Input(w-2, f) ' Skip over that many bytes (minus the 2 byte length already read)
               w = 0   ' Clear the variable
            End If
         Wend   ' Continue until the marker is found
      End If    ' Ends the check to see if the file starts with FF D8
EndOfFile:
      If Err <> 0 Then
         Err = 0
         Resume AfterError
      End If
   End If   ' Ends the check to see if the format is GIF or JPG
AfterError:
   retVal(Lbound(retVal)) = w
   retVal(Ubound(retVal)) = h
   Close #f
   ImageSize = retVal
End Function


Here's a sample GIFFile class cloning ImageSize() routine original logic:

Private Const GIF_HEADER_LENGTH = 10
Private Const GIF_MARKER = "GIF"
Private Const GIF_ID1 = "87a"
Private Const GIF_ID2= "89a"

Private Class GIFFile

Private m_w As Integer
Private m_h As Integer

Public Property Set fileName As String
Dim h ' GIF file Header: "GIF87a" or GIF89a" followed by logical width & height
h = Me.Header ' Let's check GIF format presence..
If ( Left$( h, 3 ) <> GIF_MARKER ) Then Error 1000, _
|Not a GIF file: Graphical Interchange File "GIF" marker not found|
If ( Mid$( h, 4, 3 ) <> GIF_ID1 And Mid$( h, 4, 3 ) <> GIF_ID2 ) Then Error 1002, _
|Not a GIF file: Graphical Interchange File "87a/89a" identifier not found|
m_w = Asc( Mid( h, 8, 1 ) ) * 256 + Asc( Mid( h, 7, 1 ) ) ' Little-endian Screen Width
m_h = Asc( Mid( h, 10, 1 ) ) * 256 + Asc( Mid( h, 9, 1 ) ) ' Little-endian Screen Height
End Property
Private Property Get Header As Variant
Dim h As Integer
h% = Freefile()
Open Me.Name For Input Shared As #h
Header = Input( GIF_HEADER_LENGTH, #h )
Close #h
End Property
Public Property Get Heigth As Integer
Heigth = m_h
End Property
Public Property Get Width As Integer
Me.Width = m_w
End Property

Public Sub new( fileName As String )
Me.FileName = fileName
End Sub

End Class

I have added GIF format additional checks intended to detect files holding inaccurate extension/type

这段代码第一次拼接预览的时候没有居中显示,帮我变成居中显示 import os import re import sys from PIL import Image, ImageTk import glob import tkinter as tk from tkinter import filedialog, messagebox, ttk import threading class PhotoGridApp: def __init__(self, root): self.root = root self.root.title("图片网格拼接工具") self.root.geometry("1000x800") self.root.minsize(800, 600) self.root.resizable(True, True) # 样式设置 self.style = ttk.Style() self.font_size = 10 self.font_family = "Arial" self.style.configure("TLabel", font=(self.font_family, self.font_size)) self.style.configure("TButton", font=(self.font_family, self.font_size)) self.style.configure("TEntry", font=(self.font_family, self.font_size)) self.style.configure("TLabelframe", font=(self.font_family, self.font_size)) self.style.configure("TLabelframe.Label", font=(self.font_family, self.font_size)) # 主框架 self.main_frame = ttk.Frame(root, padding="20") self.main_frame.pack(fill=tk.BOTH, expand=True) # 源文件夹选择 ttk.Label(self.main_frame, text="源文件夹:").grid(row=0, column=0, sticky=tk.W, pady=5) self.source_folder_var = tk.StringVar() ttk.Entry(self.main_frame, textvariable=self.source_folder_var, width=70).grid(row=0, column=1, sticky=tk.W, pady=5) ttk.Button(self.main_frame, text="浏览...", command=self.browse_source_folder).grid(row=0, column=2, padx=5, pady=5) # 输出文件夹选择 ttk.Label(self.main_frame, text="输出文件夹:").grid(row=1, column=0, sticky=tk.W, pady=5) self.output_folder_var = tk.StringVar() ttk.Entry(self.main_frame, textvariable=self.output_folder_var, width=70).grid(row=1, column=1, sticky=tk.W, pady=5) ttk.Button(self.main_frame, text="浏览...", command=self.browse_output_folder).grid(row=1, column=2, padx=5, pady=5) # 网格方案选择 ttk.Label(self.main_frame, text="网格方案:").grid(row=2, column=0, sticky=tk.W, pady=5) self.grid_option_var = tk.StringVar() self.grid_option_combobox = ttk.Combobox(self.main_frame, textvariable=self.grid_option_var, width=15, state="readonly") self.grid_option_combobox.grid(row=2, column=1, sticky=tk.W, pady=5) self.grid_option_combobox.bind("<<ComboboxSelected>>", self.on_grid_option_selected) # 网格设置 grid_frame = ttk.LabelFrame(self.main_frame, text="网格设置") grid_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10) ttk.Label(grid_frame, text="列数:").grid(row=0, column=0, sticky=tk.W, padx=10, pady=5) self.cols_var = tk.IntVar(value=3) ttk.Entry(grid_frame, textvariable=self.cols_var, width=5).grid(row=0, column=1, sticky=tk.W, pady=5) ttk.Label(grid_frame, text="行数:").grid(row=0, column=2, sticky=tk.W, padx=10, pady=5) self.rows_var = tk.IntVar(value=3) ttk.Entry(grid_frame, textvariable=self.rows_var, width=5).grid(row=0, column=3, sticky=tk.W, pady=5) # 图片预览区域 preview_frame = ttk.LabelFrame(self.main_frame, text="拼接预览") preview_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10) preview_frame.rowconfigure(0, weight=1) preview_frame.columnconfigure(0, weight=1) # 带滚轮的画布 self.canvas = tk.Canvas(preview_frame, bg="#f0f0f0", highlightthickness=0) scrollbar_y = ttk.Scrollbar(preview_frame, orient="vertical", command=self.canvas.yview) scrollbar_x = ttk.Scrollbar(preview_frame, orient="horizontal", command=self.canvas.xview) self.canvas.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set) # 布局滚动条和画布 scrollbar_y.pack(side="right", fill="y") scrollbar_x.pack(side="bottom", fill="x") self.canvas.pack(side="left", fill="both", expand=True) # 居中容器框架 self.center_frame = ttk.Frame(self.canvas) self.canvas_window = self.canvas.create_window((0, 0), window=self.center_frame, anchor="center") # 预览内容框架 self.preview_content = ttk.Frame(self.center_frame) self.preview_content.pack(anchor="center", padx=5, pady=5) # 绑定事件 self.center_frame.bind("<Configure>", self.on_center_frame_configure) self.canvas.bind("<Configure>", self.on_canvas_configure) self.canvas.bind_all("<MouseWheel>", self.on_mousewheel) # Windows self.canvas.bind_all("<Button-4>", self.on_mousewheel) # Linux self.canvas.bind_all("<Button-5>", self.on_mousewheel) # Linux # 状态和进度条 self.status_var = tk.StringVar(value="就绪") ttk.Label(self.main_frame, textvariable=self.status_var).grid(row=5, column=0, columnspan=3, sticky=tk.W, pady=5) self.progress_var = tk.DoubleVar(value=0) self.progress_bar = ttk.Progressbar(self.main_frame, variable=self.progress_var, maximum=100) self.progress_bar.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5) # 操作按钮 button_frame = ttk.Frame(self.main_frame) button_frame.grid(row=7, column=0, columnspan=3, pady=10) ttk.Button(button_frame, text="预览拼接", command=self.preview_grid).grid(row=0, column=0, padx=10) ttk.Button(button_frame, text="保存拼接", command=self.save_grid).grid(row=0, column=1, padx=10) ttk.Button(button_frame, text="退出", command=root.quit).grid(row=0, column=2, padx=10) # 图片相关变量 self.image_files = [] self.photo_grid = None self.grid_photo = None self.grid_label = None self.grid_options = [] # 设置权重 self.main_frame.rowconfigure(4, weight=1) self.main_frame.columnconfigure(1, weight=1) # 绑定窗口大小变化事件 self.root.bind("<Configure>", self.on_main_window_resize) def browse_source_folder(self): folder = filedialog.askdirectory(title="选择源文件夹") if folder: self.source_folder_var.set(folder) self.calculate_grid_options() def calculate_grid_options(self): source_folder = self.source_folder_var.get() if not source_folder or not os.path.isdir(source_folder): return image_files = glob.glob(os.path.join(source_folder, '*')) self.image_files = [f for f in image_files if f.lower().endswith( ('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.webp', '.tiff', '.tif') )] num_images = len(self.image_files) if num_images == 0: self.status_var.set("提示: 所选文件夹中未找到图片文件") self.grid_option_combobox['values'] = [] self.grid_options = [] return self.status_var.set(f"找到 {num_images} 张图片") self.grid_options = [] max_dim = int(num_images ** 0.5) + 2 for cols in range(1, max_dim + 1): rows = (num_images + cols - 1) // cols self.grid_options.append((cols, rows)) if cols != rows: cols2, rows2 = rows, cols if cols2 * rows2 >= num_images: self.grid_options.append((cols2, rows2)) self.grid_options = list(set(self.grid_options)) self.grid_options.sort(key=lambda x: (x[0] * x[1], abs(x[0] - x[1]))) self.grid_options = self.grid_options[:10] option_texts = [f"{c}列 × {r}行" for c, r in self.grid_options] self.grid_option_combobox['values'] = option_texts if option_texts: self.grid_option_combobox.current(0) self.on_grid_option_selected(None) def on_grid_option_selected(self, event): if not self.grid_options: return selected_index = self.grid_option_combobox.current() if selected_index >= 0 and selected_index < len(self.grid_options): cols, rows = self.grid_options[selected_index] self.cols_var.set(cols) self.rows_var.set(rows) def browse_output_folder(self): folder = filedialog.askdirectory(title="选择输出文件夹") if folder: self.output_folder_var.set(folder) def natural_sort_key(self, s): filename = os.path.basename(s) return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', filename)] def create_photo_grid(self, for_preview=True): try: source_folder = self.source_folder_var.get() cols = self.cols_var.get() rows = self.rows_var.get() if not source_folder or not os.path.isdir(source_folder): self.status_var.set("错误: 请选择有效的源文件夹") return None if cols <= 0 or rows <= 0: self.status_var.set("错误: 列数和行数必须大于0") return None if not self.image_files: image_files = glob.glob(os.path.join(source_folder, '*')) self.image_files = [f for f in image_files if f.lower().endswith( ('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.webp', '.tiff', '.tif') )] if not self.image_files: self.status_var.set("错误: 在所选文件夹中未找到图片文件") return None self.image_files.sort(key=self.natural_sort_key) total_images = cols * rows if len(self.image_files) < total_images: self.status_var.set(f"提示: 需要 {total_images} 张图片,但只找到 {len(self.image_files)} 张") total_images = len(self.image_files) try: with Image.open(self.image_files[0]) as img: img_width, img_height = img.size except Exception as e: self.status_var.set(f"错误: 无法打开第一张图片: {e}") return None canvas_width = img_width * cols canvas_height = img_height * rows canvas = Image.new('RGB', (canvas_width, canvas_height)) total_files = len(self.image_files[:total_images]) for index, image_path in enumerate(self.image_files[:total_images]): try: with Image.open(image_path) as img: if img.size != (img_width, img_height): img = img.resize((img_width, img_height)) row = index // cols col = index % cols x = col * img_width y = row * img_height canvas.paste(img, (x, y)) if for_preview: progress = (index + 1) / total_files * 100 self.root.after(0, lambda p=progress: self.progress_var.set(p)) self.root.after(0, lambda s=f"处理中: {index + 1}/{total_files} - {os.path.basename(image_path)}": self.status_var.set( s)) except Exception as e: self.status_var.set(f"跳过无法处理的图片: {os.path.basename(image_path)} - {e}") except Exception as e: self.status_var.set(f"错误: {str(e)}") messagebox.showerror("错误", f"处理过程中发生错误:\n{str(e)}") return None if for_preview: self.root.after(0, lambda: self.progress_var.set(100)) self.root.after(0, lambda: self.status_var.set(f"预览已生成")) return canvas def preview_grid(self): self.status_var.set("生成预览...") self.progress_var.set(0) for widget in self.preview_content.winfo_children(): widget.destroy() self.grid_label = None self.grid_photo = None thread = threading.Thread(target=self._preview_grid_thread) thread.daemon = True thread.start() def _preview_grid_thread(self): self.photo_grid = self.create_photo_grid(for_preview=True) if self.photo_grid: # 使用延迟执行确保UI完全渲染后再更新预览 self.root.after(100, self.update_preview_size) def on_main_window_resize(self, event=None): if self.photo_grid and event and event.widget == self.root: self.update_preview_size() def update_preview_size(self): if not self.photo_grid: return # 强制刷新尺寸信息 self.canvas.update_idletasks() canvas_width = self.canvas.winfo_width() canvas_height = self.canvas.winfo_height() if canvas_width <= 0: canvas_width = 800 if canvas_height <= 0: canvas_height = 500 img_width, img_height = self.photo_grid.size ratio = min(canvas_width / img_width, canvas_height / img_height) max_ratio = 1.0 min_ratio = 0.1 ratio = max(min(ratio, max_ratio), min_ratio) new_size = (int(img_width * ratio), int(img_height * ratio)) preview_img = self.photo_grid.resize(new_size, Image.LANCZOS) # 使用after确保在下一事件循环中执行,确保UI已准备好 self.root.after(0, self._update_preview_ui, preview_img) def _update_preview_ui(self, preview_img): self.grid_photo = ImageTk.PhotoImage(preview_img) if self.grid_label: self.grid_label.config(image=self.grid_photo) else: self.grid_label = ttk.Label(self.preview_content, image=self.grid_photo) self.grid_label.pack() # 强制刷新布局并居中 self.center_frame.update_idletasks() self.canvas.update_idletasks() # 首次预览使用延迟执行确保居中 if not hasattr(self, 'first_preview_done'): self.root.after(50, self.center_canvas_content) self.first_preview_done = True else: self.center_canvas_content() def center_canvas_content(self): # 强制更新尺寸 self.canvas.update_idletasks() self.center_frame.update_idletasks() canvas_width = self.canvas.winfo_width() canvas_height = self.canvas.winfo_height() frame_width = self.center_frame.winfo_width() frame_height = self.center_frame.winfo_height() # 计算偏移量 x = (canvas_width - frame_width) / 2 y = (canvas_height - frame_height) / 2 # 移动窗口到计算出的位置 self.canvas.coords(self.canvas_window, x, y) self.canvas.configure(scrollregion=self.canvas.bbox("all")) def on_center_frame_configure(self, event): self.canvas.configure(scrollregion=self.canvas.bbox("all")) self.center_canvas_content() def on_canvas_configure(self, event): # 画布尺寸变化时重新居中 self.center_canvas_content() # 如果有图片,调整大小 if self.grid_label: self.update_preview_size() def on_mousewheel(self, event): # 垂直滚动 if event.num == 5 or event.delta < 0: self.canvas.yview_scroll(1, "units") elif event.num == 4 or event.delta > 0: self.canvas.yview_scroll(-1, "units") # 水平滚动(Shift键) if event.state & 0x10: if event.num == 5 or event.delta < 0: self.canvas.xview_scroll(1, "units") elif event.num == 4 or event.delta > 0: self.canvas.xview_scroll(-1, "units") def save_grid(self): if not self.photo_grid: messagebox.showinfo("提示", "请先预览拼接效果") return output_folder = self.output_folder_var.get() or os.getcwd() if not os.path.exists(output_folder): os.makedirs(output_folder) total_images = len(self.image_files) output_filename = f"{total_images}格.jpg" output_path = os.path.join(output_folder, output_filename) counter = 1 while os.path.exists(output_path): output_filename = f"{total_images}_{counter}.jpg" output_path = os.path.join(output_folder, output_filename) counter += 1 try: self.photo_grid.save(output_path) self.status_var.set(f"成功保存图片网格: {output_path}") messagebox.showinfo("成功", f"图片网格已成功保存!\n位置: {output_path}") except Exception as e: self.status_var.set(f"错误: 无法保存图片 - {str(e)}") messagebox.showerror("错误", f"保存图片时发生错误:\n{str(e)}") def main(): root = tk.Tk() app = PhotoGridApp(root) root.mainloop() if __name__ == "__main__": main()
最新发布
07-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值