乐于分享
好东西不私藏

H5版圆形印章生成工具 使用文档

H5版圆形印章生成工具 使用文档

圆形印章生成工具 使用文档

学在坚持公众号 出品

工具简介

圆形印章生成工具提供两种使用方式:

  1. Python 桌面版圆形印章生成工具.py):基于 Tkinter + Pillow,支持 4 倍超采样高清渲染
  2. H5 网页版圆形印章生成工具.html):纯前端 Canvas 实现,双击即可在浏览器中使用,无需安装任何依赖

两个版本功能一致:可视化制作中文/中英文圆形公章,支持全参数自定义、各元素可拖动调整位置、实时预览、一键下载透明底印章图片。


版本对比

特性
Python 桌面版
H5 网页版
运行方式
python 圆形印章生成工具.py
双击 HTML 文件
依赖
Python 3.7+ / Pillow
无(现代浏览器即可)
渲染质量
4倍超采样抗锯齿
Canvas 原生渲染
字体支持
系统字体(宋体/黑体/楷体等)
浏览器字体
元素拖动
✅ 支持
✅ 支持
实时预览
✅ 200ms 防抖
✅ 100ms 防抖
参数无上限
下载格式
PNG 透明底
PNG 透明底
老化效果

快速开始

Python 桌面版

pip install Pillow
python 圆形印章生成工具.py

H5 网页版

直接双击 圆形印章生成工具.html 文件,在浏览器中打开即可使用。


界面布局

两个版本均采用左右分栏布局:

  • 左侧(40%):参数配置面板,带滚动条
  • 右侧(60%):印章实时预览区,支持鼠标拖动各元素

拖动功能说明

右侧预览区的印章各元素可以直接用鼠标拖动调整位置:

区域
对应元素
拖动效果
上半外圈
公司名称
上下拖动调整字边距(文字离边框远近)
中心区域
五角星/中心内容
上下左右自由拖动
中下区域
章名
上下左右自由拖动
下半外圈
防伪码
上下拖动调整字边距

拖动时左侧对应参数值会实时同步更新。点击”重置拖动位置”可恢复默认。


参数配置详解

1. 公司名称(外圈弧形文字)

沿印章外圈弧形排列的主体文字。

参数
默认值
说明
内容
学在坚持圆形印章科技有限公司
外圈显示的文字
字号
20
文字大小(无上限)
字间距
0
字符之间的额外间距
字边距
0
文字与边框的距离(可拖动调整)

2. 章名(下方居中文字)

参数
默认值
说明
内容
专用章
章名文字
字号
16
文字大小
上下距离
-16
垂直偏移(可拖动调整)
左右距离
0
水平偏移(可拖动调整)

3. 中心内容(印章中心图案)

参数
默认值
说明
内容
中心字符/图案
字号
50
图案大小
上下距离
0
垂直偏移(可拖动调整)
左右距离
0
水平偏移(可拖动调整)

4. 防伪码(底部弧形文字)

参数
默认值
说明
内容
1234567890123
防伪编码
字号
10
文字大小
字间距
0
字符间距
矫正
0
垂直位置微调
字边距
0
与边框距离(可拖动调整)

5. 边框设置

参数
默认值
说明
外边线粗细
2
外圈边框线宽
内边线粗细
1
内圈边框线宽
内边线显示
开启
是否显示内圈边线

6. 外观与尺寸

