File Column 的验证,中文提示,容易出错的细节和图片位置的获得

本文介绍如何使用 FileColumn 进行图片上传,并详细解释了如何验证文件格式、文件大小及图片尺寸。同时提供了常见错误处理方法及图片路径获取技巧。
部署运行你感兴趣的模型镜像
[color=red]先说验证问题。[/color]
按照传统的file_column :image, :magick => xxx
无论你想不想验证,file_column 都会验证上传的文件是不是图形文件,如果不是,它会在你的模型错误里面加"invalid image"。该错误验证的地方在 magick_file_column.rb里。查找

if options[:magick][:image_required]
@magick_errors ||= []
@magick_errors << "invalid image"
end

可以将第3行修改为:

@magick_errors << (options[:error_message] || "invalid image")

这样,就可以在file_column 时加上 :error_message来提示非图形文件的错误信息。

然后,file column自带了3个错误验证方法:

validates_file_format_of 验证文件类型,也就是后缀名。options是:in
比如验证后缀为jpg png gif的图形文件可以

validates_file_format_of :image, :in => ["gif", "png", "jpg"]

出错信息默认:"is not a valid format."
想修改为中文错误信息,可以修改validations.rb中的validates_file_format_of方法。
查找:
record.errors.add attr, "is not a valid format." unless extensions.include?(value.scan(EXT_REGEXP).flatten.first)
这一行
将其中的字符串改为你想要的中文即可,也可以用option的方法。我是改成。

options[:message] = [options[:messge]].to_s || "is not a valid format."
record.errors.add attr, options[:message] unless extensions.include?(value.scan(EXT_REGEXP).flatten.first)

这样就可以使用

validates_file_format_of :image, :in => ["gif", "png", "jpg"], :message => "只能是后缀为jpg, png, gif的文件"


validates_filesize_of 验证文件大小,options也是:in
比如验证1M的文件

validates_filesize_of :image, :in => 0..1.megabyte

错误信息分 太大 和 太小 两个。默认信息还是查看文件validations.rb的validates_filesize_of方法。可以参照validates_file_format_of的方法修改错误提示,改为我们习惯的:too_small和:too_large

第3个方法是验证图片大小 validates_image_size,是验证XxY的。不是文件大小。options只有:min。中文化参照前两个。

[color=red]再来说说file column使用中容易出错的地方。[/color]
1.需要有数据库表对应字段。用来存文件名的
2.file_column_field和url_for_file_column 中如果用Symbol出错,最好都写成字符串,比如缩略图要用'thumb'不要:thumb
3.validates_filesize_of :image, :in => 0..1.megabyte 不会判空,所以 validates_present_of :image是必须的
4.上传的图片要resize,要在:magick里加:geometry,不要写到:versions里面了。
5.上传中文图片是乱码的问题,虽然注释掉filename.gsub!(/[^a-zA-Z0-9\.\-\+_]/,"_")可以,但是还是会不定期出现特殊问题,所以最好的是上传后修改文件名,在file_column.rb中搜索@filename = FileColumn::sanitize_filename(file.original_filename),将file.original_filename改成你想要的名字。

[color=red]图片位置的获取:[/color]
用一个头像Portrait举例,User has_one Portrait

class Portrait < ActiveRecord::Base
belongs_to :user
file_column :image, :magick => {
:versions => { "thumb" => "72x72", "icon" => "36x36>"},
:geometry => "150x150>", :error_message => "不是图形文件"
}
validates_filesize_of :image, :in => 0..1.megabyte, :too_large => "不能大于1M“
validates_file_format_of :image, :in => ["gif", "png", "jpg"], :message => "必须是后缀为jpg png gif的图形文件"
validates_presence_of :image
end

我们想在User里直接得到头像的原图,缩略图和图标,可以这样

class User < ActiveRecord::Base
has_one :portrait

def main_portrait
"/portrait/image/" + portrait.send("image_relative_path")
end

def thumbnail
"/portrait/image/" + portrait.send("image_relative_path", "thumb")
end

def icon
"/portrait/image/" + portrait.send("image_relative_path", "icon")
end
end

这样就可以直接使用:
<%= image_tag @user.thumbnail %>

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

