Python实现文字转ASCII艺术字生成器

1. 简介

作为一个程序员,经常看到console里面有很多使用各种字符构建的文字,一直以为是自己拼出来的,但是这样老麻烦了,所以就想着用python的图像处理看看能不能实现这样的功能。

2. 具体依赖

  • Python 3.x
  • PIL (Pillow) - 图像处理
  • NumPy - 数组操作
  • 跨平台字体检测

3. 核心实现

3.1跨平台字体检测

python 复制代码
def get_system_font():
    """获取系统可用的字体"""
    font_paths = []

    # Windows 字体路径
    if os.name == 'nt':
        font_dirs = [
            os.path.join(os.environ['WINDIR'], 'Fonts'),
            os.path.join(os.environ['SYSTEMROOT'], 'Fonts'),
        ]
        for font_dir in font_dirs:
            if os.path.exists(font_dir):
                # 中文常用字体
                chinese_fonts = [
                    'simhei.ttf',  # 黑体
                    'msyh.ttc',  # 微软雅黑
                    'simsun.ttc',  # 宋体
                    'msjh.ttc',  # 微软正黑体
                    'kaiu.ttf',  # 标楷体
                ]
                for font in chinese_fonts:
                    font_path = os.path.join(font_dir, font)
                    if os.path.exists(font_path):
                        return font_path

3.2 文字到ASCII转换算法

python 复制代码
def text_to_ascii_simple(text, char='*', width=60):
    """
    简化的版本,兼容性更好
    """
    # 创建图片
    font_size = 80
    img_width = font_size * len(text) + 100
    img_height = font_size + 50

    img = Image.new('RGB', (img_width, img_height), 'white')
    draw = ImageDraw.Draw(img)

    # 获取字体
    font_path = get_system_font()

    try:
        if font_path:
            font = ImageFont.truetype(font_path, font_size)
        else:
            font = ImageFont.load_default()
    except:
        font = ImageFont.load_default()

3.3 字体检测机制

  • Windows系统:检测C:\Windows\Fonts目录下的中文字体
  • macOS系统:检测/System/Library/Fonts等目录
  • Linux系统:检测/usr/share/fonts等目录
python 复制代码
# Linux 字体路径
font_dirs = [
    '/usr/share/fonts',
    '/usr/local/share/fonts',
    os.path.expanduser('~/.fonts'),
]

3.4 图像处理

  • 创建图像:根据文字长度创建适当大小的图像
  • 文字绘制:使用检测到的字体绘制文字
  • 灰度转换:将彩色图像转换为灰度图像
  • 字符映射:根据灰度值映射为ASCII字符
python 复制代码
# 转换为灰度
img_gray = img.convert('L')
img_array = np.array(img_gray)

# 转换为字符
lines = []
for y in range(new_h):
    line = ''.join([' ' if resized.getpixel((x, y)) > 200 else char for x in range(width)])
    lines.append(line)

3.5 智能裁剪算法

主要是为了将字符转成图片的时候去掉上部和下部的空白

python 复制代码
# 找到文字边界
non_white_rows = np.where(np.any(arr < 250, axis=1))[0]
non_white_cols = np.where(np.any(arr < 250, axis=0))[0]

if len(non_white_rows) == 0 or len(non_white_cols) == 0:
    return [f"无法显示: {text}"]

top, bottom = non_white_rows[0], non_white_rows[-1]
left, right = non_white_cols[0], non_white_cols[-1]

4. 完整实现

python 复制代码
from PIL import Image, ImageDraw, ImageFont
import numpy as np
import os


def get_system_font():
    """获取系统可用的字体"""
    font_paths = []

    # Windows 字体路径
    if os.name == 'nt':
        font_dirs = [
            os.path.join(os.environ['WINDIR'], 'Fonts'),
            os.path.join(os.environ['SYSTEMROOT'], 'Fonts'),
        ]
        for font_dir in font_dirs:
            if os.path.exists(font_dir):
                # 中文常用字体
                chinese_fonts = [
                    'simhei.ttf',  # 黑体
                    'msyh.ttc',  # 微软雅黑
                    'simsun.ttc',  # 宋体
                    'msjh.ttc',  # 微软正黑体
                    'kaiu.ttf',  # 标楷体
                ]
                for font in chinese_fonts:
                    font_path = os.path.join(font_dir, font)
                    if os.path.exists(font_path):
                        return font_path

                # 如果没找到中文字体,找第一个ttf文件
                for file in os.listdir(font_dir):
                    if file.lower().endswith(('.ttf', '.ttc', '.otf')):
                        return os.path.join(font_dir, file)

    # macOS 字体路径
    elif os.name == 'posix':
        font_dirs = [
            '/System/Library/Fonts',
            '/Library/Fonts',
            os.path.expanduser('~/Library/Fonts'),
        ]
        for font_dir in font_dirs:
            if os.path.exists(font_dir):
                # macOS 中文字体
                chinese_fonts = [
                    'PingFang.ttc',
                    'STHeiti.ttc',
                    'Hiragino Sans GB.ttc',
                    'Apple SD Gothic Neo.ttc',
                ]
                for font in chinese_fonts:
                    font_path = os.path.join(font_dir, font)
                    if os.path.exists(font_path):
                        return font_path

                # 找第一个支持中文的字体
                for file in os.listdir(font_dir):
                    if file.lower().endswith(('.ttf', '.ttc', '.otf')):
                        return os.path.join(font_dir, file)

    # Linux 字体路径
    font_dirs = [
        '/usr/share/fonts',
        '/usr/local/share/fonts',
        os.path.expanduser('~/.fonts'),
    ]
    for font_dir in font_dirs:
        if os.path.exists(font_dir):
            # Linux 中文字体
            chinese_fonts = [
                'wqy-microhei.ttc',  # 文泉驿微米黑
                'wqy-zenhei.ttc',  # 文泉驿正黑
                'droid-sans-fallback.ttf',
            ]
            for font in chinese_fonts:
                font_path = os.path.join(font_dir, font)
                if os.path.exists(font_path):
                    return font_path

            # 找第一个字体文件
            for root, dirs, files in os.walk(font_dir):
                for file in files:
                    if file.lower().endswith(('.ttf', '.ttc', '.otf')):
                        return os.path.join(root, file)

    return None


