保姆级教程,拿来就能用!手把手教你如何制作属于自己的小卡片【window版】
柔枝姐姐是女王
编辑于 2024年01月08日 05:29
收录于文集
共5篇

大家好,我是枝枝。

最近很眼馋某人的小卡片,把文字记录在小卡片里,简洁又美观,真是泰裤辣!

终于狠下心,在ChatGPT的帮助下,自己手把手写了一个程序。

如果有更好的方法,欢迎告诉我,感激不尽!

对你有用的话,记得动动小手指,点个赞哟!

以下是相关代码分享,注释里包含了我对代码逻辑的理解,哪怕零基础,拿去就能用。

使用工具:Python,vscode

需要准备:个人头像,字体文件

还有,最重要的:思考逻辑的啊脑袋

01图片部分

代码块
text/x-python
自动换行
复制代码
# 创建渐变图片
def generate_gradient(height1):
    """
    生成一个从上到下的渐变背景图片。
    参数:
    width -- 图片宽度
    height -- 图片高度
    start_color -- 渐变开始的颜色 (RGB格式)
    end_color -- 渐变结束的颜色 (RGB格式)
    """

    # 定义图像大小和颜色
    width = 580
    font = ImageFont.truetype('simhei', 30)
    count = font.getsize('枝')[1] # 计算单个字符高度

    # 设定图片高度随文字行数而调整
    height = height1 * count + 340 + height1 * 13

    end_color = (191, 151, 211)  # 渐变开始颜色
    start_color = (210, 231, 220)    # 渐变结束颜色,紫色
    base = Image.new('RGB', (width, height))  # 创建图片
    draw = ImageDraw.Draw(base)  # 创建绘图对象

    # 对角线的长度
    diagonal_length = math.sqrt(width**2 + height**2)

    # 遍历每一个像素点,计算每一个像素点的RGB
    for x in range(width):
        for y in range(height):
            # 当前点到起始点的距离
            distance = math.sqrt(x**2 + y**2)
            # 计算当前位置的颜色
            ratio = distance / diagonal_length  # ratio 的范围是0-1,0表示起点坐标, 1表示对角线对点坐标
            r = int((end_color[0] - start_color[0]) * ratio + start_color[0]) # end_color[0] - start_color 这个计算表示渐变的颜色范围,乘于ratio表示处于哪个渐变颜色
            g = int((end_color[1] - start_color[1]) * ratio + start_color[1]) # 如果ratio代表颜色渐变的比例,取值两个颜色的差值
            b = int((end_color[2] - start_color[2]) * ratio + start_color[2]) # 每个像素点最终呈现的颜色就和三原色原理相关了,也就是红绿蓝,有个基本规律是数值越大越亮
            draw.point((x, y), (r, g, b)) # 绘制颜色

    # 定义边距和圆角半径
    margin = 60  # 边距
    corner_radius = 28  # 圆角的半径
    shadow_offset = 10  # 阴影偏移量
    shadow_blur = 15  # 阴影模糊半径

    # 计算方形的左上角和右下角坐标
    left = margin
    top = margin
    right = width - margin
    bottom = height - margin

    # 创建阴影图像
    shadow_image = Image.new('RGBA', (width, height), (0, 0, 0, 0))
    shadow_draw = ImageDraw.Draw(shadow_image)

    # 阴影矩形的位置
    shadow_rect = [left + shadow_offset, top + shadow_offset,
                   right + shadow_offset, bottom + shadow_offset]

    # 绘制阴影矩形,确定四个角后绘制矩形
    shadow_draw.rectangle(shadow_rect, fill=(0, 0, 0, 128))

    # 应用模糊滤镜
    shadow_image = shadow_image.filter(
        ImageFilter.GaussianBlur(radius=shadow_blur))

    # 将阴影合并到原图
    base.paste(shadow_image, (0, 0), shadow_image)

    # 绘制圆角矩形
    draw = ImageDraw.Draw(base)

    # 绘制矩形的四个边
    color = (226, 200, 225)  # 定义矩形的颜色
    draw.rectangle([left + corner_radius, top, right -
                   corner_radius, bottom], fill=color)
    draw.rectangle([left, top + corner_radius, right,
                   bottom - corner_radius], fill=color)

    # 绘制四个角的圆
    draw.pieslice([left, top, left + 2 * corner_radius, top +
                  2 * corner_radius], start=180, end=270, fill=color)
    draw.pieslice([right - 2 * corner_radius, top, right, top +
                  2 * corner_radius], start=270, end=360, fill=color)
    draw.pieslice([left, bottom - 2 * corner_radius, left + 2 *
                  corner_radius, bottom], start=90, end=180, fill=color)
    draw.pieslice([right - 2 * corner_radius, bottom - 2 *
                  corner_radius, right, bottom], start=0, end=90, fill=color)

    return base