import os import requests import xlwings as xw from io import BytesIO from PIL import Image, ImageOps from tkinter import filedialog, messagebox, ttk import tkinter as tk from openpyxl import load_workbook from concurrent.futures import ThreadPoolExecutor, as_completed # 下载并保存单张图片(带验证,安全保存) def download_image(url, path): try: print(f"📥 正在下载图片:{url}") response = requests.get(url, timeout=10) img = Image.open(BytesIO(response.content)) img.verify() # 验证图片是否损坏 img = Image.open(BytesIO(response.content)) # 必须重新加载 with open(path, 'wb') as f: img.save(f, format='PNG') f.flush() os.fsync(f.fileno()) print(f"✅ 保存成功:{path}") return True except Exception as e: print(f"❌ 下载失败:{url},原因:{e}") return False # 多线程批量下载图片 def download_images_multithreaded(url_map, folder): downloaded = {} with ThreadPoolExecutor(max_workers=5) as executor: future_to_row = { executor.submit(download_image, url, os.path.join(folder, f"img_{row}.png")): row for row, url in url_map.items() } for future in as_completed(future_to_row): row = future_to_row[future] try: success = future.result() if success: downloaded[row] = os.path.join(folder, f"img_{row}.png") except Exception as e: print(f"❌ 第 {row} 行下载任务异常: {e}") return downloaded # 获取最大有内容的行数 def get_max_data_row(file_path, sheet_name, column_letter): wb = load_workbook(file_path, read_only=True) ws = wb[sheet_name] for row in reversed(range(1, ws.max_row + 1)): if ws[f"{column_letter}{row}"].value: return row return 0 # 处理单个Sheet的图片插入 def process_excel(file_path, sheet_name, column_letter, start_row, progress_widget=None, label_widget=None, max_img_width=200, max_img_height=200): import tempfile from PIL import Image # 兼容 Pillow 新旧版本的重采样参数 try: resample_method = Image.Resampling.LANCZOS except AttributeError: resample_method = Image.ANTIALIAS app = xw.App(visible=False) wb = app.books.open(file_path) ws = wb.sheets[sheet_name] sheet_folder = os.path.join("images", sheet_name) os.makedirs(sheet_folder, exist_ok=True) max_row = get_max_data_row(file_path, sheet_name, column_letter) # 收集下载链接 url_map = {} for row in range(start_row, max_row + 1): cell = ws.range(f"{column_letter}{row}") if cell.value: url_map[row] = cell.value # 多线程下载图片 downloaded_images = download_images_multithreaded(url_map, sheet_folder) for row in range(start_row, max_row + 1): if progress_widget: progress_value = row - start_row + 1 progress_widget["value"] = progress_value progress_widget.update() if label_widget: label_widget.config(text=f"正在处理 [{sheet_name}] 第 {row} 行 / 共 {max_row} 行") label_widget.update() if row in downloaded_images: img_path = os.path.abspath(downloaded_images[row]) print(f"🖼️ 正在插入图片:{img_path}(存在:{os.path.exists(img_path)})") try: cell = ws.range(f"{column_letter}{row}") left = cell.left top = cell.top img = Image.open(img_path) img_width, img_height = img.size # 计算缩放比例,保持图片比例 scale_ratio = min(max_img_width / img_width, max_img_height / img_height) new_width = int(img_width * scale_ratio) new_height = int(img_height * scale_ratio) resized_img = img.resize((new_width, new_height), resample_method) # 新建白底背景,尺寸固定为 max_img_width x max_img_height canvas = Image.new("RGB", (max_img_width, max_img_height), (255, 255, 255)) paste_x = (max_img_width - new_width) // 2 paste_y = (max_img_height - new_height) // 2 canvas.paste(resized_img, (paste_x, paste_y)) with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file: canvas.save(temp_file.name) temp_img_path = temp_file.name # 调整单元格宽高以匹配图片尺寸,增加10%的边距 cell_width = new_width * 0.14 * 1.1 # Excel列宽单位转换,增加10%边距 cell_height = new_height * 0.75 * 1.1 # Excel行高单位转换,增加10%边距 ws.range(f"{column_letter}{row}").column_width = cell_width ws.range(f"{column_letter}{row}").row_height = cell_height # 插入图片,并设置图片大小与调整后的单元格匹配 pic = ws.pictures.add( temp_img_path, left=left, top=top, width=new_width, height=new_height ) # 设置图片随单元格移动但不随单元格调整大小 pic.api.Placement = 1 # 1 = xlMove (图片随单元格移动) print(f"✅ 插入成功:[{sheet_name}] 第 {row} 行") os.remove(temp_img_path) except Exception as e: print(f"❌ 插入失败({sheet_name} 第 {row} 行):{e}") output_path = file_path.replace(".xlsx", f"_{sheet_name}_带图.xlsx") wb.save(output_path) wb.close() app.quit() return output_path # 批量处理多个sheet def batch_process(file_path, sheet_names, column_letter, start_row): results = [] total_sheets = len(sheet_names) for idx, sheet in enumerate(sheet_names, 1): progress_label.config(text=f"处理第 {idx}/{total_sheets} 个Sheet:{sheet}") progress_label.update() progress["value"] = 0 max_row = get_max_data_row(file_path, sheet, column_letter) progress["maximum"] = max_row - start_row + 1 if max_row >= start_row else 1 output = process_excel(file_path, sheet, column_letter, start_row, progress_widget=progress, label_widget=progress_label) results.append(output) return results # GUI逻辑 def start_process(): file_path = file_var.get() sheet_names_str = sheet_var.get() column_letter = column_var.get().upper() try: start_row = int(start_row_var.get()) except: messagebox.showerror("错误", "起始行必须是数字") return if not os.path.exists(file_path): messagebox.showerror("错误", "文件路径无效") return sheet_names = [s.strip() for s in sheet_names_str.split(",") if s.strip()] if not sheet_names: messagebox.showerror("错误", "请输入至少一个Sheet名称") return try: outputs = batch_process(file_path, sheet_names, column_letter, start_row) msg = "图片插入完成,生成文件列表:\n" + "\n".join(outputs) messagebox.showinfo("完成", msg) except Exception as e: messagebox.showerror("出错", str(e)) def choose_file(): file_path = filedialog.askopenfilename(filetypes=[("Excel 文件", "*.xlsx")]) file_var.set(file_path) # 创建图形界面 root = tk.Tk() root.title("Excel 图片插入工具") root.option_add("*Font", "SimHei 10") file_var = tk.StringVar() sheet_var = tk.StringVar(value="Sheet1") column_var = tk.StringVar(value="A") start_row_var = tk.StringVar(value="2") tk.Label(root, text="选择 Excel 文件:").grid(row=0, column=0, sticky='e', padx=10, pady=10) tk.Entry(root, textvariable=file_var, width=50).grid(row=0, column=1, padx=5, pady=10) tk.Button(root, text="浏览", command=choose_file).grid(row=0, column=2, padx=10, pady=10) tk.Label(root, text="Sheet 名称(多个用逗号分隔):").grid(row=1, column=0, sticky='e', padx=10, pady=10) tk.Entry(root, textvariable=sheet_var).grid(row=1, column=1, padx=5, pady=10) tk.Label(root, text="图片列(如 B):").grid(row=2, column=0, sticky='e', padx=10, pady=10) tk.Entry(root, textvariable=column_var).grid(row=2, column=1, padx=5, pady=10) tk.Label(root, text="起始行号(如 2):").grid(row=3, column=0, sticky='e', padx=10, pady=10) tk.Entry(root, textvariable=start_row_var).grid(row=3, column=1, padx=5, pady=10) tk.Button( root, text="开始处理", command=start_process, bg="#4CAF50", fg="white", font=("SimHei", 12, "bold"), width=15 ).grid(row=4, column=0, columnspan=3, pady=20) progress_label = tk.Label(root, text="") progress_label.grid(row=5, column=0, columnspan=3, pady=5) progress = ttk.Progressbar( root, orient="horizontal", length=400, mode="determinate" ) progress.grid(row=6, column=0, columnspan=3, pady=5) copyright_label = tk.Label( root, text="© 2025 Project Name: Excel图片插入工具 Author: songyuting. All rights reserved.", font=("SimHei", 8) ) copyright_label.grid(row=7, column=0, columnspan=3, pady=10, sticky='s') root.grid_rowconfigure(7, weight=1) root.grid_columnconfigure(1, weight=1) root.mainloop() 我需要保留不规则图片处理及保留图片清晰度的部分,使插入的每个图片的宽度高度不超出单元格,我的目的是让图片随单元格变化大小及位置
06-11
插入0张图片,你参考下这个代码里面的插入图片: import os import glob from openpyxl import load_workbook, Workbook from openpyxl.drawing.image import Image from openpyxl.drawing.spreadsheet_drawing import AnchorMarker, TwoCellAnchor, OneCellAnchor from openpyxl.styles import PatternFill, Border, Side, Alignment, Font, colors from openpyxl.utils import get_column_letter import copy import shutil import tempfile def copy_cell_style(src_cell, dest_cell): """复制单元格样式:字体、填充、边框、对齐方式等""" if src_cell.font: dest_cell.font = copy.copy(src_cell.font) if src_cell.fill: dest_cell.fill = copy.copy(src_cell.fill) if src_cell.border: dest_cell.border = copy.copy(src_cell.border) if src_cell.alignment: dest_cell.alignment = copy.copy(src_cell.alignment) if src_cell.number_format: dest_cell.number_format = src_cell.number_format def create_header_style(): """创建第一列的标题样式""" return ( Font(bold=True, color=colors.WHITE), PatternFill(start_color="4F81BD", end_color="4F81BD", fill_type="solid"), Alignment(horizontal='center', vertical='center') ) def merge_excel_with_images_and_styles(input_dir, output_file): """合并Excel文件,保留格式图片,并在第一列添加文件名工作表名""" # 创建新工作簿 wb_output = Workbook() ws_output = wb_output.active ws_output.title = "Merged Data" # 创建第一列标题的样式 header_font, header_fill, header_alignment = create_header_style() ws_output.column_dimensions['A'].width = 40 # 设置第一列宽度 current_row = 1 # 当前写入行 try: for excel_file in glob.glob(os.path.join(input_dir, "*.xlsx")): file_name = os.path.basename(excel_file) print(f"处理文件: {file_name}") wb_source = load_workbook(excel_file, keep_vba=False) for ws_name in wb_source.sheetnames: print(f" 处理工作表: {ws_name}") ws_source = wb_source[ws_name] start_row = current_row # 记录当前工作表的起始行 # 1. 复制列宽(从第二列开始) for col_idx, col_dim in enumerate(ws_source.column_dimensions.values(), 1): col_letter = get_column_letter(col_idx + 1) # +1 因为第一列是新增的 if col_dim.width is not None: ws_output.column_dimensions[col_letter].width = col_dim.width # 2. 复制行高数据 for src_row_idx, row in enumerate(ws_source.iter_rows(), 1): # 设置行高 ws_output.row_dimensions[current_row].height = ws_source.row_dimensions[src_row_idx].height # 添加文件名工作表名到第一列(所有行都使用相同的文件名-工作表名格式) header_text = f"{file_name}-{ws_name}" header_cell = ws_output.cell(row=current_row, column=1) header_cell.value = header_text header_cell.font = header_font header_cell.fill = header_fill header_cell.alignment = header_alignment # 复制单元格数据样式(从第二列开始) for col_idx, cell in enumerate(row, 1): dest_cell = ws_output.cell(row=current_row, column=col_idx + 1) # 复制值 if cell.value is not None: dest_cell.value = cell.value # 复制样式 copy_cell_style(cell, dest_cell) current_row += 1 # 3. 处理图片 img_count = 0 for drawing in ws_source._images: try: img_copy = copy.deepcopy(drawing) anchor = img_copy.anchor # 计算行偏移量 row_offset = start_row - 1 # 调整锚点位置:列+1(因为新增了第一列) if isinstance(anchor, TwoCellAnchor): # 双单元格锚点 new_anchor = TwoCellAnchor( _from=AnchorMarker( col=anchor._from.col + 1, # 列索引+1 row=anchor._from.row + row_offset, colOff=anchor._from.colOff, rowOff=anchor._from.rowOff ), to=AnchorMarker( col=anchor.to.col + 1, # 列索引+1 row=anchor.to.row + row_offset, colOff=anchor.to.colOff, rowOff=anchor.to.rowOff ) ) img_copy.anchor = new_anchor elif isinstance(anchor, OneCellAnchor): # 单单元格锚点 new_anchor = OneCellAnchor( _from=AnchorMarker( col=anchor._from.col + 1, # 列索引+1 row=anchor._from.row + row_offset, colOff=anchor._from.colOff, rowOff=anchor._from.rowOff ) ) img_copy.anchor = new_anchor # 添加图片到目标工作表 ws_output.add_image(img_copy) img_count += 1 except Exception as e: print(f" 图片处理失败: {str(e)}") print(f" 添加了 {img_count} 张图片") # 在表格之间添加分隔行 current_row += 1 print(f" 工作表处理完成,当前行: {current_row}") except Exception as e: print(f"处理过程中出错: {str(e)}") raise # 保存结果 try: wb_output.save(output_file) print(f"\n合并完成! 输出文件: {output_file}") print(f"共处理 {len(glob.glob(os.path.join(input_dir, '*.xlsx')))} 个文件") print(f"最终工作表行数: {current_row}") except Exception as e: print(f"保存文件时出错: {str(e)}") if __name__ == "__main__": input_directory = "./input_excels" # Excel文件所在目录 output_filename = "merged_with_filename_and_sheetname.xlsx" merge_excel_with_images_and_styles(input_directory, output_filename)
10-11
import os import shutil import re import sys import tempfile from datetime import datetime import openpyxl from openpyxl.drawing.image import Image as OpenpyxlImage from PIL import Image as PILImage import tkinter as tk from tkinter import ttk, filedialog, messagebox from tkinter.font import Font import threading class ImageProcessorAndExcelGenerator: def __init__(self, root): self.root = root self.root.title("图片批量重命名与Excel导入工具") self.root.geometry("750x400") self.root.configure(bg="#f0f0f0") self.root.resizable(False, False) # 自定义字体 self.title_font = Font(family="Microsoft YaHei", size=12, weight="bold") self.label_font = Font(family="Microsoft YaHei", size=10) self.button_font = Font(family="Microsoft YaHei", size=10, weight="bold") self.status_font = Font(family="Microsoft YaHei", size=9) # 创建主框架 main_frame = ttk.Frame(self.root, padding=15) main_frame.pack(fill=tk.BOTH, expand=True) # 标题 title_label = ttk.Label( main_frame, text="图片批量重命名与Excel导入工具", font=self.title_font, foreground="#2c3e50", ) title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20)) # 创建表单框架 form_frame = ttk.LabelFrame(main_frame, text="处理参数", padding=10) form_frame.grid(row=1, column=0, columnspan=3, sticky="we", padx=5, pady=5) # 原始图片根目录选择 ttk.Label(form_frame, text="图片目录:", font=self.label_font).grid( row=0, column=0, padx=5, pady=10, sticky="e" ) self.source_dir_var = tk.StringVar() source_dir_entry = ttk.Entry( form_frame, textvariable=self.source_dir_var, width=60, font=self.label_font ) source_dir_entry.grid(row=0, column=1, padx=5, pady=5, sticky="we") browse_source_btn = ttk.Button( form_frame, text="浏览...", command=self.browse_source_dir ) browse_source_btn.grid(row=0, column=2, padx=5, pady=5) # Excel输出路径选择 current_time = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"output_{current_time}.xlsx" default_path = os.path.join( os.path.expanduser("~"), "Desktop", default_filename ) self.output_excel_var = tk.StringVar(value=default_path) ttk.Label(form_frame, text="Excel输出路径:", font=self.label_font).grid( row=1, column=0, padx=5, pady=10, sticky="e" ) output_excel_entry = ttk.Entry( form_frame, textvariable=self.output_excel_var, width=60, font=self.label_font, ) output_excel_entry.grid(row=1, column=1, padx=5, pady=5, sticky="we") browse_excel_btn = ttk.Button( form_frame, text="浏览...", command=self.browse_excel_output ) browse_excel_btn.grid(row=1, column=2, padx=5, pady=5) # 按钮框架 button_frame = ttk.Frame(main_frame) button_frame.grid(row=2, column=0, columnspan=3, pady=20) # 执行按钮 self.process_btn = ttk.Button( button_frame, text="开始处理并生成Excel", command=self.start_processing_thread, width=25, style="Accent.TButton", ) self.process_btn.pack(pady=5) # 状态标签 status_frame = ttk.Frame(main_frame) status_frame.grid(row=3, column=0, columnspan=3, sticky="we", pady=10) self.status_var = tk.StringVar( value="准备就绪:请选择原始图片根目录Excel输出路径" ) status_label = ttk.Label( status_frame, textvariable=self.status_var, font=self.status_font, foreground="#3498db", wraplength=650, anchor="center", ) status_label.pack(fill=tk.X, padx=10) # 进度条 self.progress = ttk.Progressbar( status_frame, orient="horizontal", length=650, mode="determinate" ) self.progress.pack(pady=5) # 版权信息 footer_frame = ttk.Frame(main_frame) footer_frame.grid(row=4, column=0, columnspan=3, pady=(10, 0)) ttk.Label( footer_frame, text="Designed with Python ", font=("Microsoft YaHei", 8), foreground="#7f8c8d", ).pack() # 配置样式 self.configure_styles() # 临时文件列表 self.temp_files = [] def configure_styles(self): """配置自定义样式""" style = ttk.Style() style.configure("TFrame", background="#f0f0f0") style.configure("TLabelframe", background="#ffffff", borderwidth=1) style.configure("TLabelframe.Label", font=self.title_font) # 主按钮样式 style.configure( "Accent.TButton", font=self.button_font, foreground="black", background="#3498db", padding=8, ) style.map( "Accent.TButton", background=[("active", "#2980b9"), ("disabled", "#bdc3c7")], ) # 状态标签样式 style.configure("Red.TLabel", foreground="#e74c3c") style.configure("Green.TLabel", foreground="#27ae60") # 进度条样式 style.configure( "Custom.Horizontal.TProgressbar", thickness=16, background="#2ecc71", troughcolor="#ecf0f1", ) def browse_source_dir(self): dir_path = filedialog.askdirectory(title="选择原始图片根目录") if dir_path: self.source_dir_var.set(dir_path) def browse_excel_output(self): file_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")], title="保存Excel文件", ) if file_path: self.output_excel_var.set(file_path) def process_and_copy_images(self, source_dir): """处理图片:按路径层级重命名并复制到桌面上的输出文件夹""" # 在桌面上创建输出目录 desktop_path = os.path.join(os.path.expanduser("~"), "Desktop") current_time = datetime.now().strftime("%Y%m%d_%H%M%S") output_dir = os.path.join(desktop_path, f"images_{current_time}") image_extensions = { ".jpg", ".jpeg", ".png", } processed_count = 0 skipped_count = 0 # 获取图片总数用于进度条 total_images = 0 for root, _, files in os.walk(source_dir): for filename in files: ext = os.path.splitext(filename)[1].lower() if ext in image_extensions: total_images += 1 if total_images == 0: return output_dir, 0, 0, total_images os.makedirs(output_dir, exist_ok=True) self.status_var.set(f"在桌面创建输出目录: {output_dir}") self.root.update() self.progress["maximum"] = total_images self.progress["value"] = 0 for root, _, files in os.walk(source_dir): for filename in files: ext = os.path.splitext(filename)[1].lower() if ext in image_extensions: file_path = os.path.join(root, filename) relative_path = os.path.relpath(root, source_dir) # 生成新文件名 if relative_path == ".": new_filename = filename else: path_prefix = relative_path.replace(os.path.sep, "-") # 确保文件名长度不超过Windows限制 if len(path_prefix) > 100: path_prefix = path_prefix[:100] + "_TRUNCATED" new_filename = f"{path_prefix}-{filename}" # 处理重名文件 output_path = os.path.join(output_dir, new_filename) counter = 1 while os.path.exists(output_path): name, ext = os.path.splitext(new_filename) new_filename = f"{name}_{counter}{ext}" output_path = os.path.join(output_dir, new_filename) counter += 1 # 复制文件 try: shutil.copy2(file_path, output_path) processed_count += 1 self.progress["value"] = processed_count self.status_var.set( f"处理图片 {processed_count}/{total_images}: {new_filename}" ) self.root.update() except Exception as e: self.status_var.set(f"处理失败 {filename}: {str(e)}") self.root.update() skipped_count += 1 else: skipped_count += 1 return output_dir, processed_count, skipped_count, total_images def natural_sort_key(self, s): """自然排序键函数,用于按数字顺序排序文件名""" sub_strings = re.split(r"(\d+)", s) return [int(c) if c.isdigit() else c for c in sub_strings] def generate_excel_from_images(self, image_folder, output_excel_path): """从处理后的图片文件夹生成Excel""" temp_files = [] wb = openpyxl.Workbook() ws = wb.active ws.title = "图片汇总" # # 设置工作表标题 # title_cell = ws.cell(row=1, column=1, value="图片汇总表") # title_cell.font = openpyxl.styles.Font(size=14, bold=True) # ws.merge_cells( # start_row=1, start_column=1, end_row=1, end_column=10 # ) # 合并标题行 # 设置圖片尺寸(单位为厘米) cm_to_pixels = 96 / 2.54 # 单位转换:厘米到像素 width_pixels = int(7.2 * cm_to_pixels) height_pixels = int(5.4 * cm_to_pixels) start_row, start_col = 2, 1 # 从第3行开始,预留标题空间 current_row, current_col = start_row, start_col # 获取并筛选图片文件 all_files = os.listdir(image_folder) image_extensions = ( ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".webp", ".jfif", ) image_files = [f for f in all_files if f.lower().endswith(image_extensions)] if not image_files: raise ValueError(f"处理后的图片文件夹中未找到图片: {image_folder}") # 按文件名分组(假设格式为"数字-...") image_groups = {} for image_file in image_files: try: # 获取文件名的第一部分作为分组依据 first_part = image_file.split("-")[0] if not first_part.isdigit(): # 尝试另一种分割方式 first_part = image_file.split("_")[0] if not first_part.isdigit(): raise ValueError(f"前缀不是数字: {first_part}") group_key = int(first_part) except (IndexError, ValueError) as e: # 如果无法解析为数字分组,将所有图片放入同一组 group_key = 0 if group_key not in image_groups: image_groups[group_key] = [] image_groups[group_key].append(image_file) sorted_groups = sorted(image_groups.items(), key=lambda x: x[0]) if not sorted_groups: raise ValueError("未成功分组任何图片文件,请检查文件名格式") # 配置进度条 total_images = len(image_files) self.progress["maximum"] = total_images self.progress["value"] = 0 processed_images = 0 # 处理每个分组 total_groups = len(sorted_groups) for group_idx, (group, files) in enumerate(sorted_groups, 1): # 添加分组标题 group_cell = ws.cell( row=current_row - 1, column=current_col, value=f"分组 {group}" ) group_cell.font = openpyxl.styles.Font(bold=True) self.status_var.set(f"处理分组 {group}({group_idx}/{total_groups})") self.root.update() sorted_files = sorted(files, key=self.natural_sort_key) for file_idx, image_file in enumerate(sorted_files, 1): processed_images += 1 self.progress["value"] = processed_images self.status_var.set( f"插入图片 {file_idx}/{len(sorted_files)}(分组 {group})" ) self.root.update() image_path = os.path.join(image_folder, image_file) # 处理图片尺寸 try: with PILImage.open(image_path) as img: if img.width == 0 or img.height == 0: raise ValueError(f"图片尺寸无效: {image_file}") # # 保持宽高比调整尺寸 # ratio = min( # width_pixels / img.width, height_pixels / img.height # ) # new_width = int(img.width * ratio) # new_height = int(img.height * ratio) # img_resized = img.resize((new_width, new_height)) img_resized = img.resize((width_pixels, height_pixels)) except Exception as e: self.status_var.set(f"处理图片失败: {image_file} - {str(e)}") self.root.update() continue # 保存临时图片 temp_img_path = os.path.join( tempfile.gettempdir(), f"temp_{group}_{file_idx}_{datetime.now().strftime('%H%M%S%f')}.png", ) img_resized.save(temp_img_path) temp_files.append(temp_img_path) # 插入Excel img_name = os.path.splitext(image_file)[0] ws.cell(row=current_row - 1, column=current_col, value=img_name) try: excel_img = OpenpyxlImage(temp_img_path) ws.add_image( excel_img, ws.cell(row=current_row, column=current_col).coordinate, ) except Exception as e: self.status_var.set(f"插入图片到Excel失败: {image_file} - {str(e)}") self.root.update() continue # 设置行列尺寸以适应图片 col_letter = openpyxl.utils.get_column_letter(current_col) ws.column_dimensions[col_letter].width = width_pixels / 6.5 # 调整列宽 ws.row_dimensions[current_row].height = height_pixels * 0.75 # 调整行高 current_col += 1 current_row += 2 # 分组间增加空行 current_col = start_col # 保存Excel if os.path.exists(output_excel_path): name, ext = os.path.splitext(output_excel_path) output_excel_path = f"{name}_{datetime.now().strftime('%H%M%S')}{ext}" self.status_var.set( f"文件已存在,自动重命名为: {os.path.basename(output_excel_path)}" ) self.root.update() try: wb.save(output_excel_path) self.status_var.set(f"Excel文件已保存: {output_excel_path}") self.root.update() except Exception as e: raise IOError(f"保存Excel文件失败: {str(e)}") return output_excel_path, len(image_files), total_groups, temp_files def start_processing_thread(self): """启动处理线程,避免界面冻结""" self.process_btn.config(state=tk.DISABLED) self.status_var.set("开始处理...") self.progress["value"] = 0 self.root.update() # 启动后台处理线程 processing_thread = threading.Thread(target=self.process_and_generate) processing_thread.daemon = True processing_thread.start() def process_and_generate(self): """主流程:处理图片→生成Excel""" original_root = self.source_dir_var.get() excel_output = self.output_excel_var.get() temp_files = [] try: # 验证输入 if not original_root: raise ValueError("请选择原始图片根目录") if not os.path.isdir(original_root): raise FileNotFoundError(f"原始目录不存在: {original_root}") if not excel_output: raise ValueError("请选择Excel输出路径") # 处理图片 - 输出到桌面 self.status_var.set("正在处理图片并保存到桌面...") self.root.update() output_img_dir, processed, skipped, total = self.process_and_copy_images( original_root ) if processed == 0: raise ValueError("未找到任何可处理的图片文件") # 生成Excel self.status_var.set("正在根据处理的图片生成Excel文件...") self.root.update() # 创建Excel并插入图片 excel_path, img_count, group_count, excel_temp_files = ( self.generate_excel_from_images(output_img_dir, excel_output) ) temp_files.extend(excel_temp_files) # 完成处理并显示结果 self.status_var.set("处理完成!") self.root.update() # 显示成功消息 self.status_var.set("全部处理完成!") messagebox.showinfo( "成功", f"图片处理与Excel生成完成!\n\n" f"原始目录: {original_root}\n" f"处理后图片位置: {output_img_dir}\n" f"处理图片数量: {processed}张\n" f"跳过文件数量: {skipped}个\n" f"Excel文件位置: {excel_path}\n" f"插入图片数量: {img_count}张\n" f"分组数量: {group_count}组", ) # # 打开文件所在位置 # try: # if sys.platform == "win32": # os.startfile(os.path.dirname(excel_path)) # except: # pass except Exception as e: self.status_var.set(f"处理出错: {str(e)}") messagebox.showerror("错误", f"处理过程中发生错误:\n{str(e)}") # log_path = os.path.join(os.path.expanduser("~"), "Desktop", "error_log.txt") # with open(log_path, "a") as log: # log.write(f"{datetime.now()} - {str(e)}\n") finally: # 清理临时文件 for temp_file in temp_files: try: os.remove(temp_file) except: pass self.process_btn.config(state=tk.NORMAL) self.progress["value"] = 0 if __name__ == "__main__": root = tk.Tk() if sys.platform == "win32": import ctypes ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("ImageProcessor") app = ImageProcessorAndExcelGenerator(root) root.mainloop() 如上代码,在修改图片尺寸的时候,将图片画质压缩了。我现在想要无损压缩,怎么弄
10-18
import os import re import sys import tempfile from datetime import datetime import openpyxl from openpyxl.drawing.image import Image as OpenpyxlImage from openpyxl.utils import get_column_letter from PIL import Image import tkinter as tk from tkinter import ttk, filedialog, messagebox from tkinter.font import Font import threading class ImageToExcelProcessor: def __init__(self, root): self.root = root self.root.title("图片批量插入Excel工具") self.root.geometry("750x500") # 增加高度以容纳新控件 self.root.configure(bg="#f0f0f0") self.root.resizable(False, False) # 自定义字体 self.title_font = Font(family="Microsoft YaHei", size=12, weight="bold") self.label_font = Font(family="Microsoft YaHei", size=10) self.button_font = Font(family="Microsoft YaHei", size=10, weight="bold") self.status_font = Font(family="Microsoft YaHei", size=9) # 创建主框架 main_frame = ttk.Frame(self.root, padding=15) main_frame.pack(fill=tk.BOTH, expand=True) # 标题 title_label = ttk.Label( main_frame, text="图片批量插入Excel工具", font=self.title_font, foreground="#2c3e50", ) title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20)) # 创建表单框架 form_frame = ttk.LabelFrame(main_frame, text="处理参数", padding=10) form_frame.grid(row=1, column=0, columnspan=3, sticky="we", padx=5, pady=5) # 图片根目录选择 ttk.Label(form_frame, text="图片目录:", font=self.label_font).grid( row=0, column=0, padx=5, pady=10, sticky="e" ) self.source_dir_var = tk.StringVar() source_dir_entry = ttk.Entry( form_frame, textvariable=self.source_dir_var, width=60, font=self.label_font ) source_dir_entry.grid(row=0, column=1, padx=5, pady=5, sticky="we") browse_source_btn = ttk.Button( form_frame, text="浏览...", command=self.browse_source_dir ) browse_source_btn.grid(row=0, column=2, padx=5, pady=5) # Excel输出路径选择 current_time = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"output_{current_time}.xlsx" default_path = os.path.join( os.path.expanduser("~"), "Desktop", default_filename ) self.output_excel_var = tk.StringVar(value=default_path) ttk.Label(form_frame, text="Excel输出路径:", font=self.label_font).grid( row=1, column=0, padx=5, pady=10, sticky="e" ) output_excel_entry = ttk.Entry( form_frame, textvariable=self.output_excel_var, width=60, font=self.label_font, ) output_excel_entry.grid(row=1, column=1, padx=5, pady=5, sticky="we") browse_excel_btn = ttk.Button( form_frame, text="浏览...", command=self.browse_excel_output ) browse_excel_btn.grid(row=1, column=2, padx=5, pady=5) # 模板目录配置项(新增) default_template_dir = os.path.join(os.path.expanduser("~"), "Desktop", "DPA") self.template_dir_var = tk.StringVar(value=default_template_dir) ttk.Label(form_frame, text="Excel模板文件:", font=self.label_font).grid( row=2, column=0, padx=5, pady=10, sticky="e" ) self.template_file_var = tk.StringVar() template_file_entry = ttk.Entry( form_frame, textvariable=self.template_file_var, width=60, font=self.label_font, ) template_file_entry.grid(row=2, column=1, padx=5, pady=5, sticky="we") browse_template_btn = ttk.Button( form_frame, text="浏览...", command=self.browse_template_file ) browse_template_btn.grid(row=2, column=2, padx=5, pady=5) # 按钮框架 button_frame = ttk.Frame(main_frame) button_frame.grid(row=3, column=0, columnspan=3, pady=20) # 执行按钮 self.process_btn = ttk.Button( button_frame, text="开始处理并生成Excel", command=self.start_processing_thread, width=25, style="Accent.TButton", ) self.process_btn.pack(pady=5) # 状态标签 status_frame = ttk.Frame(main_frame) status_frame.grid(row=4, column=0, columnspan=3, sticky="we", pady=10) self.status_var = tk.StringVar( value="准备就绪:请选择图片目录、模板目录Excel输出路径" ) status_label = ttk.Label( status_frame, textvariable=self.status_var, font=self.status_font, foreground="#3498db", wraplength=650, anchor="center", ) status_label.pack(fill=tk.X, padx=10) # 进度条 self.progress = ttk.Progressbar( status_frame, orient="horizontal", length=650, mode="determinate" ) self.progress.pack(pady=5) # 版权信息 footer_frame = ttk.Frame(main_frame) footer_frame.grid(row=5, column=0, columnspan=3, pady=(10, 0)) ttk.Label( footer_frame, text="Developed by ICT ", font=("Microsoft YaHei", 8), foreground="#7f8c8d", ).pack() # 配置样式 self.configure_styles() # 临时文件列表 self.temp_files = [] def configure_styles(self): """配置自定义样式""" style = ttk.Style() style.configure("TFrame", background="#f0f0f0") style.configure("TLabelframe", background="#ffffff", borderwidth=1) style.configure("TLabelframe.Label", font=self.title_font) # 主按钮样式 style.configure( "Accent.TButton", font=self.button_font, foreground="black", background="#3498db", padding=8, ) style.map( "Accent.TButton", background=[("active", "#2980b9"), ("disabled", "#bdc3c7")], ) # 状态标签样式 style.configure("Red.TLabel", foreground="#e74c3c") style.configure("Green.TLabel", foreground="#27ae60") # 进度条样式 style.configure( "Custom.Horizontal.TProgressbar", thickness=16, background="#2ecc71", troughcolor="#ecf0f1", ) def browse_template_file(self): """浏览并选择Excel模板文件""" file_path = filedialog.askopenfilename( filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")], title="选择Excel模板文件", ) if file_path: self.template_file_var.set(file_path) def browse_source_dir(self): """浏览并选择图片源目录""" dir_path = filedialog.askdirectory(title="选择图片目录") if dir_path: self.source_dir_var.set(dir_path) def browse_excel_output(self): """浏览并选择Excel输出路径""" file_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")], title="保存Excel文件", ) if file_path: self.output_excel_var.set(file_path) def browse_template_dir(self): """浏览并选择模板目录(新增方法)""" dir_path = filedialog.askdirectory(title="选择模板目录") if dir_path: self.template_dir_var.set(dir_path) def natural_sort_key(self, s): """自然排序键函数,用于按数字顺序排序文件名""" return [ int(text) if text.isdigit() else text.lower() for text in re.split(r"(\d+)", s) ] def find_template_file(self, template_code): """根据模板代码查找模板文件(使用配置的模板目录)""" template_dir = self.template_dir_var.get() if not os.path.isdir(template_dir): raise FileNotFoundError(f"模板目录不存在: {template_dir}") # 在模板目录中查找匹配的模板文件 for filename in os.listdir(template_dir): if filename.startswith(template_code) and filename.lower().endswith( ".xlsx" ): return os.path.join(template_dir, filename) return None def insert_images_to_template(self, template_path, output_path, image_data): """将图片插入Excel模板的指定位置""" temp_files = [] try: # 加载模板 wb = openpyxl.load_workbook(template_path) # Excel单位转换常量 EXCEL_DPI = 96 CM_TO_INCH = 0.3937 TARGET_WIDTH_CM = 7.39 TARGET_HEIGHT_CM = 5.77 # 计算目标尺寸 target_width_inch = TARGET_WIDTH_CM * CM_TO_INCH target_height_inch = TARGET_HEIGHT_CM * CM_TO_INCH target_width_px = target_width_inch * EXCEL_DPI target_height_px = target_height_inch * EXCEL_DPI total_images = sum(len(images) for _, _, images in image_data) self.progress["maximum"] = total_images self.progress["value"] = 0 processed_images = 0 # 处理每个图片组 for sheet_name, start_cell, direction, image_paths in image_data: if sheet_name not in wb.sheetnames: self.status_var.set(f"警告: 工作表 '{sheet_name}' 不存在,跳过") self.root.update() continue ws = wb[sheet_name] col, row = self.parse_cell_reference(start_cell) # 处理每个图片 for image_path in image_paths: try: # 创建临时文件 temp_img_path = os.path.join( tempfile.gettempdir(), f"excel_temp_{os.path.basename(image_path)}_{datetime.now().strftime('%H%M%S%f')}.png", ) with Image.open(image_path) as img: # 计算缩放比例 width_ratio = target_width_px / img.width height_ratio = target_height_px / img.height scale_ratio = min(width_ratio, height_ratio) # 缩放图片 scaled_width = int(img.width * scale_ratio) scaled_height = int(img.height * scale_ratio) # 保存临时文件 img.resize((scaled_width, scaled_height)).save( temp_img_path, "PNG" ) temp_files.append(temp_img_path) # 插入图片到Excel excel_img = OpenpyxlImage(temp_img_path) cell_ref = f"{get_column_letter(col)}{row}" ws.add_image(excel_img, cell_ref) # 更新状态 processed_images += 1 self.progress["value"] = processed_images self.status_var.set( f"插入图片: {os.path.basename(image_path)} 到 {sheet_name}!{cell_ref}" ) self.root.update() # 移动到下一个单元格 if direction == "H": col += 1 # 横向插入 else: row += 1 # 纵向插入 except Exception as e: self.status_var.set( f"处理失败 {os.path.basename(image_path)}: {str(e)}" ) self.root.update() # 保存Excel文件 if os.path.exists(output_path): name, ext = os.path.splitext(output_path) timestamp = datetime.now().strftime("%H%M%S") output_path = f"{name}_{timestamp}{ext}" wb.save(output_path) self.status_var.set(f"Excel文件已保存: {output_path}") return output_path, processed_images, len(image_data), temp_files except Exception as e: raise RuntimeError(f"生成Excel失败: {str(e)}") def parse_cell_reference(self, cell_ref): """解析单元格引用(如 'C3')为列索引行号""" # 拆分列字母行号 col_part = "".join(filter(str.isalpha, cell_ref)) row_part = "".join(filter(str.isdigit, cell_ref)) if not col_part or not row_part: raise ValueError(f"无效的单元格引用: {cell_ref}") # 转换列字母为数字索引 col_index = 0 for char in col_part.upper(): col_index = col_index * 26 + (ord(char) - ord("A") + 1) return col_index, int(row_part) def process_images(self, source_dir): """处理图片:收集图片并组织成数据结构""" image_data = [] # 存储元组(sheet_name, start_cell, direction, image_paths) # 遍历第一级目录(包含模板代码的目录) for template_dir in os.listdir(source_dir): template_path = os.path.join(source_dir, template_dir) if not os.path.isdir(template_path): continue # 遍历第二级目录(sheet名称) for sheet_dir in os.listdir(template_path): sheet_path = os.path.join(template_path, sheet_dir) if not os.path.isdir(sheet_path): continue # 遍历第三级目录(单元格定位) for cell_dir in os.listdir(sheet_path): cell_path = os.path.join(sheet_path, cell_dir) if not os.path.isdir(cell_path): continue # 解析单元格方向(例如:C3-H) if "-" not in cell_dir: self.status_var.set( f"警告: 无效的目录名格式 '{cell_dir}', 应为 '单元格-方向'" ) continue cell_ref, direction = cell_dir.rsplit("-", 1) direction = direction.upper() if direction not in ["H", "S"]: self.status_var.set( f"警告: 无效的方向 '{direction}', 应为 'H' 或 'S'" ) continue # 收集图片 image_paths = [] for filename in os.listdir(cell_path): file_path = os.path.join(cell_path, filename) if os.path.isfile(file_path) and filename.lower().endswith( (".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".webp") ): image_paths.append(file_path) if image_paths: # 按自然顺序排序图片 image_paths.sort( key=lambda x: self.natural_sort_key(os.path.basename(x)) ) image_data.append((sheet_dir, cell_ref, direction, image_paths)) return image_data, template_code def start_processing_thread(self): """启动处理线程,避免界面冻结""" self.process_btn.config(state=tk.DISABLED) self.status_var.set("开始处理...") self.progress["value"] = 0 self.root.update() # 启动后台处理线程 processing_thread = threading.Thread(target=self.process_and_generate) processing_thread.daemon = True processing_thread.start() def process_and_generate(self): """主流程:处理图片→生成Excel""" source_dir = self.source_dir_var.get() excel_output = self.output_excel_var.get() template_file = self.template_file_var.get() # 获取模板文件路径 temp_files = [] try: # 验证输入 if not source_dir: raise ValueError("请选择图片目录") if not os.path.isdir(source_dir): raise FileNotFoundError(f"图片目录不存在: {source_dir}") if not excel_output: raise ValueError("请选择Excel输出路径") if not template_file: raise ValueError("请选择Excel模板文件") if not os.path.isfile(template_file): raise FileNotFoundError(f"模板文件不存在: {template_file}") # 处理图片并组织数据结构 self.status_var.set("正在解析图片目录结构...") self.root.update() image_data = self.process_images(source_dir) # 修改为只返回image_data if not image_data: raise ValueError("未找到任何有效的图片数据") # 生成Excel self.status_var.set("正在将图片插入Excel模板...") self.root.update() excel_path, img_count, group_count, excel_temp_files = ( self.insert_images_to_template(template_file, excel_output, image_data) ) temp_files.extend(excel_temp_files) # 完成处理并显示结果 self.status_var.set("处理完成!") messagebox.showinfo( "成功", f"图片处理与Excel生成完成!\n\n" f"图片目录: {source_dir}\n" f"使用模板: {os.path.basename(template_file)}\n" f"Excel文件位置: {excel_path}\n" f"插入图片数量: {img_count}张\n" f"处理的分组数量: {group_count}组", ) except Exception as e: self.status_var.set(f"处理出错: {str(e)}") messagebox.showerror("错误", f"处理过程中发生错误:\n{str(e)}") finally: # 清理临时文件 for temp_file in temp_files: try: if os.path.exists(temp_file): os.remove(temp_file) except: pass self.process_btn.config(state=tk.NORMAL) self.progress["value"] = 0 if __name__ == "__main__": root = tk.Tk() if sys.platform == "win32": import ctypes ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("ImageProcessor") app = ImageToExcelProcessor(root) root.mainloop() 代碼似乎不太對,我現在圖片路徑C:\Users\U180705\Desktop\T0 5891110BU1 25899011-01-00\1Bump\C3-H 。模板路徑C:\Users\U180705\Desktop\DPA\空白模板\T0.xlsx. T0 5891110BU1 25899011-01-00 的前兩碼是模板的excel的名稱。1Bump 是模板裡面的sheet名稱。C3-H 是定位到這個1Bump sheet的C3單元格,H表示橫向對C3-H文件夾下的圖片進行放置。即C3,C4,C5 單元格放置
最新发布
11-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值