Python汉字笔顺图及书写gif动画生成代码

#推荐
Python汉字笔顺图及书写gif动画生成代码

2026-03-17 2
[!--dianshu--] C币
VIP折扣
    折扣详情
  • 体验VIP会员

    免费

  • 月卡VIP会员

    免费

  • 年卡VIP会员

    免费

  • 永久VIP会员

    免费

查看演示
下载不了?请联系网站客服提交链接错误!
TAG标签: 安装指导

#推荐
Python汉字笔顺图及书写gif动画生成代码

2026-03-17 php教程 9999 2
郑重承诺丨总裁主题提供安全交易、信息保真!
TAG标签:
安装指导
[!--dianshu--] C币
VIP权限详情
    会员权限详情
  • 体验VIP会员

    免费

  • 月卡VIP会员

    免费

  • 年卡VIP会员

    免费

  • 永久VIP会员

    免费

开通VIP尊享优惠特权
立即下载 等待添加 升级会员 最新活动
微信扫码咨询 微信扫码咨询

联系电话:18888888888

进入TA的商铺 联系官方客服
详情介绍

欢迎!我白天是个邮递员,晚上就是个有抱负的演员。这是我的网站。我住在天朝的帝都,有条叫做Jack的狗。

基于Github的makemeahanzi项目开发,项目地址:https://www.skishore.me/makemeahanzi/

笔顺图及书写动画实现

里面提供了9574个汉字的SVG笔顺内容和gif动画。根据这里数据,实现了汉字的笔顺图和笔顺书写gif图,现将程序分享出来,有兴趣的朋友可以进行有趣的扩展。

以“愛”字为例,它是以汉字的十进制编码命名,有两种格式,双击打开svg文件

# 读取svgs生成笔顺图.pyimport osimport jsonimport xml.etree.ElementTree as ETimport 获取笔顺from concurrent.futures import ThreadPoolExecutor  def process_svg(svg, path, index, total_num):    """处理单个SVG文件的函数"""    try:        char_number = svg.split('-')[0]        dec_num = int(char_number)        char = chr(dec_num)  # 转汉字        svg_path = os.path.join(path, svg)         # 读取SVG文件        root = ET.parse(svg_path).getroot()         # 使用命名空间来查找path元素        ns = {'svg': 'http://www.w3.org/2000/svg'}        path_elements = root.findall('.//svg:path', ns)        svg_path_data = [path.attrib['d'] for path in path_elements]  # 获取笔顺内容         print(f'{index} / {total_num} 正在生成 {char} 的笔顺图...')         # 传入数据        获取笔顺.main(svg_path_data, char)     except Exception as e:        print(f"处理 {svg} 时出错: {e}")  def main():    path = r'makemeahanzi-master\svgs-still'    svgs_list = os.listdir(path)    total_num = len(svgs_list)     # 创建线程池,max_workers设置并发线程数    with ThreadPoolExecutor(max_workers=4) as executor:        # 提交所有任务到线程池        futures = []        for index, svg in enumerate(svgs_list):            futures.append(                executor.submit(process_svg, svg, path, index, total_num)            )         # 等待所有任务完成        for future in futures:            future.result()  # 这里会抛出异常,如果有的话  if __name__ == '__main__':    main()

# 获取笔顺.py 