复制成功

02文字部分

1)因为我想要每行绘制11个字,Python里中英文字符宽度是1比1的,但是实际生成的文字,英文只有中文大小的一半。为了视觉上对齐中英文,需要编写一个计算中英文所占字符宽度的规则。

代码块
text/x-python
自动换行
复制代码
# 获取字符宽度
def get_char_width(char):
    if ('\u4e00' <= char <= '\u9fff') or ('\u3000' <= char <= '\u303F') or ('\u0020' <= char <= '\u007F'):  # 假设这个范围内的是中文字符 或者中文标点符号
        char_width = 2
    elif char == ')' or '(' or ',':
        char_width = 2
    else:
        char_width = 1.1  # 定义单字所占文本长度
    return char_width
复制成功

2)一般来说,句首都不是标点符号

代码块
text/x-python
自动换行
复制代码
# 是否中英文标点符号
def is_cjk_or_english_punctuation(char):
    if ('\u3000' <= char <= '\u303F' or '\u0020' <= char <= '\u007F'):
        return True
    else:
        return False
复制成功

代码块
text/x-python
自动换行
复制代码
# 开始编写换行规则, 句首不能是标点符号
def change_line(text):
    # 这一步是处理从别的别的地方复制过来的文本
    text = text.replace(u'\xa0', u' ')  # 将不间断空白符 (NO-BREAK SPACE) 替换为标准空格
    text = text.replace(u'\u3000', u' ')  # 将全角空格 (IDEOGRAPHIC SPACE) 替换为标准空格

    # 标点符号列表
    punctuation = ['。', ',', '?', '!', ';', ':',
                   ',', '.', '!', '?', ';', ':']

    # 先定义初始变量, 用来储存文字和长度定义
    current_width = 0  # 定义文本长度变量
    final_text = []  # 定义储存所有句子的列表
    current_line = ''  # 定义初始句子长度, 用来储存句子

    for char in text:
        char_width = get_char_width(char)

        if current_width + char_width <= 22:  # 如果当前文本长度 和 当前的文字宽度相加还是小于等于限定的14
            '''处理换行逻辑, 把换行符前面的变成一行, 因为这是遍历所有字符, 不添加这个逻辑, 原文 ”#学习原则

            如果问题有替代方案,过“ 会变成 “['#学习原则\n\n如果问题', '有替代方案,暂时找不到', '原因,过']”'''
            if char is '\n':
                final_text.append(current_line)
                current_line = ''
                current_width = 0
                continue
            current_width += char_width  # 更新当前文本长度
            current_line += char  # 把文字添加到句子
        else:
            # 如果这个字符是在标点符号范围内并且当前行末尾不是标点符号,
            if is_cjk_or_english_punctuation(char) == True and current_line and is_cjk_or_english_punctuation(current_line[-1]) == False or char in punctuation:
                last_char = current_line[-1]  # 分离最后一个字符
                final_text.append(current_line[:-1]) # 把分离最后一个字符后的句子添加到句子列表
                current_line = last_char + char # 更新句子
                current_width = get_char_width( 
                    last_char) + get_char_width(char) # 更新句子长度
            else:
                final_text.append(current_line)  # 把句子添加到句子列表
                current_line = char  # 更新这次循环的字符为下一行第一个字符
                current_width = char_width  # 更新句子长度

    # 把最后一行添加进句子列表, 因为如果最后一句不满足14个字符或者15个的话, 不会被上面的逻辑添加进句子列表
    if current_line:
        final_text.append(current_line)

    # 用换行符连接句子列表, 重新变成字符串

    final_text = '\n'.join(final_text)  # 用空格是因为已经分行了, 用'\n' 会在输入文本换行的时候多出空行
    return final_text
复制成功

3)开始绘制文字,在测试过程中不断调整图片宽度,文字大小,文字位置,等等。

