Creating a Dynamic Heart Animation with C

CCBeginner
Practice Now

Introduction

In this project, you will learn how to create a mesmerizing dynamic heart animation using the C programming language. The project utilizes the X Window System to render animated visuals. By following the step-by-step instructions, you will set up the project, generate data, and create a captivating animation that brings a dynamic heart to life on your screen.

👀 Preview

Dynamic Heart

ðŸŽŊ Tasks

In this project, you will learn:

  • How to set up a C programming project to create a dynamic heart animation
  • How to use X Window System libraries to create and manage graphical windows
  • How to generate random points and animate them to form a heart shape
  • How to control the animation to expand and contract, creating a captivating visual effect

🏆 Achievements

After completing this project, you will be able to:

  • Use X Window System libraries for graphical programming in C
  • Generate and manipulate random points in C
  • Create a dynamic animation using a combination of data generation and rendering techniques

Create the Project Files

Ensure you have the required libraries installed. You will need the X11 development libraries. You can install them using the following command:

sudo apt-get install libx11-dev

Then, create a new file named dynamic_heart.c and open it in your preferred code editor.

cd ~/project
touch dynamic_heart.c
âœĻ Check Solution and Practice

Define Necessary Variables

First, we need to write the C code. The first step is to include the header files:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <stdbool.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <unistd.h>

Define the structure to store point information, including its x coordinate, y coordinate, and color. Used to represent points in a graph.

struct Point {
    double x, y;
    unsigned long color;
};

Then, define some global variables:

unsigned long colors[7] = {0xff1f53, 0xfcdefa, 0xff0000, 0xff0000, 0xff0202, 0xff0008, 0xff0505};
const int xScreen = 1200;
const int yScreen = 800;
const double PI = 3.1426535159;
const double e = 2.71828;
const double average_distance = 0.162;
const int quantity = 506;
const int circles = 210;
const int frames = 20;
struct Point* origin_points;
struct Point* points;

Use X Window System related variables for creating and managing graphical Windows and graphical contexts.

Display *display;
Window win;
GC gc;

display represents the X server connection, win represents the window, and gc represents the graphics context, which is used to draw the graphics elements.

âœĻ Check Solution and Practice

Implement Screen Coordinate Functions

double screen_x(double x) {
    x += xScreen / 2;
    return x;
}

double screen_y(double y) {
    y = -y + yScreen / 2;
    return y;
}

The purpose of the screen_x and screen_y functions is to map the actual coordinates to the screen coordinates to ensure that the graphic elements in the animation are within the visible area of the screen and are displayed in the correct position. To be specific:

  1. screen_x:
  • This function takes an actual x coordinate value as an argument and then adjusts it so that it is drawn correctly on the screen. It adds the input x coordinate value to half of xScreen, so that it will be in the horizontal center of the screen, since xScreen represents the width of the screen, divided by 2 to find the horizontal center of the screen.
  • The function returns the adjusted x coordinate value for drawing points or shapes on the screen.
  1. screen_y:
  • This function is similar to screen_x, but deals with the y coordinates. It takes the actual y coordinate value as a parameter and converts it to the screen coordinate system. First, it inverts the y coordinate so that the origin of the coordinate system is in the upper left corner of the screen, then adds it to half of yScreen so that the coordinate is in the vertical center of the screen.
  • The function returns the adjusted y coordinate value so that a point or shape is drawn correctly on the screen.
âœĻ Check Solution and Practice

Implement Random Number Generator Function

int create_random(int x1, int x2) {
    if (x2 > x1) {
        return rand() % (x2 - x1 + 1) + x1;
    }
    return 0;
}

This is a function used to generate random integers within a specified range, and it is used to randomize the color and position of the generated points to increase the visual variety of the animation.

Where x1 and x2 are the two integers passed as arguments. The function returns a random integer between x1 and x2, including x1 and x2. If x2 is greater than x1, a random integer is generated using the rand() function, which is restricted to a valid range using modular arithmetic. The rand() function usually returns a random integer between 0 and RAND_MAX (usually 32767). Finally, it adds the result of the modular operation to x1 to ensure that the random integer falls within the specified range.

âœĻ Check Solution and Practice

Initialize and Generate Heart-Shaped Point Sets:

The create_data function generates data for the animation. It calculates points for a heart shape and animates them based on the defined algorithm.

