当前位置: 首页 > news >正文

python将Excel数据写进图片中

需求

一张图片模板,里面有两个字段,内容都要来自Excel,Excel里,对应两个字段。

分析

针对这种需求,没有代码基础的人可以直接使用ppt+Excel的结合的方式。有代码基础的人我直接推荐接下来的代码。

目录结构

在这里插入图片描述
auto.py就是主文件内容就是下面的代码。image.png就是要被操作的图片模板。test.xlsx是需要对应放上图片的数据。以本例子为例就是名称和编码的内容。

代码

# -*- coding: utf-8 -*-
"""
物料标识卡(小模板 299x92)自动生成
- 写字区域用相对比例,适配任意尺寸模板
- 名称自动换行(最多2行),编码单行
- 自动修正无效矩形(left>right / 越界)
- 支持红框调试与放大渲染(提升清晰度)
依赖: pip install pillow pandas openpyxl
"""import os, re
import pandas as pd
from PIL import Image, ImageDraw, ImageFont# ========= 基本配置 =========
TEMPLATE = "image.png"      # 模板图(当前这张小模板)
EXCEL    = "test.xlsx"      # 数据表
OUTDIR   = "output"         # 输出目录# 兼容列名
CAND_CODE = ["物料编码","编码","料号","物料代号","物料編碼"]
CAND_NAME = ["物料名称","名称","品名","物料名稱"]# 字体(按系统情况自选一个存在的)
FONT_CANDIDATES = ["C:/Windows/Fonts/msyh.ttc",     # 微软雅黑"C:/Windows/Fonts/simsun.ttc",   # 宋体"/System/Library/Fonts/STHeiti Light.ttc",  # mac 旧系统"/System/Library/Fonts/PingFang.ttc",       # mac 苹方(若存在)
]
COLOR = (0, 68, 178)   # 文字颜色(与模板蓝一致或相近)# ========= 版面(相对比例) =========
# 这组比例已针对 299×92 的模板精调,如对不上可微调 0.01~0.02
# (left, top, right, bottom) 都是 0~1 的比例
NAME_BOX_RATIO = (0.19, 0.50, 0.46, 0.74)  # “名称”字段的可写区做上右下
CODE_BOX_RATIO = (0.74, 0.55, 0.97, 0.74)  # “物料编码”字段的可写区 (0.74, 0.60, 0.97, 0.86# 细微像素级偏移(先保持 0,0;想再贴线就 +-1~2 调)
NUDGE_NAME = (0, 0)
NUDGE_CODE = (0, 0)# 对齐与字号
V_RATIO = 0.50           # 垂直几何居中(0.5)
H_ALIGN = 'left'         # 'left' 或 'right'
MIN_FONT      = 18
NAME_MAX_FONT = 50
CODE_MAX_FONT = 50# 其他
SAFE_MARGIN_PX = 6       # 防止贴边
DRAW_GUIDE = False       # True 显示红框调试
UPSCALE = 3              # 放大渲染倍数:1/2/3(2或3更清晰)# ========= 工具函数 =========
def choose_font(size: int):for fp in FONT_CANDIDATES:try:return ImageFont.truetype(fp, size)except Exception:continue# 全部失败则退回默认位图字体return ImageFont.load_default()def text_wh(draw, txt, font):x1, y1, x2, y2 = draw.textbbox((0, 0), txt, font=font)return x2 - x1, y2 - y1def fit_font(draw, txt, max_w, max_font, min_font=MIN_FONT, safe_margin=SAFE_MARGIN_PX):target_w = max(1, max_w - safe_margin)for sz in range(max_font, min_font - 1, -1):f = choose_font(sz)w, _ = text_wh(draw, txt, f)if w <= target_w:return freturn choose_font(min_font)def norm(s: str) -> str:s = re.sub(r"\s+", "", str(s or "")).lower()s = s.replace("編","编").replace("名稱","名称")return sdef find_col(df: pd.DataFrame, cands):nm = {c: norm(c) for c in df.columns}cs = [norm(x) for x in cands]# 完全匹配优先for col, n in nm.items():if n in cs:return col# 子串次之for col, n in nm.items():if any(c in n for c in cs):return colraise KeyError(f"找不到列:{cands}")def normalize_box(box, W, H, min_w=2, min_h=2):l, t, r, b = box# 排序,保证 l<=r, t<=bif l > r: l, r = r, lif t > b: t, b = b, t# 夹到边界l = max(0, min(l, W-1)); r = max(0, min(r, W-1))t = max(0, min(t, H-1)); b = max(0, min(b, H-1))# 保证最小宽高(防止 0 宽/高引发报错)if r - l < min_w: r = min(W-1, l + min_w)if b - t < min_h: b = min(H-1, t + min_h)return (l, t, r, b)def ratio2box(ratio_box, W, H, nudge=(0,0)):l = int(ratio_box[0] * W) + nudge[0]t = int(ratio_box[1] * H) + nudge[1]r = int(ratio_box[2] * W) + nudge[0]b = int(ratio_box[3] * H) + nudge[1]return normalize_box((l, t, r, b), W, H)def draw_in_box(draw, text, box, v_ratio=0.5, h_align='left', max_font=46):l, t, r, b = boxmax_w = r - lfont = fit_font(draw, text, max_w, max_font=max_font)w, h = text_wh(draw, text, font)# 标准几何居中:去除之前实现里的 "*2"y = int(t + (b - t - h) * v_ratio)x = (r - w) if h_align == 'right' else ldraw.text((x, y), text, font=font, fill=COLOR)def _wrap_fit_lines(draw, text, box, max_font, min_font=MIN_FONT, max_lines=2,safe_margin=SAFE_MARGIN_PX, line_spacing=1.08):l, t, r, b = boxmax_w = r - l - safe_marginmax_h = b - t - safe_marginseps = ['/', '/', '、', ',', ',', ' ']def tokenize(s):tokens, cur = [], ''for ch in str(s).replace('\n', ' '):if ch in seps:if cur: tokens.append(cur); cur = ''tokens.append(ch)else:cur += chif cur: tokens.append(cur)return tokenstokens = tokenize(text)for sz in range(max_font, min_font - 1, -1):f = choose_font(sz)lines, cur = [], ''for tk in tokens:trial = cur + tkw, _ = text_wh(draw, trial, f)if w <= max_w or cur == '':cur = trialelse:lines.append(cur); cur = tkif len(lines) >= max_lines:breakif cur and len(lines) < max_lines:lines.append(cur)# 对超宽行做硬切保障def hard_wrap(s):out, buf = [], ''for ch in s:trial = buf + chw, _ = text_wh(draw, trial, f)if w <= max_w or buf == '':buf = trialelse:out.append(buf); buf = chif buf: out.append(buf)return outfixed = []for ln in lines:if text_wh(draw, ln, f)[0] <= max_w:fixed.append(ln)else:fixed.extend(hard_wrap(ln))if len(fixed) > max_lines:fixed = fixed[:max_lines]; breakif not fixed:continueline_h = int(sz * line_spacing)if line_h * len(fixed) <= max_h:return f, fixed[:max_lines]# 兜底:省略号f = choose_font(min_font)lines = [''] * max_linesi = 0; buf = ''for ch in ''.join(tokens):trial = buf + chif text_wh(draw, trial, f)[0] <= max_w:buf = trialelse:lines[i] = buf; i += 1; buf = chif i >= max_lines: breakif i < max_lines: lines[i] = buflast = lines[-1]while last and text_wh(draw, last + '…', f)[0] > max_w:last = last[:-1]lines[-1] = (last + '…') if last else '…'return f, linesdef draw_wrapped_in_box(draw, text, box, v_ratio=0.5, h_align='left',max_font=46, min_font=MIN_FONT, max_lines=2, line_spacing=1.08):l, t, r, b = boxf, lines = _wrap_fit_lines(draw, text, box, max_font, min_font, max_lines, line_spacing)line_h = int(f.size * line_spacing)total_h = line_h * len(lines)y0 = int(t + (b - t - total_h) * v_ratio)for idx, ln in enumerate(lines):w, h = text_wh(draw, ln, f)x = (r - w) if h_align == 'right' else ly = y0 + idx * line_hdraw.text((x, y), ln, font=f, fill=COLOR)# ========= 主流程 =========
def main():os.makedirs(OUTDIR, exist_ok=True)df = pd.read_excel(EXCEL, dtype=str).fillna("")base = Image.open(TEMPLATE).convert("RGBA")# 放大渲染(整体先放大再写字,字更锐利)if UPSCALE > 1:W0, H0 = base.sizebase = base.resize((W0*UPSCALE, H0*UPSCALE), resample=Image.LANCZOS)W, H = base.sizename_box = ratio2box(NAME_BOX_RATIO, W, H, NUDGE_NAME)code_box = ratio2box(CODE_BOX_RATIO, W, H, NUDGE_CODE)# 调试输出print("Template size:", (W, H))print("name_box:", name_box, "code_box:", code_box)col_code = find_col(df, CAND_CODE)col_name = find_col(df, CAND_NAME)for i, row in df.iterrows():code = str(row.get(col_code, "")).strip()name = str(row.get(col_name, "")).strip()img = base.copy()draw = ImageDraw.Draw(img)if DRAW_GUIDE:draw.rectangle(name_box, outline=(255, 0, 0), width=1)draw.rectangle(code_box, outline=(255, 0, 0), width=1)# 编码:单行draw_in_box(draw, code, code_box, v_ratio=V_RATIO, h_align=H_ALIGN, max_font=CODE_MAX_FONT)# 名称:自动换行(最多两行)draw_wrapped_in_box(draw, name, name_box, v_ratio=V_RATIO, h_align=H_ALIGN,max_font=NAME_MAX_FONT, min_font=MIN_FONT, max_lines=2, line_spacing=1.08)out = os.path.join(OUTDIR, f"{code or '未命名'}_{i+1}.png")img.save(out, dpi=(300, 300))print("Saved:", out)print("\n✅ 完成,输出目录:", os.path.abspath(OUTDIR))if __name__ == "__main__":main()