代码块
text/x-python
自动换行
复制代码
# 绘制文字
def draw_text(img, final_text, height):

    # 创建可绘制对象
    draw = ImageDraw.Draw(img)

    # 选择字体和字号
    font = ImageFont.truetype('simhei.ttf', 30)

    # 选择字体颜色
    text_color = (0, 0, 0)

    # 选择文字位置
    text_position = (113, 175)

    # 设定行距和字距
    char_spacing = 2
    line_spacing = 13
    x, y = text_position  # 解包字符初始绘制坐标

    # 遍历文本的每一行,设定间距行距
    for line in final_text.split('\n'):
        if line.strip(): # 如果文本不是空行
            current_x = x  # 设置在这里实现重置每行起始绘制位置
            for char in line: # 遍历一行的每个字符

                # 绘制文字
                draw.text((current_x, y), char, font=font, fill=text_color) # 绘制文字
                current_x += font.getsize(char)[0] + char_spacing # 刷新绘制文字的横坐标
            y += font.getsize(line)[1] + line_spacing  # 放在句子的循环内实现控制行距
        else:
            y += font.getsize('枝')[1] + line_spacing
    # 保存修改后的图片
    # 指定字体路径
    if hasattr(sys, '_MEIPASS'):
        # 在打包后的环境中
        font_path = os.path.join(
            sys._MEIPASS, 'fonts', 'SmileySans-Oblique.otf')
    else:
        # 在开发环境中
        font_path = os.path.join(os.path.dirname(
            __file__), 'fonts', 'SmileySans-Oblique.otf')
    # 绘制日期时间
    draw.text((105, (height + 1) * font.getsize('枝')[1] + 170 + (13 * height) + 13), datetime.now().strftime('  %I:%M  %p  %m,%b  %Y'),
              font=ImageFont.truetype(font_path, 25), fill=(150, 150, 150))  # 日期时间

    draw.text((175, 110),  '枝枝',
              font=ImageFont.truetype('simhei', 27), fill=(8, 4, 6))  # 作者署名
    # 创建保存图片的文件夹
    image_dir = 'D:\zhiz_image'
    os.makedirs(image_dir, exist_ok=True)

    # 生成图片名称
    img_name = datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + '.jpg'
    # 指定保存路径
    save_path = os.path.join(
        image_dir, img_name)

    # 打开头像文件
    if hasattr(sys, '_MEIPASS'):
        # 在打包后的环境中
        avater_path = os.path.join(sys._MEIPASS, 'images', '枝枝头像.jpg')
    else:
        # 在开发环境中
        avater_path = os.path.join(
            os.path.dirname(__file__), 'images', '枝枝头像.jpg')

    avater = Image.open(avater_path) # 打开头像文件
    avater = avater.resize((60, 60), Image.Resampling.LANCZOS)  # 把图片尺寸改小
    img.paste(avater, (105, 90))  # 把头像粘贴到图片里, 参数是文件路径, 粘贴坐标
 
    # 把最终制作好的图片复制到剪切板
    output = io.BytesIO()
    img.convert("RGB").save(output, "BMP")
    data = output.getvalue()[14:]
    output.close()

    win32clipboard.OpenClipboard()
    win32clipboard.EmptyClipboard()
    win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
    win32clipboard.CloseClipboard()

    # 保存图片
    img.save(save_path)
    # 让图片处于选中状态
    subprocess.Popen(r'explorer /select,%s' %
                     save_path)  # 意思是, 打开资源管理器/选中命令, 选中的文件路径,
复制成功

03UI界面

绑定到UI界面里,方便打包为独立程序使用。

在这里使用上面编写好的函数,最后运行。

代码块
text/x-python
自动换行
复制代码
# Tkinter GUI 设置
def setup_gui():
    root = tk.Tk()
    root.title('枝枝宝')
    
    # 设定UI界面大小、位置,我比较喜欢处于屏幕中间
    window_width = 400
    window_height = 400
    screen_width = root.winfo_screenwidth()
    screen_height = root.winfo_screenheight()
    position_right = int(screen_width / 2 - window_width / 2)
    position_top = int(screen_height / 2 - window_height / 2)
    root.geometry('%dx%d+%d+%d' %
                  (window_width, window_height, position_right, position_top))

    # 设定UI界面中的组件,例如文本框、按钮等。
    a_label = tk.Label(root, text='枝枝宝请输入文字:')
    a_label.grid(row=0, column=0)
    a_text = tk.Text(root, height=17, width=28)
    a_text.grid(row=0, column=1, padx=10, pady=10)
    a_text.focus_set()

    # 定义作图快捷键
    def on_button_click(event=None):
        text = a_text.get("1.0", "end-1c").rstrip()
        a_text.delete('1.0', 'end')
        a_text.insert('1.0', text)
        print('原始文本是{}'.format(text))
        final_text = change_line(text)
        height = (final_text.count('\n') + 1)
        print('最终文本是', final_text)
        img = generate_gradient(height)
        draw_text(img, final_text, height)
        return 'break'
    
    # 设定按钮来生成图片
    button = tk.Button(root, text='枝枝宝大帅哥', command=on_button_click)
    button.grid(row=2, column=1, columnspan=2, padx=10, pady=10)
    root.bind('<Shift-Return>', lambda event: on_button_click(event))

    root.mainloop()


# 主函数
if __name__ == "__main__":
    setup_gui()
复制成功

04完

代码也不是很多,如果使用过程中遇到问题,欢迎来找我哦,如果能帮到你,非常开心。