Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing an Animated Beating Heart Graphic with Python

Tech 1

To create an animtaed, pulsating heart graphic, we will use Python's tkinter for the graphical interface and mathematical functions to generate and animate the shape.

Required Imports

import random
from math import sin, cos, pi, log
from tkinter import *

Canvas Configuration

Define the dimensions of the drawing window.

WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
CENTER_X = WINDOW_WIDTH / 2
CENTER_Y = WINDOW_HEIGHT / 2
SCALE_FACTOR = 11
COLOR_HEART = "#e3242b"

Core Heart Coordinate Function

This parametric function defines the classic heart shape.

def generate_heart_coords(parameter, scale: float = SCALE_FACTOR):
    """
    Calculates x, y coordinates for a heart shape based on a parameter t.
    :param scale: Scaling factor.
    :param parameter: The parameter t.
    :return: Integer x, y coordinates.
    """
    # Parametric equations for a heart
    x_coord = 16 * (sin(parameter) ** 3)
    y_coord = -(13 * cos(parameter) - 5 * cos(2 * parameter) - 2 * cos(3 * parameter) - cos(4 * parameter))

    # Apply scaling and center on canvas
    x_coord *= scale
    y_coord *= scale
    x_coord += CENTER_X
    y_coord += CENTER_Y

    return int(x_coord), int(y_coord)

Coordinate Transformation Functions

Add randomness and movement effects to the points.

def diffuse_point(x, y, intensity=0.15):
    """
    Creates a random inward diffusion effect from a given point.
    :param x: Original x-coordinate.
    :param y: Original y-coordinate.
    :param intensity: Diffusion strength.
    :return: New x, y coordinates.
    """
    factor_x = - intensity * log(random.random())
    factor_y = - intensity * log(random.random())
    offset_x = factor_x * (x - CENTER_X)
    offset_y = factor_y * (y - CENTER_Y)
    return x - offset_x, y - offset_y

def apply_pulsation(x, y, pulsation_ratio):
    """
    Applies a pulsating motion effect to a point.
    :param x: Original x-coordinate.
    :param y: Original y-coordinate.
    :param pulsation_ratio: Current pulsation strength.
    :return: New x, y coordinates.
    """
    distance = ((x - CENTER_X) ** 2 + (y - CENTER_Y) ** 2) ** 0.6
    force = -1 / distance if distance != 0 else 0
    dx = pulsation_ratio * force * (x - CENTER_X)
    dy = pulsation_ratio * force * (y - CENTER_Y)
    return x - dx, y - dy

Animation Timing Function

Defines the beating rhythm.

def pulsation_curve(p):
    """
    A custom periodic function to control the heart's beat rhythm.
    :param p: Input parameter.
    :return: Scaled sine value.
    """
    return 2 * (2 * sin(4 * p)) / (2 * pi)

Heart Animation Class

This class manages the state and renedring of the animated heart.

class BeatingHeart:
    def __init__(self, total_frames=20):
        self.contour = set()
        self.edge_points = set()
        self.inner_points = set()
        self.frame_data = {}
        self.initialize_points(2000)
        self.total_frames = total_frames
        for frame_index in range(total_frames):
            self.compute_frame(frame_index)

    def initialize_points(self, num_points):
        # Generate base heart contour
        for _ in range(num_points):
            t = random.uniform(0, 2 * pi)
            x, y = generate_heart_coords(t)
            self.contour.add((x, y))
        # Create diffuse edge points
        for point_x, point_y in list(self.contour):
            for _ in range(3):
                x, y = diffuse_point(point_x, point_y, 0.05)
                self.edge_points.add((x, y))
        # Create inner fill points
        contour_list = list(self.contour)
        for _ in range(4000):
            x, y = random.choice(contour_list)
            x, y = diffuse_point(x, y, 0.17)
            self.inner_points.add((x, y))

    @staticmethod
    def adjust_position(x, y, adjustment_ratio):
        dist_power = ((x - CENTER_X) ** 2 + (y - CENTER_Y) ** 2) ** 0.520
        force = 1 / dist_power if dist_power != 0 else 0
        dx = adjustment_ratio * force * (x - CENTER_X) + random.randint(-1, 1)
        dy = adjustment_ratio * force * (y - CENTER_Y) + random.randint(-1, 1)
        return x - dx, y - dy

    def compute_frame(self, frame_idx):
        current_ratio = 10 * pulsation_curve(frame_idx / 10 * pi)
        glow_radius = int(4 + 6 * (1 + pulsation_curve(frame_idx / 10 * pi)))
        glow_count = int(3000 + 4000 * abs(pulsation_curve(frame_idx / 10 * pi) ** 2))
        points_for_frame = []
        # Generate glowing halo effect
        glow_set = set()
        for _ in range(glow_count):
            t = random.uniform(0, 2 * pi)
            x, y = generate_heart_coords(t, shrink_ratio=11.6)
            x, y = apply_pulsation(x, y, glow_radius)
            if (x, y) not in glow_set:
                glow_set.add((x, y))
                x += random.randint(-14, 14)
                y += random.randint(-14, 14)
                point_size = random.choice((1, 2, 2))
                points_for_frame.append((x, y, point_size))
        # Process contour points
        for x, y in self.contour:
            x, y = self.adjust_position(x, y, current_ratio)
            size = random.randint(1, 3)
            points_for_frame.append((x, y, size))
        # Process inner points
        for x, y in self.edge_points:
            x, y = self.adjust_position(x, y, current_ratio)
            size = random.randint(1, 2)
            points_for_frame.append((x, y, size))
        for x, y in self.inner_points:
            x, y = self.adjust_position(x, y, current_ratio)
            size = random.randint(1, 2)
            points_for_frame.append((x, y, size))
        self.frame_data[frame_idx] = points_for_frame

    def draw(self, canvas_target, current_frame):
        frame_key = current_frame % self.total_frames
        for x, y, sz in self.frame_data[frame_key]:
            canvas_target.create_rectangle(x, y, x + sz, y + sz, width=0, fill=COLOR_HEART)

Main Rendering Loop

Sets up the Tkinter window and starts the animation.

def render_loop(window: Tk, canvas: Canvas, heart_obj: BeatingHeart, frame_num=0):
    canvas.delete('all')
    heart_obj.draw(canvas, frame_num)
    window.after(160, render_loop, window, canvas, heart_obj, frame_num + 1)

if __name__ == '__main__':
    main_window = Tk()
    main_canvas = Canvas(main_window, bg='black', height=WINDOW_HEIGHT, width=WINDOW_WIDTH)
    main_canvas.pack()
    heart_animation = BeatingHeart()
    render_loop(main_window, main_canvas, heart_animation)
    main_window.mainloop()

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.