参数
默认值
说明
印章颜色
红色 (#FF0000)
支持任意颜色
印章尺寸
240px
输出图片尺寸(无上限)
老化效果
关闭
模拟印章磨损做旧

操作流程

1. 打开工具(Python版运行py / H5版双击html)
   ↓
2. 左侧面板调整参数(或右侧直接拖动元素)
   ↓
3. 实时预览自动更新
   ↓
4. 满意后点击"下载印章"保存 PNG 文件

输出格式

属性
说明
格式
PNG
背景
透明
色彩模式
RGBA
默认尺寸
240×240 像素(可自定义)

功能按钮

按钮
说明
生成中文印章
按当前配置生成纯中文圆形印章
生成中英文印章
支持制作包含中英文内容的印章
下载印章
导出当前预览的透明底印章图片
重置拖动位置
将所有拖动偏移恢复为默认值

项目文件

圆形印章生成工具.py     # Python 桌面版(Tkinter GUI)
圆形印章生成工具.html   # H5 网页版(纯前端 Canvas)
圆形印章生成工具.md     # 本文档

Python 版代码架构

类名
职责
SealGenerator
印章图片生成核心引擎(Pillow 渲染)
DraggablePreview
右侧预览区拖动交互逻辑
SealApp
GUI 界面与参数管理

H5 版技术栈

  • 纯 HTML + CSS + JavaScript
  • Canvas 2D API 绘制印章
  • 鼠标事件实现拖动
  • toDataURL() 导出 PNG

常见问题

Q: Python 版字体显示不正确?

A: 确保 Windows 系统已安装宋体、黑体、楷体等字体(系统自带)。

Q: H5 版字体和 Python 版不一样?

A: H5 版使用浏览器字体渲染,效果可能略有差异,但功能一致。

Q: 文字重叠怎么办?

A: 调整字间距、字边距或字号,或直接拖动元素调整位置。

Q: 如何去掉防伪码?

A: 将防伪码的”内容”清空即可。

Q: 参数有上限吗?

A: 没有最大上限,可以自由输入任意数值。


注意事项

  1. 本工具仅供学习和演示用途,请勿用于制作伪造公章等违法行为
  2. 生成的印章为模拟效果,不具有法律效力
  3. Python 版建议在 Windows 系统下使用以获得最佳字体支持
  4. H5 版建议使用 Chrome / Edge / Firefox 等现代浏览器

版本信息

  • 版本:2.0.0
  • 作者:学在坚持公众号
  • Python 版:Tkinter + Pillow
  • H5 版:HTML5 Canvas
  • 许可证:MIT License
圆形印章生成工具 - 学在坚持公众号        * { margin: 0; padding: 0; box-sizing: border-box; }        body {            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI''Microsoft YaHei', sans-serif;            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);            min-height: 100vh; padding: 20px;        }        .container {            max-width: 1200px; margin: 0 auto;            background: #fff; border-radius: 16px;            box-shadow: 0 20px 60px rgba(0,0,0,0.15);            overflow: hidden;        }        .header {            background: linear-gradient(90deg, #e74c3c#c0392b);            color: #fff; padding: 20px 30px; text-align: center;        }        .header h1 { font-size: 24px; margin-bottom: 5px; }        .header p { font-size: 14px; opacity: 0.9; }        .main { display: flex; min-height: 600px; }        .left-panel {            flex: 2; padding: 20px; overflow-y: auto;            max-height: 700px; border-right: 1px solid #eee;        }        .right-panel {            flex: 3; padding: 30px; display: flex;            flex-direction: column; align-items: center; justify-content: center;            background: #fafafa;        }        .section { margin-bottom: 20px; }        .section-title {            font-size: 14px; font-weight: bold; color: #333;            padding: 8px 0; border-bottom: 2px solid #e74c3c;            margin-bottom: 12px;        }        .form-row {            display: flex; align-items: center;            margin-bottom: 8px; gap: 10px;        }        .form-row label {            min-width: 80px; font-size: 13px; color: #555;        }        .form-row input[type="text"],        .form-row input[type="number"],        .form-row select {            flex: 1; padding: 6px 10px; border: 1px solid #ddd;            border-radius: 6px; font-size: 13px; outline: none;            transition: border-color 0.2s;        }        .form-row input:focus, .form-row select:focus {            border-color: #e74c3c;        }        .form-row input[type="number"] { width: 80px; flex: none; }        .form-row input[type="checkbox"] { width: 18px; height: 18px; }        .form-row input[type="color"] {            width: 40px; height: 30px; border: none;            border-radius: 4px; cursor: pointer;        }        #preview-canvas {            background: #fff; border: 2px solid #eee;            border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.08);            cursor: move;        }        .btn-group { margin-top: 20px; display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }        .btn {            padding: 10px 24px; border: none; border-radius: 8px;            font-size: 14px; cursor: pointer; font-weight: 500;            transition: all 0.2s;        }        .btn-primary { background: #e74c3c; color: #fff; }        .btn-primary:hover { background: #c0392b; transform: translateY(-1px); }        .btn-success { background: #27ae60; color: #fff; }        .btn-success:hover { background: #219a52; transform: translateY(-1px); }        .btn-info { background: #3498db; color: #fff; }        .btn-info:hover { background: #2980b9; transform: translateY(-1px); }        .drag-tip {            margin-top: 12px; font-size: 12px; color: #999; text-align: center;        }        @media (max-width768px) {            .main { flex-direction: column; }            .left-panel { max-height: none; border-right: none; border-bottom: 1px solid #eee; }            .right-panel { padding: 20px; }        }圆形印章生成工具学在坚持公众号 | 全参数自定义 · 实时预览 · 可拖动 · 一键下载1. 公司名称(外圈弧形文字)内容:字号:字间距:字边距:2. 章名(下方居中文字)内容:字号:上下距离:左右距离:3. 中心内容(印章中心图案)内容:字号:上下距离:左右距离:4. 防伪码(底部弧形文字)内容:字号:字间距:矫正:字边距:5. 边框设置外边线粗细:内边线粗细:内边线显示:6. 外观与尺寸印章颜色:印章尺寸:老化效果:提示:可拖动印章中的各元素调整位置(中心★ / 章名 / 公司名称 / 防伪码)                生成中文印章                生成中英文印章                下载印章const canvas = document.getElementById('preview-canvas');const ctx = canvas.getContext('2d');// 拖动状态let dragging = null;let dragStart = {x: 0, y: 0};functiongetParams() {    return {        companyName: document.getElementById('companyName').value,        companyFontSize: parseInt(document.getElementById('companyFontSize').value) || 20,        companySpacing: parseInt(document.getElementById('companySpacing').value) || 0,        companyMargin: parseInt(document.getElementById('companyMargin').value) || 0,        sealName: document.getElementById('sealName').value,        nameFontSize: parseInt(document.getElementById('nameFontSize').value) || 14,        nameOffsetY: parseInt(document.getElementById('nameOffsetY').value) || 0,        nameOffsetX: parseInt(document.getElementById('nameOffsetX').value) || 0,        centerContent: document.getElementById('centerContent').value,        centerFontSize: parseInt(document.getElementById('centerFontSize').value) || 40,        centerOffsetY: parseInt(document.getElementById('centerOffsetY').value) || 0,        centerOffsetX: parseInt(document.getElementById('centerOffsetX').value) || 0,        codeText: document.getElementById('codeText').value,        codeFontSize: parseInt(document.getElementById('codeFontSize').value) || 9,        codeSpacing: parseInt(document.getElementById('codeSpacing').value) || 0,        codeCorrection: parseInt(document.getElementById('codeCorrection').value) || 0,        codeMargin: parseInt(document.getElementById('codeMargin').value) || 0,        outerBorder: parseInt(document.getElementById('outerBorder').value) || 4,        innerBorder: parseInt(document.getElementById('innerBorder').value) || 2,        innerBorderShow: document.getElementById('innerBorderShow').checked,        sealColor: document.getElementById('sealColor').value,        sealSize: parseInt(document.getElementById('sealSize').value) || 300,        agingEffect: document.getElementById('agingEffect').checked,    };}functiongenerateSeal() {    const p = getParams();    const size = 400// canvas固定400    const scale = size / p.sealSize;    ctx.clearRect(00, size, size);    const cx = size / 2;    const cy = size / 2;    const radius = (size - 20) / 2;    // 外边框    ctx.beginPath();    ctx.arc(cx, cy, radius, 0, Math.PI * 2);    ctx.strokeStyle = p.sealColor;    ctx.lineWidth = p.outerBorder * scale;    ctx.stroke();    // 内边框    if (p.innerBorderShow) {        const innerRadius = radius - (p.outerBorder * scale + 4);        ctx.beginPath();        ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2);        ctx.lineWidth = p.innerBorder * scale;        ctx.stroke();    }    // 外圈弧形文字(公司名称)    drawArcTextTop(ctx, cx, cy, radius, p);    // 中心内容    drawCenterContent(ctx, cx, cy, p);    // 章名    drawBottomText(ctx, cx, cy, p);    // 防伪码    drawArcTextBottom(ctx, cx, cy, radius, p);    // 老化效果    if (p.agingEffect) {        applyAging(ctx, size);    }}functiondrawArcTextTop(ctx, cx, cy, radius, p{    const text = p.companyName;    if (!text) return;    const fontSize = p.companyFontSize;    const spacing = p.companySpacing;    const margin = p.companyMargin;    const textRadius = radius - margin - fontSize / 2 - 10;    ctx.font = `bold ${fontSize}px SimSun, serif`;    ctx.fillStyle = p.sealColor;    ctx.textAlign = 'center';    ctx.textBaseline = 'middle';    // 计算每个字符的角度    let charWidths = [];    let totalWidth = 0;    for (let char of text) {        const w = ctx.measureText(char).width + spacing;        charWidths.push(w);        totalWidth += w;    }    const totalAngle = totalWidth / textRadius;    let startAngle = -Math.PI / 2 - totalAngle / 2;    let currentAngle = startAngle;    for (let i = 0; i < text.length; i++) {        const charAngle = charWidths[i] / textRadius;        const angle = currentAngle + charAngle / 2;        const x = cx + textRadius * Math.cos(angle);        const y = cy + textRadius * Math.sin(angle);        ctx.save();        ctx.translate(x, y);        ctx.rotate(angle + Math.PI / 2);        ctx.fillText(text[i], 00);        ctx.restore();        currentAngle += charAngle;    }}functiondrawCenterContent(ctx, cx, cy, p{    const text = p.centerContent;    if (!text) return;    const fontSize = p.centerFontSize;    ctx.font = `bold ${fontSize}px SimSun, serif`;    ctx.fillStyle = p.sealColor;    ctx.textAlign = 'center';    ctx.textBaseline = 'middle';    ctx.fillText(text, cx + p.centerOffsetX, cy + p.centerOffsetY);}functiondrawBottomText(ctx, cx, cy, p{    const text = p.sealName;    if (!text) return;    const fontSize = p.nameFontSize;    ctx.font = `bold ${fontSize}px SimSun, serif`;    ctx.fillStyle = p.sealColor;    ctx.textAlign = 'center';    ctx.textBaseline = 'middle';    ctx.fillText(text, cx + p.nameOffsetX, cy + fontSize + 10 + p.nameOffsetY);}functiondrawArcTextBottom(ctx, cx, cy, radius, p{    const text = p.codeText;    if (!text) return;    const fontSize = p.codeFontSize;    const spacing = p.codeSpacing;    const margin = p.codeMargin;    const correction = p.codeCorrection;    const textRadius = radius - margin - fontSize / 2 - 10;    ctx.font = `bold ${fontSize}px KaiTi, serif`;    ctx.fillStyle = p.sealColor;    ctx.textAlign = 'center';    ctx.textBaseline = 'middle';    let charWidths = [];    let totalWidth = 0;    for (let char of text) {        const w = ctx.measureText(char).width + spacing;        charWidths.push(w);        totalWidth += w;    }    const totalAngle = totalWidth / textRadius;    let startAngle = Math.PI / 2 - totalAngle / 2;    let currentAngle = startAngle;    for (let i = 0; i < text.length; i++) {        const charAngle = charWidths[i] / textRadius;        const angle = currentAngle + charAngle / 2;        const x = cx + textRadius * Math.cos(angle);        const y = cy + textRadius * Math.sin(angle) + correction;        ctx.save();        ctx.translate(x, y);        ctx.rotate(angle - Math.PI / 2);        ctx.fillText(text[i], 00);        ctx.restore();        currentAngle += charAngle;    }}functionapplyAging(ctx, size{    const imageData = ctx.getImageData(00, size, size);    const data = imageData.data;    const pixelCount = size * size;    // 随机去除像素    for (let i = 0; i < pixelCount * 0.03; i++) {        const idx = Math.floor(Math.random() * pixelCount) * 4;        if (data[idx + 3] > 0) {            data[idx + 3] = 0;        }    }    ctx.putImageData(imageData, 00);}functiondownloadSeal() {    generateSeal();    const link = document.createElement('a');    link.download = '印章.png';    link.href = canvas.toDataURL('image/png');    link.click();}// 拖动功能functiongetElementAt(x, y{    const cx = 200, cy = 200;    const rx = x - cx, ry = y - cy;    const dist = Math.sqrt(rx * rx + ry * ry);    const radius = 190;    if (dist > radius) return null;    if (dist < radius * 0.25return 'center';    if (dist < radius * 0.55 && ry > 0return 'name';    if (dist >= radius * 0.55 && ry < 0return 'company';    if (dist >= radius * 0.55 && ry > 0return 'code';    return 'center';}canvas.addEventListener('mousedown', function(e) {    const rect = canvas.getBoundingClientRect();    const x = e.clientX - rect.left;    const y = e.clientY - rect.top;    dragging = getElementAt(x, y);    dragStart = {x: e.clientX, y: e.clientY};});canvas.addEventListener('mousemove', function(e) {    if (!dragging) {        const rect = canvas.getBoundingClientRect();        const x = e.clientX - rect.left;        const y = e.clientY - rect.top;        canvas.style.cursor = getElementAt(x, y) ? 'move' : 'default';        return;    }    const dx = e.clientX - dragStart.x;    const dy = e.clientY - dragStart.y;    dragStart = {x: e.clientX, y: e.clientY};    if (dragging === 'center') {        const el = document.getElementById('centerOffsetX');        const el2 = document.getElementById('centerOffsetY');        el.value = parseInt(el.value) + Math.round(dx);        el2.value = parseInt(el2.value) + Math.round(dy);    } else if (dragging === 'name') {        const el = document.getElementById('nameOffsetX');        const el2 = document.getElementById('nameOffsetY');        el.value = parseInt(el.value) + Math.round(dx);        el2.value = parseInt(el2.value) + Math.round(dy);    } else if (dragging === 'company') {        const el = document.getElementById('companyMargin');        el.value = parseInt(el.value) + Math.round(dy);    } else if (dragging === 'code') {        const el = document.getElementById('codeMargin');        el.value = parseInt(el.value) - Math.round(dy);    }    generateSeal();});canvas.addEventListener('mouseup', function() { dragging = null; });canvas.addEventListener('mouseleave', function() { dragging = null; });// 实时渲染:监听所有输入变化let renderTimer = null;functionscheduleRender() {    if (renderTimer) clearTimeout(renderTimer);    renderTimer = setTimeout(generateSeal, 100);}document.querySelectorAll('input, select').forEach(el => {    el.addEventListener('input', scheduleRender);    el.addEventListener('change', scheduleRender);});// 初始渲染window.addEventListener('load', generateSeal);