不敢去表白?来用代码画♥

一、核心参数配置(常量定义)

python 复制代码
# 画布尺寸(正方形,640x640,视觉更协调)
CANVAS_WIDTH = 640
CANVAS_HEIGHT = 640

# 画布中心坐标(自动计算,用于将爱心居中)
CANVAS_CENTER_X = CANVAS_WIDTH / 2  # 320
CANVAS_CENTER_Y = CANVAS_HEIGHT / 2  # 320

# 爱心缩放比例(控制爱心整体大小,值越大爱心越大)
IMAGE_ENLARGE = 11

# 爱心颜色(单一粉红色 #Fd798f,搭配黑色背景更突出)
HEART_COLOR = "#Fd798f"

这些参数是动画的基础配置,直接决定了爱心的大小、位置、颜色和画布尺寸。

二、核心数学函数(生成爱心形状与动态效果)

1. 爱心轮廓生成函数 heart_function(t)

python 复制代码
def heart_function(t, shrink_ratio: float = IMAGE_ENLARGE):
    # 笛卡尔心形曲线公式(核心:用数学计算生成爱心形状)
    x = 16 * (sin(t) ** 3)
    y = -(15 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(3*t))  # 微调后更圆润

    # 缩放爱心(原始公式生成的爱心很小,需放大)
    x *= shrink_ratio
    y *= shrink_ratio

    # 将爱心移动到画布中心(原始公式以原点为中心,需偏移到画布中间)
    x += CANVAS_CENTER_X
    y += CANVAS_CENTER_Y

    return int(x), int(y)
  • 原理 :通过参数 t(范围 0~2π)计算爱心上的点坐标,t 遍历一周时,(x,y) 轨迹形成爱心形状。
  • 微调细节 :Y 坐标公式中最后一项用 cos(3t) 替代了常见的 cos(4t),让爱心轮廓更圆润。

2. 内部填充点生成函数 scatter_inside(x, y)

python 复制代码
def scatter_inside(x, y, beta=0.15):
    # 基于对数分布生成随机扩散比例(让点向中心聚集,避免均匀分布)
    ratio_x = - beta * log(random.random())
    ratio_y = - beta * log(random.random())

    # 计算扩散距离(距离中心越远,扩散幅度越大)
    dx = ratio_x * (x - CANVAS_CENTER_X)
    dy = ratio_y * (y - CANVAS_CENTER_Y)

    return x - dx, y - dy  # 返回扩散后的点(向中心收缩)
  • 作用:从爱心轮廓点出发,生成向内扩散的点,用于填充爱心内部,让爱心更饱满。
  • 参数 beta :控制扩散强度,值越大扩散范围越广(边缘点用 beta=0.3,中心填充点用 beta=0.2,实现 "边缘疏、中心密" 的效果)。

3. 外围光晕生成函数 shrink(x, y, ratio)

python 复制代码
def shrink(x, y, ratio):
    # 计算收缩力(距离中心越远,收缩力越强,让外围点形成环绕效果)
    force = -1 / (((x - CANVAS_CENTER_X) **2 + (y - CANVAS_CENTER_Y)** 2) **0.6)
    
    # 计算收缩距离(按比例调整,生成光晕)
    dx = ratio * force * (x - CANVAS_CENTER_X)
    dy = ratio * force * (y - CANVAS_CENTER_Y)
    
    return x - dx, y - dy  # 返回收缩后的点(形成外围光晕)
  • 作用:生成爱心外围的 "光晕" 效果,让爱心边缘有渐变的扩散感。

4. 跳动周期控制函数 curve(p)

python 复制代码
def curve(p):
    # 正弦函数生成周期性值(控制心跳的"收缩-扩张"节奏)
    return 2 * (2 * sin(4 * p)) / (2 * pi)
  • 输出特性 :值随 p 周期性变化(范围约 -0.63~0.63),模拟心跳的节奏。

三、Heart 类(动画核心逻辑)

该类负责生成爱心的所有点集,并计算每帧的位置,实现 "跳动" 效果。

1. 初始化 __init__