from cairosvg import svg2pngimport osimport re  def makedirs(path):    if not os.path.exists(path):        os.makedirs(path)  def ChangeSVG2png(svg_path, chinese):    outputpath = f'strokeOrder/{chinese}'  # 笔顺保存路径    makedirs(outputpath)     # 如果笔顺图跟笔顺list相等,则说明已经生成过了    png_files = sorted([f for f in os.listdir(outputpath)                        if f.startswith(f'{chinese}_') and f.endswith('.png')])    if len(png_files) == len(svg_path):        print(f'*** {chinese} 的笔顺图已经生成,跳过***')        return     svg_output = {        'width': '1024px',        'height': '1024px',        'xmlns': 'http://www.w3.org/2000/svg',        "font_color": "#000000",  # 黑色        "font_color_last": "#FF1111",  # 红色        # "font_color_last": "#0000FF",  # 蓝色        "output_address": outputpath,        "output_filename": ''    }     if not os.path.exists(outputpath):  # 为每个汉字创建文件夹        os.mkdir(outputpath)     # Grid lines (米字格)    # 在 ChangeSVG2png 函数中添加以下米字格定义(替换原来的 grid_lines)    grid_lines = [        # 对角线(长线,保持实线但更细)        '<line x1="0" y1="0" x2="1024" y2="1024" style="stroke:#EEEEEE;stroke-width:4;stroke-dasharray:10,10"/>',        '<line x1="1024" y1="0" x2="0" y2="1024" style="stroke:#EEEEEE;stroke-width:4;stroke-dasharray:10,10"/>',         # 中心横竖线(虚线小线段)        '<line x1="0" y1="512" x2="1024" y2="512" style="stroke:#EEEEEE;stroke-width:4;stroke-dasharray:10,10"/>',        '<line x1="512" y1="0" x2="512" y2="1024" style="stroke:#EEEEEE;stroke-width:4;stroke-dasharray:10,10"/>',         # 添加更多小线段作为辅助格子(可选)        # '<line x1="256" y1="0" x2="256" y2="1024" style="stroke:#F5F5F5;stroke-width:2"/>',        # '<line x1="768" y1="0" x2="768" y2="1024" style="stroke:#F5F5F5;stroke-width:2"/>',        # '<line x1="0" y1="256" x2="1024" y2="256" style="stroke:#F5F5F5;stroke-width:2"/>',        # '<line x1="0" y1="768" x2="1024" y2="768" style="stroke:#F5F5F5;stroke-width:2"/>'    ]     if len(svg_path) == 1:        svg_code = []        svg_code_temp = '<svg style="width:' + svg_output['width'] + '; height:' + svg_output['height'] + ';" xmlns="' + \                        svg_output['xmlns'] + '">'        svg_code.append(svg_code_temp)        # Add grid lines        svg_code.extend(grid_lines)        svg_code_temp = '  <g transform="translate(0, 900) scale(1, -1)">'        svg_code.append(svg_code_temp)        svg_code_temp = '      <path d="' + svg_path[0] + '" style="fill:' + svg_output[            'font_color_last'] + ';"></path>'        svg_code.append(svg_code_temp)        svg_code_temp = '   </g>'        svg_code.append(svg_code_temp)        svg_code_temp = '</svg>'        svg_code.append(svg_code_temp)        svgcode = '\n'.join(svg_code)        svg_output['output_filename'] = svg_output['output_address'] + '/' + chinese + '1.png'        try:            svg2png(bytestring=svgcode, write_to=svg_output['output_filename'])        except Exception as e:            print('error:' + str(e))         # 生成完整的笔顺图,生成svg图片        svg_code = []        svg_code_temp = '<svg style="width:' + svg_output['width'] + '; height:' + svg_output[            'height'] + ';" xmlns="' + svg_output['xmlns'] + '">'        svg_code.append(svg_code_temp)        # Add grid lines        # svg_code.extend(grid_lines)  # 这里确定是否在svg中添加米字格        svg_code_temp = '<g transform="translate(0, 900) scale(1, -1)">'        svg_code.append(svg_code_temp)        for j in range(len(svg_path)):            svg_code_temp = '    <path d="' + svg_path[j] + '" style="fill:' + svg_output[                'font_color'] + ';"></path>'            svg_code.append(svg_code_temp)        svg_code_temp = ' </g>'        svg_code.append(svg_code_temp)        svg_code_temp = '</svg>'        svg_code.append(svg_code_temp)        svgcode = '\n'.join(svg_code)        svg_output['output_filename'] = svg_output['output_address'] + '/' + chinese + '.svg'  # 修改文件扩展名为.svg        try:            with open(svg_output['output_filename'], 'w') as f:                f.write(svgcode)        except Exception as e:            print('error:' + str(e))    else:        for i in range(len(svg_path)):            svg_code = []            svg_code_temp = '<svg style="width:' + svg_output['width'] + '; height:' + svg_output[                'height'] + ';" xmlns="' + svg_output['xmlns'] + '">'            svg_code.append(svg_code_temp)            # Add grid lines            svg_code.extend(grid_lines)            svg_code_temp = '  <g transform="translate(0, 900) scale(1, -1)">'            svg_code.append(svg_code_temp)            for j in range(i + 1):                if j == i:                    svg_code_temp = '      <path d="' + svg_path[j] + '" style="fill:' + svg_output[                        'font_color_last'] + ';"></path>'                else:                    svg_code_temp = '      <path d="' + svg_path[j] + '" style="fill:' + svg_output[                        'font_color'] + ';"></path>'                svg_code.append(svg_code_temp)            svg_code_temp = '   </g>'            svg_code.append(svg_code_temp)            svg_code_temp = '</svg>'            svg_code.append(svg_code_temp)            svgcode = '\n'.join(svg_code)            svg_output['output_filename'] = svg_output['output_address'] + '/' + chinese + '_' + str(i + 1) + '.png'            try:                svg2png(bytestring=svgcode, write_to=svg_output['output_filename'])            except Exception as e:                print('error:' + str(e))         # 生成完整的笔顺图,生成svg图片        svg_code = []        svg_code_temp = '<svg style="width:' + svg_output['width'] + '; height:' + svg_output[            'height'] + ';" xmlns="' + svg_output['xmlns'] + '">'        svg_code.append(svg_code_temp)        # Add grid lines        # svg_code.extend(grid_lines)  # 这里确定是否在svg中添加米字格        svg_code_temp = '<g transform="translate(0, 900) scale(1, -1)">'        svg_code.append(svg_code_temp)        for j in range(len(svg_path)):            svg_code_temp = '    <path d="' + svg_path[j] + '" style="fill:' + svg_output[                'font_color'] + ';"></path>'            svg_code.append(svg_code_temp)        svg_code_temp = ' </g>'        svg_code.append(svg_code_temp)        svg_code_temp = '</svg>'        svg_code.append(svg_code_temp)        svgcode = '\n'.join(svg_code)        svg_output['output_filename'] = svg_output['output_address'] + '/' + chinese + '.svg'  # 修改文件扩展名为.svg        try:            with open(svg_output['output_filename'], 'w') as f:                f.write(svgcode)        except Exception as e:            print('error:' + str(e))  def main(svg_path, chinese):    ChangeSVG2png(svg_path, chinese)  if __name__ == '__main__':    svg_data = '''        <path d="M 443 715 Q 449 729 501 798 Q 513 811 498 827 Q 455 862 426 860 Q 414 860 416 844 Q 421 780 378 709 Q 357 673 321 635 Q 302 623 270 583 Q 265 573 269 570 Q 270 569 276 570 Q 341 581 428 694 L 443 715 Z" class="stroke1"/>        <path d="M 531 559 Q 586 639 667 694 Q 682 703 678 714 Q 678 717 674 720 Q 664 730 615 758 Q 604 762 592 759 L 581 754 Q 576 754 488 725 Q 470 718 443 715 C 413 711 400 703 428 694 Q 442 686 480 690 Q 498 692 525 696 Q 551 700 557 698 Q 560 696 559 690 Q 534 610 490 552 L 490 551 C 473 526 514 534 531 559 Z" class="stroke2"/>        <path d="M 314 527 Q 308 533 255 539 Q 246 539 241 537 Q 239 537 237 535 Q 233 529 237 521 L 243 511 Q 279 455 302 372 Q 309 345 326 329 Q 342 314 349 319 Q 351 321 353 325 L 355 339 L 349 374 L 323 488 C 316 517 314 527 314 527 Z" class="stroke3"/>        <path d="M 700 406 Q 739 488 776 504 Q 798 523 782 545 Q 764 563 708 595 Q 688 606 663 598 Q 598 574 531 559 L 490 551 Q 402 535 314 527 C 284 524 294 479 323 488 Q 331 490 343 494 L 504 520 Q 562 533 624 543 Q 647 547 662 543 Q 670 540 674 535 Q 681 526 665 478 L 646 425 Q 645 423 643 419 C 632 391 687 379 700 406 Z" class="stroke4"/>        <path d="M 425 348 L 580 366 L 705 374 Q 714 376 717 385 Q 717 392 700 406 L 643 419 L 636 419 Q 564 404 498 393 L 437 384 L 349 374 C 319 371 325 339 355 339 L 368 339 L 425 348 Z" class="stroke5"/>        <path d="M 310 192 L 323 194 Q 412 213 472 223 Q 492 225 490 235 Q 490 237 488 241 Q 474 256 449 259 Q 421 261 368 243 Q 345 235 310 220 C 282 208 280 187 310 192 Z" class="stroke6"/>        <path d="M 310 220 Q 310 295 308 296 Q 284 318 263 327 Q 251 332 242 331 Q 230 327 232 316 Q 265 251 262 157 L 261 143 Q 257 97 217 55 Q 208 41 210 25 L 210 24 L 212 18 Q 220 -8 231 -18 Q 239 -23 247 -19 Q 251 -16 255 -12 Q 276 18 449 118 Q 468 127 472 134 Q 476 144 468 145 Q 457 145 323 94 Q 309 89 306 96 Q 304 98 304 106 Q 308 143 310 192 L 310 220 Z" class="stroke7"/>        <path d="M 741 333 L 739 325 Q 737 295 662 247 Q 604 210 589 199 C 564 182 559 171 588 180 Q 657 201 690 216 Q 765 250 796 254 Q 816 257 813 274 L 813 278 Q 806 299 784 318 Q 762 337 752 337 Q 743 338 741 333 Z" class="stroke8"/>        <path d="M 589 199 Q 593 247 600 287 Q 602 300 599 306 Q 597 311 592 314 Q 581 326 557 335 Q 544 339 535 334 Q 529 331 531 322 Q 546 253 546 237 Q 544 126 547 99 Q 548 82 553 61 Q 558 38 580 20 Q 637 -27 757 -18 Q 780 -16 800 -12 Q 823 -7 836 -3 Q 872 9 911 37 Q 923 47 919 61 Q 919 63 916 69 Q 907 87 894 165 Q 894 177 888 182 Q 885 184 883 179 Q 882 179 879 171 Q 848 99 838 71 Q 823 55 790 45 Q 741 30 684 38 Q 652 43 635 50 Q 609 61 600 80 Q 583 115 587 166 L 588 180 L 589 199 Z" class="stroke9"/>        '''    stroke_order = []    for line in svg_data.split('\n'):        if line.strip():            match = re.search(r'd="([^"]+)"', line)            if match:                path_data = match.group(1)                stroke_order.append(path_data)     # 生成笔顺图    chinese = '&#15499;'  # 修改为您要生成的汉字    svg_path = [x for x in stroke_order]    print(svg_path)    print(len(svg_path))    main(svg_path, chinese)