def text_to_custom_ascii(text, char='*', width=80, font_size=100):
    """
    将任意文字转换为自定义字符的ASCII艺术,自动去除上下空白

    参数:
    - text: 要转换的文字
    - char: 使用的字符
    - width: 输出宽度
    - font_size: 字体大小
    """

    # 获取字体
    font_path = get_system_font()

    if font_path:
        try:
            font = ImageFont.truetype(font_path, font_size)
            print(f"使用字体: {os.path.basename(font_path)}")
        except:
            print("警告: 字体加载失败,使用默认字体")
            font = ImageFont.load_default()
    else:
        print("警告: 未找到系统字体,使用默认字体")
        font = ImageFont.load_default()

    # 创建图片(根据文字长度自适应)
    # 对于中文字符,每个字符需要更多空间
    img_width = max(len(text.encode('utf-8')) * font_size // 2, width * 10)
    img_height = font_size * 2

    img = Image.new('RGB', (img_width, img_height), 'white')
    draw = ImageDraw.Draw(img)

    # 对于默认字体,使用不同的方法
    if font.path is None:  # 默认字体
        # 使用textsize替代textbbox(兼容旧版本)
        try:
            # 尝试使用textsize
            text_width, text_height = font.getsize(text)
            x = (img_width - text_width) // 2
            y = (img_height - text_height) // 2
            draw.text((x, y), text, fill='black', font=font)
        except:
            # 如果还不行,简化处理
            print("警告: 字体可能不支持中文,尝试简化处理")
            x = img_width // 4
            y = img_height // 4
            draw.text((x, y), text, fill='black', font=font)
    else:
        # 使用textbbox(新版本PIL)
        try:
            text_bbox = draw.textbbox((0, 0), text, font=font)
            text_width = text_bbox[2] - text_bbox[0]
            text_height = text_bbox[3] - text_bbox[1]

            x = (img_width - text_width) // 2
            y = (img_height - text_height) // 2

            draw.text((x, y), text, fill='black', font=font)
        except Exception as e:
            print(f"警告: textbbox失败 ({e}),使用备用方法")
            # 备用方法
            x = img_width // 4
            y = img_height // 4
            draw.text((x, y), text, fill='black', font=font)

    # 转换为灰度
    img_gray = img.convert('L')
    img_array = np.array(img_gray)

    # 自动检测文字区域(去除上下空白)
    # 找到非白色(灰度值<250)的行
    rows_with_content = np.where(np.any(img_array < 250, axis=1))[0]

    if len(rows_with_content) > 0:
        top = max(0, rows_with_content[0] - 2)  # 留一点边距
        bottom = min(img_height, rows_with_content[-1] + 3)

        # 裁剪图片,只保留文字部分
        img_cropped = img_gray.crop((0, top, img_width, bottom + 1))
        cropped_height = bottom - top + 1
    else:
        # 如果没有找到内容,使用原始图片
        img_cropped = img_gray
        cropped_height = img_height

    # 计算新的高度,保持宽高比
    aspect_ratio = cropped_height / img_width
    new_height = max(1, int(aspect_ratio * width))

    # 调整大小
    img_resized = img_cropped.resize((width, new_height))

    # 转换为字符(简化版本,更清晰)
    output_lines = []

    for y in range(new_height):
        line = ""
        for x in range(width):
            gray = img_resized.getpixel((x, y))
            # 简单的阈值处理
            if gray > 220:  # 较亮的部分
                line += " "
            else:
                line += char
        output_lines.append(line)

    # 再次去除结果中的空白行(可选)
    # 移除顶部空白行
    while output_lines and all(c == ' ' for c in output_lines[0]):
        output_lines.pop(0)

    # 移除底部空白行
    while output_lines and all(c == ' ' for c in output_lines[-1]):
        output_lines.pop()

    return output_lines


def text_to_ascii_simple(text, char='*', width=60):
    """
    简化的版本,兼容性更好
    """

    # 创建图片
    font_size = 80
    img_width = font_size * len(text) + 100
    img_height = font_size + 50

    img = Image.new('RGB', (img_width, img_height), 'white')
    draw = ImageDraw.Draw(img)

    # 获取字体
    font_path = get_system_font()

    try:
        if font_path:
            font = ImageFont.truetype(font_path, font_size)
        else:
            font = ImageFont.load_default()
    except:
        font = ImageFont.load_default()

    # 使用try-except处理字体绘制
    try:
        # 尝试绘制文字
        x = 50
        y = 25
        draw.text((x, y), text, fill=(0, 0, 0), font=font)
    except Exception as e:
        print(f"警告: 文字绘制失败 ({e}),使用英文模式")
        # 如果失败,使用ASCII字符
        text = text.encode('ascii', 'ignore').decode('ascii')
        if not text:
            text = "TEXT"
        draw.text((x, y), text, fill=(0, 0, 0), font=font)

    # 转换为灰度并裁剪
    gray = img.convert('L')
    arr = np.array(gray)

    # 找到文字边界
    non_white_rows = np.where(np.any(arr < 250, axis=1))[0]
    non_white_cols = np.where(np.any(arr < 250, axis=0))[0]

    if len(non_white_rows) == 0 or len(non_white_cols) == 0:
        return [f"无法显示: {text}"]

    top, bottom = non_white_rows[0], non_white_rows[-1]
    left, right = non_white_cols[0], non_white_cols[-1]

    # 裁剪,加一点边距
    margin = 3
    cropped = gray.crop((
        max(0, left - margin),
        max(0, top - margin),
        min(gray.width, right + margin),
        min(gray.height, bottom + margin)
    ))

    # 调整大小
    cw, ch = cropped.size
    new_h = max(1, int(ch / cw * width * 0.5))
    resized = cropped.resize((width, new_h))

    # 转换为字符
    lines = []
    for y in range(new_h):
        line = ''.join([' ' if resized.getpixel((x, y)) > 200 else char for x in range(width)])
        lines.append(line)

    # 去除结果中的空白行
    while lines and all(c == ' ' for c in lines[0]):
        lines.pop(0)
    while lines and all(c == ' ' for c in lines[-1]):
        lines.pop()

    return lines


def main():
    print("文字转ASCII艺术字生成器")
    print("-" * 50)

    while True:
        text = input("\n请输入要显示的文字 (输入q退出): ")
        if text.lower() == 'q':
            break

        char = input("请输入要使用的字符 (默认:*): ").strip() or "*"
        width = input("请输入输出宽度 (默认:80): ").strip()
        width = int(width) if width.isdigit() else 80

        print("\n生成中...")

        try:
            # 使用简化版本
            result = text_to_ascii_simple(text, char=char, width=width)

            # 计算输出宽度
            output_width = max(len(line) for line in result) if result else width

            print("\n" + "=" * output_width)
            print(f"文字: {text}")
            print("=" * output_width)

            for line in result:
                print(line)

            print("=" * output_width)

            # 保存选项
            save = input("\n是否保存到文件? (y/n): ").lower()
            if save == 'y':
                filename = input("文件名 (默认: text_art.txt): ").strip() or "text_art.txt"
                with open(filename, 'w', encoding='utf-8') as f:
                    f.write('\n'.join(result))
                print(f"已保存到 {filename}")

        except Exception as e:
            print(f"生成失败: {e}")
            print("尝试使用更简单的版本...")

            # 最简单的回退方案
            print("\n简单版本:")
            print("=" * width)
            for i in range(5):
                spaces = (width - len(text) * 3) // 2
                if i == 2:
                    print(" " * spaces + text)
                else:
                    print(" " * spaces + char * (len(text) * 3))
            print("=" * width)


if __name__ == "__main__":
    main()

5. 实现效果

相关推荐
SatVision炼金士20 小时前
SDK管理本地java版本
java·开发语言·sdkman
奕成则成20 小时前
Python 创建虚拟环境
开发语言·python
独自破碎E20 小时前
什么时候@Async会失效?
java·开发语言
军军君0120 小时前
Three.js基础功能学习四:摄像机与阴影
开发语言·前端·javascript·3d·typescript·three·三维
Morwit20 小时前
如何使用CMake构建Qt新项目
开发语言·c++·qt
独自破碎E20 小时前
Leetcode1499满足不等式的最大值
java·开发语言
zmzb010320 小时前
C++课后习题训练记录Day62
开发语言·c++
蕨蕨学AI20 小时前
【Wolfram语言】36 创建云端应用
开发语言·wolfram
lambo mercy21 小时前
python入门
前端·数据库·python