python 复制代码
def __init__(self, generate_frame=20):
    self._points = set()  # 爱心轮廓点
    self._edge_diffusion_points = set()  # 边缘扩散点
    self._center_diffusion_points = set()  # 中心填充点
    self.all_points = {}  # 存储每帧的所有点(key: 帧数,value: 点列表)
    self.build(2000)  # 生成基础点集
    self.generate_frame = generate_frame  # 动画周期(20帧)
    for frame in range(generate_frame):
        self.calc(frame)  # 预计算每帧的点位置
  • 核心:初始化三类点集,并提前计算动画周期内(20 帧)所有点的位置,保证动画流畅。

2. 生成基础点集 build

python 复制代码
def build(self, number):
    # 1. 生成2000个轮廓点(爱心的基本形状)
    for _ in range(number):
        t = random.uniform(0, 2 * pi)  # 随机参数t
        x, y = heart_function(t)  # 计算轮廓点
        self._points.add((x, y))

    # 2. 生成边缘扩散点(每个轮廓点生成3个,共6000个,让边缘柔和)
    for _x, _y in list(self._points):
        for _ in range(3):
            x, y = scatter_inside(_x, _y, 0.3)  # 边缘扩散强度0.3
            self._edge_diffusion_points.add((x, y))

    # 3. 生成中心填充点(4000个,让爱心内部实心)
    point_list = list(self._points)
    for _ in range(4000):
        x, y = random.choice(point_list)  # 随机选轮廓点
        x, y = scatter_inside(x, y, 0.2)  # 中心扩散强度0.2(比边缘弱)
        self._center_diffusion_points.add((x, y))
  • 逻辑:通过 "轮廓点 + 边缘点 + 中心填充点" 的组合,让爱心从外到内都有足够的点,视觉上更饱满。

3. 计算每帧点位置 calc(实现跳动的核心)

python 复制代码
def calc(self, generate_frame):
    # 1. 计算当前帧的跳动比例(基于曲线函数,控制缩放幅度)
    ratio = 15 * curve(generate_frame / 15 * pi)  # 放大15倍,增强跳动感

    # 2. 计算外围光晕参数(随跳动变化,光晕时大时小)
    halo_radius = int(4 + 6 * (1 + curve(generate_frame / 15 * pi)))  # 光晕半径
    halo_number = int(3000 + 4000 * abs(curve(generate_frame / 15 * pi) **2))  # 光晕点数量

    all_points = []  # 存储当前帧的所有点

    # 3. 生成外围光晕点
    heart_halo_point = set()  # 去重,避免重复点
    for _ in range(halo_number):
        t = random.uniform(0, 2 * pi)
        x, y = heart_function(t, shrink_ratio=11.5)  # 稍大的爱心轮廓
        x, y = shrink(x, y, halo_radius)  # 收缩成光晕
        x += random.randint(-16, 16)  # 随机偏移,让光晕更自然
        y += random.randint(-16, 16)
        size = random.choice((2, 2, 1))  # 光晕点大小
        all_points.append((x, y, size))

    # 4. 调整轮廓点位置(随跳动缩放)
    for x, y in self._points:
        x, y = self.calc_position(x, y, ratio)  # 按比例调整位置
        size = random.randint(1, 2)  # 轮廓点大小
        all_points.append((x, y, size))

    # 5. 调整边缘扩散点位置
    for x, y in self._edge_diffusion_points:
        x, y = self.calc_position(x, y, ratio)
        size = random.randint(1, 2)
        all_points.append((x, y, size))

    # 6. 调整中心填充点位置
    for x, y in self._center_diffusion_points:
        x, y = self.calc_position(x, y, ratio)
        size = random.randint(1, 2)
        all_points.append((x, y, size))
        self.all_points[generate_frame] = all_points  # 存储当前帧的点(注:此处可优化,移到循环外)

跳动原理 :通过 ratio(随帧数周期性变化的缩放比例)调整所有点的位置,实现爱心 "放大 - 缩小" 的循环,模拟心跳。- 细节 **:光晕的大小和点数量也随跳动变化,增强视觉层次感。

4. 绘制当前帧 render