注:需要安装cairosvg程序(gtk3-runtime-3.24.31-2022-01-04-ts-win64),我会在附件中提供

此程序会生成笔顺图png(旧笔顺为黑色、新笔顺为红色),并生成一个完整的svg文件(全黑)

书写动画gif生成

# 生成gif.pyimport osimport asynciofrom playwright.async_api import async_playwrightimport subprocessimport mathfrom concurrent.futures import ThreadPoolExecutor  async def svg_to_gif(char, unhandled_num, svg_path, gif_path, frame_rate=12):    """将 SVG 动画转换为 GIF,并替换蓝色为红色"""    frames_dir = os.path.join(os.path.dirname(gif_path), f"{char}_frames")    os.makedirs(frames_dir, exist_ok=True)     async with async_playwright() as p:        browser = await p.chromium.launch()        page = await browser.new_page()        await page.set_viewport_size({"width": 1024, "height": 1024})         # 读取 SVG 并替换颜色        with open(svg_path, 'r', encoding='utf-8') as f:            svg_content = f.read()         # 关键修改:替换蓝色为红色        svg_content = svg_content.replace("stroke: blue", "stroke: #FF1111")         await page.set_content(f"""            <!DOCTYPE html>            <html>            <head>                <style>                    body {{ margin: 0; background: transparent; }}                    svg {{                        width: 1024px;                        height: 1024px;                        position: absolute;                        left: 0;                        top: 0;                    }}                </style>            </head>            <body>                {svg_content}            </body>            </html>        """)         # 计算动画时长(保持不变)        await page.wait_for_selector('svg')        await page.wait_for_timeout(500)        total_duration = await page.evaluate("""() => {            const anims = Array.from(document.querySelectorAll('*'))               .flatMap(el => el.getAnimations?.() || []);            return anims.length? Math.max(...anims.map(a => {                const timing = a.effect.getComputedTiming();                return timing.endTime || timing.delay + timing.duration;            })) / 1000 : 0;        }""")         if not total_duration or math.isnan(total_duration):            print("使用默认动画时长 5 秒")            total_duration = 5.0         total_frames = int(total_duration * frame_rate)        print(f"处理 {os.path.basename(svg_path)}: 时长 {total_duration:.2f}s, 总帧数 {total_frames}")         # 捕获帧        for frame in range(total_frames):            current_time = (frame / total_frames) * total_duration * 1000            await page.evaluate("""(time) => {                document.getAnimations().forEach(anim => anim.currentTime = time);            }""", current_time)             await page.screenshot(                path=os.path.join(frames_dir, f"frame_{frame:04d}.png"),                type="png",                omit_background=True,                clip={"x": 0, "y": 0, "width": 1024, "height": 1024}            )            print(f"\r{os.path.basename(svg_path)} 渲染进度: {frame + 1}/{total_frames}", end='')         await browser.close()     # 生成 GIF    print(f"\n{os.path.basename(svg_path)} 生成 GIF...")    try:        subprocess.run([            "ffmpeg", "-y",            "-framerate", str(frame_rate),            "-i", os.path.join(frames_dir, "frame_%04d.png"),            "-vf", "split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse",            "-loop", "0",            gif_path        ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)        print(f"{unhandled_num} {os.path.basename(svg_path)} GIF 已生成: {gif_path}")    except subprocess.CalledProcessError as e:        print(f" {os.path.basename(svg_path)} FFmpeg 错误: {e}")     # 清理临时文件    for file in os.listdir(frames_dir):        os.remove(os.path.join(frames_dir, file))    os.rmdir(frames_dir)  def process_single(svg_path, char, unhandled_num, char_gif_path):    """处理单个文件"""     char_gif_path_name = os.path.join(char_gif_path, f'{char}.gif')     asyncio.run(svg_to_gif(char, unhandled_num, svg_path, char_gif_path_name, frame_rate=15))  def main(svg_dir, max_workers=4):    """多线程主函数"""    svg_files = []    svg_list = os.listdir(svg_dir)    # 保存路径    char_gif_path = f'Gifs'    os.makedirs(char_gif_path, exist_ok=True)    gifs_list = [gif.split('.')[0] for gif in os.listdir(char_gif_path) if gif.endswith('.gif')]     # 比对未保存的,拿来使用    svg_unhandled_list = []    for tmp in svg_list:        char_code = int(os.path.splitext(tmp)[0])        char = chr(char_code)        if not char in gifs_list:            svg_unhandled_list.append(tmp)     unhandled_num = len(svg_unhandled_list)    for svg in svg_unhandled_list:        try:            char_code = int(os.path.splitext(svg)[0])            char = chr(char_code)            svg_files.append((os.path.join(svg_dir, svg), char))        except ValueError:            print(f"跳过非数字文件名: {svg}")     with ThreadPoolExecutor(max_workers=max_workers) as executor:        futures = []        for svg_path, char in svg_files:            futures.append(executor.submit(process_single, svg_path, char, unhandled_num, char_gif_path))         for future in futures:            try:                future.result()  # 等待任务完成            except Exception as e:                print(f"处理出错: {e}")  if __name__ == '__main__':    # 在txt中保存进度    svg_dir = r'makemeahanzi-master\svgs'    main(svg_dir, max_workers=4)  # 设置同时处理4个文

