您当前的位置:首页 > 计算机 > 编程开发 > Python

Python 竣工现场照片图word版本

时间:09-03来源:作者:点击数:
城东书院 www.cdsy.xyz

在项目作业中,我们经常需要做一个现场照片图,然后进行打印,常规测绘项目都是采用CAD二次开发的方式,写一个lsp的插件进行载入,然后进行打印,我做了一些改进,我直接将其插入word中,将现场照片标题和日期进行改写,加快办公效率(毕竟cad打开都需要几十秒) 。

# -*- coding: utf-8 -*-
"""
功能:
- UI:选择图片,多选;选择保存的 Word 文件名;开始生成
- 每张图片左上角添加“现场照片{序号}”,右下角添加日期(年/月/日)
- 叠加文字:宋体、黄色(带黑色描边),覆盖到图片上
- Word 文档:横向页面、两列排版
"""
 
import os
import tempfile
import shutil
import datetime
from tkinter import Tk, Frame, Button, Label, Listbox, END, filedialog, messagebox
from PIL import Image, ImageDraw, ImageFont, ExifTags
from docx import Document
from docx.shared import Inches
from docx.enum.section import WD_ORIENT
from docx.enum.text import WD_ALIGN_PARAGRAPH
 
# --------- 工具函数 ---------
def find_simsun_font():
    """尝试在常见路径找到宋体;返回字体文件路径或 None"""
    candidates = [
        r"C:\Windows\Fonts\simsun.ttc",     # Windows 常见
        r"C:\Windows\Fonts\SimSun.ttf",
        "/System/Library/Fonts/STHeiti Light.ttc",  # macOS 近似中文字体(备选)
        "/System/Library/Fonts/PingFang.ttc",       # macOS 近似中文字体(备选)
        "/usr/share/fonts/truetype/arphic/uming.ttc",  # Linux 常见中文字体(备选)
    ]
    for p in candidates:
        if os.path.exists(p):
            return p
    return None
 
def get_date_str(img_path):
    """优先从 EXIF 读拍摄日期;否则用文件修改时间;返回 'YYYY/MM/DD' """
    # 先试 EXIF
    try:
        im = Image.open(img_path)
        exif = im.getexif()
        if exif:
            # 找到 DateTimeOriginal
            tag_map = {ExifTags.TAGS.get(k, k): v for k, v in exif.items()}
            for key in ("DateTimeOriginal", "DateTime", "CreateDate"):
                if key in tag_map and isinstance(tag_map[key], str):
                    s = tag_map[key]
                    # 常见格式:'2023:08:01 12:34:56'
                    parts = s.split(" ")[0].replace(":", "/")
                    ymd = parts.split("/")
                    if len(ymd) >= 3:
                        y, m, d = ymd[:3]
                        return f"{y}/{m.zfill(2)}/{d.zfill(2)}"
    except Exception:
        pass
 
    # 用文件修改时间
    ts = os.path.getmtime(img_path)
    dt = datetime.datetime.fromtimestamp(ts)
    return dt.strftime("%Y/%m/%d")
 