python 复制代码
def render(self, render_canvas, render_frame):
    # 遍历当前帧的所有点,用矩形绘制(效率高于圆形,视觉效果接近点)
    for x, y, size in self.all_points[render_frame % self.generate_frame]:
        render_canvas.create_rectangle(
            x, y, x + size, y + size,  # 矩形左上角和右下角坐标
            width=0,  # 无边框
            fill=HEART_COLOR  # 填充爱心颜色
        )
  • 作用:将当前帧的所有点绘制到画布上,形成爱心图案。

四、动画循环 draw 函数

python 复制代码
def draw(main: Tk, render_canvas: Canvas, render_heart: Heart, render_frame=0):
    render_canvas.delete('all')  # 清空上一帧
    render_heart.render(render_canvas, render_frame)  # 绘制当前帧
    # 160毫秒后绘制下一帧,形成动画(160ms/帧 → 约6帧/秒,节奏舒缓)
    main.after(160, draw, main, render_canvas, render_heart, render_frame + 1)
  • 核心:通过 after 方法循环调用自身,不断刷新画布,实现连续动画效果。

五、主程序入口

python 复制代码
if __name__ == '__main__':
    root = Tk()  # 创建窗口
    root.title("HYQ爱心代码")  # 窗口标题(个性化定制)
    canvas = Canvas(root, bg='black', height=CANVAS_HEIGHT, width=CANVAS_WIDTH)  # 黑色画布
    canvas.pack()
    heart = Heart()  # 初始化爱心
    draw(root, canvas, heart)  # 启动动画
    root.mainloop()  # 进入事件循环
  • 功能:创建窗口、画布,初始化爱心对象并启动动画,最终显示跳动的爱心。

六、 总结

这段代码的核心逻辑是:

  1. 笛卡尔心形曲线生成爱心的基础点集(轮廓 + 填充);
  2. 正弦函数控制点的周期性缩放,实现 "心跳" 效果;
  3. 通过Tkinter 的画布循环刷新,形成连续动画。

代码特点:单一配色 + 黑色背景提升视觉质感,较慢的跳动节奏(160ms / 帧)更显温柔,适合作为个性化礼物(标题含 "HYQ",推测为定制昵称)。整体逻辑清晰,仅需优化一处冗余赋值(all_points 存储位置)即可更高效运行。

七、代码示例

python 复制代码
import random
from math import sin,cos,pi,log
from tkinter import *
CANVAS_WIDTH = 640
CANVAS_HEIGHT = 640
CANVAS_CENTER_X = CANVAS_WIDTH / 2
CANVAS_CENTER_Y = CANVAS_HEIGHT / 2
IMAGE_ENLARGE = 11
HEART_COLOR = "#Fd798f"


def heart_function(t, shrink_ratio: float = IMAGE_ENLARGE):
    x = 16 * (sin(t) ** 3)
    y = -(15 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(3*t))

    x *= shrink_ratio
    y *= shrink_ratio

    x += CANVAS_CENTER_X
    y += CANVAS_CENTER_Y
    return int(x), int(y)


def scatter_inside(x, y, beta=0.15):
    ratio_x = - beta * log(random.random())
    ratio_y = - beta * log(random.random())

    dx = ratio_x * (x - CANVAS_CENTER_X)
    dy = ratio_y * (y - CANVAS_CENTER_Y)
    return x - dx, y - dy


def shrink(x, y, ratio):
    force = -1 / (((x-CANVAS_CENTER_X) ** 2 +
                   (y - CANVAS_CENTER_Y) ** 2) ** 0.6)
    dx = ratio * force * (x - CANVAS_CENTER_X)
    dy = ratio * force * (y - CANVAS_CENTER_Y)
    return x - dx, y - dy


def curve(p):
    return 2 * (2 * sin(4 * p)) / (2 * pi)