下载地址
  • 提取密码
  • 1561
  • 解压密码
  • DWQwdewq
    立即免费下载
    Python汉字笔顺图及书写gif动画生成代码
收藏 (15) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 ()

所有文章为演示数据,不提供下载地址,版权归原作者所有,仅提供演示效果!

CMS主题网 php教程 Python汉字笔顺图及书写gif动画生成代码 /showinfo-48-460-0.html

我们只做高端Wordpress主题开发!

常见问题
  • 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。
查看详情
  • 最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用
查看详情

相关文章

帝国CMS二次开发 函数文件      PRinterror()/e/class/connect.phpline 132query()/e/class/db_sql.php line 10fetch1()/e/class/db_sql.php line 30fetch()/e/class/db_sql.php line 22checklevel()/e/class/functions.php line 3414insert_dolog()/e/class/functions.php line 3...
#推荐
2026-03-17 13 C币
Python开发一个ChatGPT GU      1、首先去下载这个ChatGPT库,用到的库是这个:https://github.com/acheong08/ChatGPT2、安装这个ChatGPT库:pip3 install revChatGPT==0.0.a423、同目录还需要一个“config.json”:{    &quot;session_token&quot;: &quot;&quot;,    &quot;cf_clearance&quot;: &quot;&quot;,    &quot;user_agent&quot;: &quot;
#推荐
2026-03-17 4 C币
Playwright闲鱼智能监控机      项目介绍Playwright闲鱼智能监控机器人项目,基于 Playwright 和AI过滤分析的闲鱼多任务实时监控与智能分析工具,配备了功能完善的 Web 管理界面。可以实时按规则抓取闲鱼商品,垃圾佬的最爱。闲鱼智能监控机器人:https://github.com/dingyufei615/ai-goof...
#推荐
2026-03-17 3 C币
帝国CMS判断当前数据库是      有时候我们需要判断数据库是否包含某字段,就可以使用下面这段SQL语法,$fr=$empire-&gt;fetch1(&quot;SELECT COUNT(*) AS column_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = &amp;#39;$infotb&amp;#39; AND COLUMN_NAME = &amp;#39;money&amp;#39;&quot;);if($fr[&amp;...
#推荐
2026-03-17 3 C币
帝国CMS8.0父子信息调用方      帝国CMS8.0版新增父子信息功能,让一条信息也能成为一个信息、一个栏目、一个专题、甚至一个网站。本文共有四个部分:一、父子信息功能使用流程。二、调用子信息:可以用索引灵动标签调用。三、父子信息列表访问地址的语法说明。四、进阶:调用当前父子信息...
#推荐
2026-03-17 3 C币
ajax上传文件进度条功能示      ajax上传文件时,有时比较耗时,需要在界面上显示下进度信息,获取ajaxSettings中的xhr对象,为它的upload属性绑定progress事件的处理函数前端代码&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&quot;utf8&quot;&gt;&lt;title&gt;test upload&lt;/title&gt;&lt;!--jquery--&gt;&lt;script src=&quot;h...
#推荐
2026-03-17 3 C币
帝国cms后台如何上传视频      方案一,通过编辑器上传1、上传文件之前,需要修改一下系统设置里面的文件设置,位置在:系统-系统设置-系统参数设置-文件设置修改附件上传大小和类型,1024KB是1M,2048KB就是2M。上传文件扩展名,增加一个.mp4,前面要用|来分开,也就是添加“|.mp4”2、完成上面的...
#推荐
2026-03-17 3 C币
苹果cms主题模板安装教程      模板安装教程1,把主题包上传到你的域名对应的根目录,(template) 文件夹里面,如果是压缩包记得解压2:然后我们进入template文件夹,打开刚上传的模板文件夹,一般里面包含 html 的文件夹,就是模板目录名了;(有时候,模板目录是html,有时候是html-my,也有可能是其他名)3...
#推荐
2026-03-17 2 C币