void create_data() {
    int index = 0;
    double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
    for (double radian = 0.1; radian <= 2 * PI; radian += 0.005) {
        // Calculate the x and y coordinates of the heart
        x2 = 16 * pow(sin(radian), 3);
        y2 = 13 * cos(radian) - 5 * cos(2 * radian) - 2 * cos(3 * radian) - cos(4 * radian);
        double distance = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
        if (distance > average_distance) {
            // Qualifying heart points are stored
            x1 = x2;
            y1 = y2;
            origin_points[index].x = x2;
            origin_points[index++].y = y2;
        }
    }
  • Use a loop to iterate through a series of radian values (from 0.1 to 2 * PI) and compute the x and y coordinates of each point.

  • The distance between two points is calculated, and if the distance is greater than average_distance, the point is stored in the origin_points array.

âœĻ Check Solution and Practice

Color Points to Generate Animation

// Inside the create_data() function
index = 0;
for (double size = 0.1, lightness = 1.5; size <= 20; size += 0.1) {
    double success_p = 1 / (1 + pow(e, 8 - size / 2));
    if (lightness > 1) {
        lightness -= 0.0025;
    }

    for (int i = 0; i < quantity; ++i) {
        if (success_p > (double)create_random(0, 100) / 100.0) {
            // Randomly generate the color of the points, coordinates, and store them in the Points array
            points[index].color = colors[create_random(0, 6)];
            points[index].x = size * origin_points[i].x + create_random(-4, 4);
            points[index++].y = size * origin_points[i].y + create_random(-4, 4);
        }
    }
}
  • There are two nested loops. The external loop gradually increases the value of size, and the internal loop processes each quantity point.

  • With success_p's probability, decide whether to generate a point and store the color and coordinates of the point in the points array.

âœĻ Check Solution and Practice

Animation Generation and Update

// Inside the create_data() function
int points_size = index;

for (int frame = 0; frame < frames; ++frame) {
    for (index = 0; index < points_size; ++index) {
        // The position of the calculated point is increased and the coordinates are updated
        double x = points[index].x, y = points[index].y;
        double distance = sqrt(pow(x, 2) + pow(y, 2));
        double distance_increase = -0.0009 * distance * distance + 0.35714 * distance + 5;
        double x_increase = distance_increase * x / distance / frames;
        double y_increase = distance_increase * y / distance / frames;
        points[index].x += x_increase;
        points[index].y += y_increase;
        // Draw points using XSetForeground and XFillArc
        XSetForeground(display, gc, points[index].color);
        XFillArc(display, win, gc, screen_x(points[index].x), screen_y(points[index].y), 2, 2, 0, 360 * 64);
    }

    for (double size = 17; size < 23; size += 0.3) {
        for (index = 0; index < quantity; ++index) {
            // Randomly generate the coordinates and color of the point according to the condition, and draw the point using XSetForeground and XFillArc
            if ((create_random(0, 100) / 100.0 > 0.6 && size >= 20) || (size < 20 && (double)create_random(0, 100) / 100.0 > 0.95)) {
                double x, y;
                if (size >= 20) {
                    x = origin_points[index].x * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
                    y = origin_points[index].y * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
                } else {
                    x = origin_points[index].x * size + create_random(-5, 5);
                    y = origin_points[index].y * size + create_random(-5, 5);
                }
                XSetForeground(display, gc, colors[create_random(0, 6)]);
                XFillArc(display, win, gc, screen_x(x), screen_y(y), 2, 2, 0, 360 * 64);
            }
        }
    }
}
  1. points_size is used to get the number of points in the current animation frame, calculated from the previous code section. index is the number of points previously generated.

  2. External loop for (int frame = 0; frame < frames; ++frame) is used to iterate over each frame of the animation, and the frames specify how many frames there are in total.

  3. Internal loop for (index = 0; index < points_size; ++index) is used to process each point in the current frame. In each frame, it does the following:

  • First, calculate the new position of each point. This is done by the following formula:
double x = points[index].x, y = points[index].y;
double distance = sqrt(pow(x, 2) + pow(y, 2));
double distance_increase = -0.0009 * distance * distance + 0.35714 * distance + 5;
double x_increase = distance_increase * x / distance / frames;
double y_increase = distance_increase * y / distance / frames;
points[index].x += x_increase;
points[index].y += y_increase;

These calculations are used to update the x and y coordinates of the point to achieve the motion of the point in the animation. distance_increase controls the speed at which the point moves, which varies with the distance from the point's original position.

  • Draw points using the XSetForeground and XFillArc functions. This draws the point onto the screen, the XSetForeground for setting the paint color, the XFillArc for drawing a filled dot, and the coordinates of the center of the circle are converted by the screen_x and screen_y functions.
  1. The second part of the inner loop for (double size = 17; size < 23; size += 0.3) is used to generate additional points in the current frame. In this loop, each point is generated, colored, and drawn onto the screen.
  • The coordinates and colors of the new points are generated randomly according to the following conditions:

If size >= 20 and the random number is greater than 0.6, or size < 20 and the random number is greater than 0.95, a new point is generated.

  • The x and y coordinates of the generated points are calculated from the position of the original point and some random offsets.
double x, y;
if (size >= 20) {
    x = origin_points[index].x * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
    y = origin_points[index].y * size + create_random(-frame * frame / 5 - 15, frame * frame / 5 + 15);
} else {
    x = origin_points[index].x * size + create_random(-5, 5);
    y = origin_points[index].y * size + create_random(-5, 5);
}
  • Finally, use the XSetForeground and XFillArc functions to draw the generated point onto the screen, just like the previous point.
âœĻ Check Solution and Practice

Create X Window and Initialize

The main goal of this main function is to create an X window, initialize the data, and then generate and draw every frame of the heart-shaped animation in an infinite loop.

int main() {
    display = XOpenDisplay(NULL);

    int blackColor = BlackPixel(display, DefaultScreen(display));
    win = XCreateSimpleWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, xScreen, yScreen, 0, blackColor, blackColor);
    XSelectInput(display, win, StructureNotifyMask);
    XMapWindow(display, win);
    gc = XCreateGC(display, win, 0, NULL);
}
  • display: Open the connection to the X server.
  • blackColor: Gets the displayed black pixel value.
  • win: Create a window and specify its properties, location, size, and border color.
  • XSelectInput: Specifies the input event mask for the window.
  • XMapWindow: Map the window to the screen.
  • gc: Create a Graphics Context.