效果

在这里插入图片描述
在这里插入图片描述

扩展

我如何调整数据的位置 比如 觉得他在虚线太靠上了,怎么做呢。我如果图片换模板,该怎么做呢。就需要回到代码去调整。

NAME_BOX_RATIO = (0.19, 0.50, 0.46, 0.74)  # “名称”字段的可写区做上右下
CODE_BOX_RATIO = (0.74, 0.55, 0.97, 0.74)  # “物料编码”字段的可写区 (0.74, 0.60, 0.97, 0.86

这部分就是的大小调整。这个框可以让他暂时显示

DRAW_GUIDE = False       # True 显示红框调试

调整上述的参数就可以调整框的大小,进而调整字体自适应大小。

http://www.dtcms.com/a/597137.html

相关文章:

  • 五金配件网站建设报价圣弓 网站建设
  • Django中如何重写save()方法
  • C在线编程 | 提升编程技能,掌握C语言的核心要点
  • 京东这样的网站怎么做网站建设费用怎么算
  • django模型数据查询
  • 佛山骏域网站建设软件开发价格标准
  • discuz企业网站一诺摄影设计
  • 基于微信小程序的特色农产品交易系统
  • 【windows常见问题】pin不可用,无法登录Windows
  • 免费正能量励志网站网站登陆界面怎么做
  • 网站建设找丿金手指排名在iis上部署的网站本机无法浏览解决方法
  • 【Android Studio】解决4K电视机上,网页无法适配的问题
  • 如何选择适合自动化的测试用例?
  • 一步一步网站建设教程联通 网站备案
  • 著名心理学导师钧岚确认出席2025厦门IP+AI万人峰会​
  • 10.游戏逆向-pxxx-UObjectBase成员解密
  • 触发器,存储过程
  • 计算点到三次 Bézier 曲线最短距离及对应参数 u 的方法(转化为五次多项式)
  • npm中-d -g 和默认安装的区别
  • 深圳商城网站建设报价单青岛网站建设的方案
  • AI 编程工具全景分析与 Claude Code 配置 MiniMax - m2 模型指南​
  • MES 生产调度模块:业务逻辑、功能细节与全场景落地
  • PG认证培训考试中心-CUUG【工信人才授权指定】
  • 研究院 网站建设云主机怎么装网站
  • 微网站模板代码电子商务实网站的建设课件
  • 55_AI智能体运维部署之实战经验:编写生产级Dockerfile的深度思考
  • 软件.PostgreSQL与StackBuilder及配置使用
  • web华为商城前端项目4页面
  • window命令行差外网ip
  • windows—wsl2—docker配置代理以push/pull