def draw_overlay(img_path, index, font_path, save_to):
    """
    在图片左上角画 '现场照片{index}',右下角画日期
    - 黄色字体
    - 大字号(宽度的 5%)
    - 加粗(重复绘制)
    - 最上层叠加
    """
    im = Image.open(img_path).convert("RGB")
    W, H = im.size
    draw = ImageDraw.Draw(im)
 
    # 字号占图片宽度的 5%,更显眼
    font_size = max(20, int(W * 0.05))
    try:
        font = ImageFont.truetype(font_path, font_size) if font_path else ImageFont.load_default()
    except Exception:
        font = ImageFont.load_default()
 
    # 文本内容
    left_top_text = f"现场照片{index}"
    right_bottom_text = get_date_str(img_path)
 
    # 颜色和加粗实现:先画多次偏移的黑色描边,再画一次黄色文字
    fill_color = (255, 255, 0)   # 黄色
    # stroke_color = (0, 0, 0)     # 黑色描边
    # stroke_w = max(3, font_size // 10)  # 描边宽
 
    # 位置边距
    pad = max(15, font_size // 3)
 
    # 左上角文字位置
    lt_x, lt_y = pad, pad
 
    # 右下角文字位置
    rb_bbox = draw.textbbox((0, 0), right_bottom_text, font=font)
    rb_w = rb_bbox[2] - rb_bbox[0]
    rb_h = rb_bbox[3] - rb_bbox[1]
    rb_x = W - rb_w - pad
    rb_y = H - rb_h - pad
 
    # --- 加粗描边效果 ---
    # 左上角
    draw.text((lt_x, lt_y), left_top_text, font=font, fill=fill_color)
              # stroke_width=stroke_w, stroke_fill=stroke_color)
 
    # 右下角
    draw.text((rb_x, rb_y), right_bottom_text, font=font, fill=fill_color)
              # stroke_width=stroke_w, stroke_fill=stroke_color)
 
    # 保存
    im.save(save_to, quality=95)
 
 
# --------- 生成 Word 文档 ---------
def build_docx(image_paths, save_path):
    if not image_paths:
        raise ValueError("没有图片可生成。")
 
    doc = Document()
    section = doc.sections[0]
    section.orientation = WD_ORIENT.LANDSCAPE
    # 横向需要交换宽高
    section.page_width, section.page_height = section.page_height, section.page_width
 
    # 边距
    section.left_margin = Inches(0.5)
    section.right_margin = Inches(0.5)
    section.top_margin = Inches(0.5)
    section.bottom_margin = Inches(0.5)
 
    usable = section.page_width - section.left_margin - section.right_margin
    col_width = usable / 2
    pic_width = col_width - Inches(0.2)
 
    table = doc.add_table(rows=0, cols=2)
    # 想隐藏边框可注释掉下一行
    table.style = "Table Grid"
 
    row_cells = None
    for i, p in enumerate(image_paths):
        if i % 2 == 0:
            row_cells = table.add_row().cells
        cell = row_cells[i % 2]
        para = cell.paragraphs[0] if cell.paragraphs else cell.add_paragraph()
        run = para.add_run()
        run.add_picture(p, width=pic_width)  # 只给宽度,保持纵横比
        para.alignment = WD_ALIGN_PARAGRAPH.CENTER
 
    doc.save(save_path)
 
# --------- Tkinter UI ---------
class App:
    def __init__(self, master):
        self.master = master
        self.master.title("图片插入 Word(横向,两列,叠加“现场照片 + 日期”)")
 
        self.font_path = find_simsun_font()
        if not self.font_path:
            messagebox.showwarning(
                "字体提示",
                "未找到“宋体(SimSun)”,将尝试使用系统默认字体渲染。\n"
                "建议在 Windows 上确保 C:\\Windows\\Fonts\\simsun.ttc 存在。"
            )
 
        frm = Frame(master, padx=10, pady=10)
        frm.pack(fill="both", expand=True)
 
        Label(frm, text="已选择的图片:").grid(row=0, column=0, sticky="w")
 
        self.lst = Listbox(frm, width=80, height=12)
        self.lst.grid(row=1, column=0, columnspan=3, sticky="nsew", pady=(5, 10))
 
        # 自适应伸缩
        frm.grid_rowconfigure(1, weight=1)
        frm.grid_columnconfigure(0, weight=1)
 
        self.btn_select = Button(frm, text="选择图片(可多选)", command=self.select_images)
        self.btn_select.grid(row=2, column=0, sticky="w")
 
        self.btn_export = Button(frm, text="生成 Word", command=self.export_word)
        self.btn_export.grid(row=2, column=2, sticky="e")
 
        self.images = []
 
    def select_images(self):
        paths = filedialog.askopenfilenames(
            title="选择要插入的图片(可多选)",
            filetypes=[("Images", "*.jpg;*.jpeg;*.png;*.bmp;*.gif"), ("All files", "*.*")]
        )
        if not paths:
            return
        self.images = list(paths)
        self.lst.delete(0, END)
        for p in self.images:
            self.lst.insert(END, p)
 
    def export_word(self):
        if not self.images:
            messagebox.showinfo("提示", "请先选择图片。")
            return
 
        save_path = filedialog.asksaveasfilename(
            title="选择保存位置和文件名",
            defaultextension=".docx",
            filetypes=[("Word 文档", "*.docx")],
            initialfile="图片.docx"
        )
        if not save_path:
            return
 
        tmpdir = tempfile.mkdtemp(prefix="img_overlay_")
        try:
            # 叠加文字后保存到临时目录
            out_imgs = []
            for idx, src in enumerate(self.images, start=1):
                name = f"overlay_{idx:04d}{os.path.splitext(src)[1].lower()}"
                dst = os.path.join(tmpdir, name)
                draw_overlay(src, idx, self.font_path, dst)
                out_imgs.append(dst)
 
            # 生成 Word
            build_docx(out_imgs, save_path)
            messagebox.showinfo("完成", f"已生成:\n{save_path}")
        except Exception as e:
            messagebox.showerror("错误", f"生成失败:{e}")
        finally:
            # 清理临时文件
            try:
                shutil.rmtree(tmpdir, ignore_errors=True)
            except Exception:
                pass
 
def main():
    root = Tk()
    root.geometry("820x480")
    App(root)
    root.mainloop()
 
if __name__ == "__main__":
    main()

 

城东书院 www.cdsy.xyz
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