âœĻ Check Solution and Practice

Initializes Data and Creates Point Groups

// Inside the main() function
while (1) {
    XEvent e;
    XNextEvent(display, &e);
    if (e.type == MapNotify)
        break;
}

XFlush(display);
srand(time(NULL));
origin_points = (struct Point*)malloc(quantity * sizeof(struct Point));
points = (struct Point*)malloc(circles * quantity * sizeof(struct Point));

create_data();
  1. while (1) is an infinite loop that waits for the X window Map to complete, which is the initialization phase before the window is displayed on the screen.
  • Defines an XEvent struct to receive X events.
  • XNextEvent: Wait and get the next X event and store it in the e variable.
  • Use if to check whether the type of the event is MapNotify, indicating that the window has been successfully mapped to the screen. If the window mapping is complete (that is, the event type is MapNotify).
  1. XFlush: Empty the output buffer of the X server to ensure that previous drawing commands take effect and are not delayed to a subsequent animation drawing.

  2. srand: Initialize the random number generator. Use the current time as a seed for a random number generator to generate random effects in an animation.

  3. origin_points: Allocate memory and create an array of quantity Point structs that will be used to store the original point coordinates.

  4. points: Again, allocate memory and create a larger array to store the points in the animation. circles controls the number of points in the animation and is a constant value.

  5. Finally, the create_data() Function is called to initialize the data, generate and set the coordinates of the original points, and initialize the color and initial coordinates of the animated points.

âœĻ Check Solution and Practice

Animation Main Loop

The purpose of this main loop is to control the state of the heart pattern by changing the value of the frame to allow the animation effect to expand and contract. The usleep function is used to control the frame rate so that the animation plays at a certain speed.

// Inside the main() function
bool extend = true, shrink = false;
for (int frame = 0;;) {
    usleep(20000);
    XClearWindow(display, win);
    if (extend)
        frame == 19 ? (shrink = true, extend = false) : ++frame;
    else
        frame == 0 ? (shrink = false, extend = true) : --frame;
}

First, define two Boolean variables, extend and shrink, and initialize them to true and false, representing the extended and contracted states of the heart pattern, respectively.

Then, an infinite loop is started, and the frame variable in the loop is used to track the count of the current animation frames.

  • usleep is used to control the animation speed.

  • Clear everything on the window by calling the X11 function XClearWindow so that it can be redrawn in the next frame.

  • Add or shrink frames based on the extend and shrink values to allow the animation to expand and shrink. The conditional operator ? : to set a Boolean value.

If extend is true, check that the frame is equal to 19. If so, the animation is about to switch from the extended state to the contracted state, set shrink to true, and extend to false. Otherwise, increment the frame.

If extend is false, check that frame is equal to 0. If so, the animation is about to switch from the shrink state to the extended state, set shrink to false and extend to true. Otherwise, the frame is decremented.

Finally, release the resource and exit.

// Inside the main() function
XCloseDisplay(display);
free(origin_points);
free(points);
return 0;
âœĻ Check Solution and Practice

Compile and Run the Project

  1. Open your terminal and navigate to the project directory.
cd ~/project
  1. Compile the code using the following command:
gcc -o dynamic_heart dynamic_heart.c -lX11 -lm
  1. Run the application:
./dynamic_heart
Dynamic Heart
âœĻ Check Solution and Practice

Summary

In this project, you have learned how to create a captivating dynamic heart animation using the C programming language. You have set up the project, defined variables, implemented screen coordinate functions, random number generator functions, and data generation functions. The program utilizes the X Window System to render the animated heart. Finally, you have created an animation loop and closed the display, freeing allocated memory. You can now enjoy your very own dynamic heart animation created in C.

Other C Tutorials you may like