class Heart:
    def __init__(self, generate_frame=20):
        self._points = set()
        self._edge_diffusion_points = set()
        self._center_diffusion_points = set()
        self.all_points = {}
        self.build(2000)

        self.random_halo = 1000
        self.generate_frame = generate_frame
        for frame in range(generate_frame):
            self.calc(frame)

    def build(self, number):
        for _ in range(number):
            t = random.uniform(0, 2 * pi)
            x, y = heart_function(t)
            self._points.add((x, y))

        for _x, _y in list(self._points):
            for _ in range(3):
                x, y = scatter_inside(_x, _y, 0.3)
                self._edge_diffusion_points.add((x, y))
        point_list = list(self._points)
        for _ in range(4000):
                x, y = random.choice(point_list)
                x, y = scatter_inside(x, y, 0.2)
                self._center_diffusion_points.add((x, y))

    @staticmethod
    def calc_position(x, y, ratio):
        force = 1 / (((x - CANVAS_CENTER_X) ** 2 +
                      (y - CANVAS_CENTER_Y) ** 2) ** 0.520)
        dx = ratio * force * (x - CANVAS_CENTER_X) + random.randint(-2, 2)
        dy = ratio * force * (y - CANVAS_CENTER_Y) + random.randint(-2, 2)
        return x - dx, y - dy

    def calc(self,generate_frame):
        ratio = 15 * curve(generate_frame / 15 * pi)
        halo_radius = int(4 + 6 * (1 + curve(generate_frame / 15 * pi)))
        halo_number = int(3000 + 4000 * abs(curve(generate_frame / 15 * pi) ** 2))
        all_points = []
        heart_halo_point = set()
        for _ in range(halo_number):
            t = random.uniform(0, 2 * pi)
            x, y = heart_function(t, shrink_ratio=11.5)
            x, y = shrink(x, y, halo_radius)
            if (x, y) not in heart_halo_point:
                heart_halo_point.add((x, y))
                x += random.randint(-16, 16)
                y += random.randint(-16, 16)
                size = random.choice((2, 2, 1))
                all_points.append((x, y, size))
                
        for x, y in self._points:
            x, y = self.calc_position(x, y, ratio)
            size = random.randint(1, 2)
            all_points.append((x, y, size))

        for x, y in self._edge_diffusion_points:
            x, y = self.calc_position(x, y, ratio)
            size = random.randint(1, 2)
            all_points.append((x, y, size))
        # self.all_points[generate_frame] = all_points
        for x, y in self._center_diffusion_points:
            x, y = self.calc_position(x, y, ratio)
            size = random.randint(1, 2)
            all_points.append((x, y, size))
            self.all_points[generate_frame] = all_points

    def render(self, render_canvas, render_frame):
        for x, y, size in self.all_points[render_frame % self.generate_frame]:
            render_canvas.create_rectangle(x, y, x + size, y + size, width=0, fill=HEART_COLOR)


def draw(main: Tk, render_canvas: Canvas, render_heart: Heart, render_frame=0):
    render_canvas.delete('all')
    render_heart.render(render_canvas, render_frame)
    main.after(160, draw, main, render_canvas, render_heart, render_frame + 1)


if __name__ == '__main__':
    root = Tk()
    root.title("HYQ爱心代码")
    canvas = Canvas(root, bg='black', height=CANVAS_HEIGHT, width=CANVAS_WIDTH)
    canvas.pack()
    heart = Heart()
    draw(root, canvas, heart)
    root.mainloop()
    

八、效果图

相关推荐
人间乄惊鸿客6 小时前
python-day8
开发语言·python
Mrliu__6 小时前
Python数据结构(七):Python 高级排序算法:希尔 快速 归并
数据结构·python·排序算法
C嘎嘎嵌入式开发6 小时前
(22)100天python从入门到拿捏《【网络爬虫】网络基础与HTTP协议》
网络·爬虫·python
zzzyulin7 小时前
huggingface transformers调试问题--加载本地路径模型时pdb断点消失
python·transformer
教练、我想打篮球7 小时前
12 pyflink 的一个基础使用, 以及环境相关
python·flink·pyflink
飞翔的佩奇7 小时前
【完整源码+数据集+部署教程】【天线&运输】直升机战机类型识别目标检测系统源码&数据集全套:改进yolo11-CSP-EDLAN
前端·python·yolo·计算机视觉·数据集·yolo11·直升机战机类型识别目标检测系统
C嘎嘎嵌入式开发7 小时前
(21)100天python从入门到拿捏《XML 数据解析》
xml·开发语言·python
蓝博AI7 小时前
基于卷积神经网络的香蕉成熟度识别系统,resnet50,vgg16,resnet34【pytorch框架,python代码】
人工智能·pytorch·python·神经网络·cnn
小白银子8 小时前
零基础从头教学Linux(Day 54)
linux·windows·python