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

🎯 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 update
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
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.
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:
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, sincexScreenrepresents the width of the screen, divided by2to find the horizontal center of the screen. - The function returns the adjusted x coordinate value for drawing points or shapes on the screen.
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 ofyScreenso 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.
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.
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.1to2 * 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 theorigin_pointsarray.
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 eachquantitypoint.With
success_p's probability, decide whether to generate a point and store the color and coordinates of the point in thepointsarray.
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);
}
}
}
}
points_sizeis used to get the number of points in the current animation frame, calculated from the previous code section.indexis the number of points previously generated.External loop
for (int frame = 0; frame < frames; ++frame)is used to iterate over each frame of the animation, and theframesspecify how many frames there are in total.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
XSetForegroundandXFillArcfunctions. This draws the point onto the screen, theXSetForegroundfor setting the paint color, theXFillArcfor drawing a filled dot, and the coordinates of the center of the circle are converted by thescreen_xandscreen_yfunctions.
- 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 >= 20and the random number is greater than0.6, orsize < 20and the random number is greater than0.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
XSetForegroundandXFillArcfunctions to draw the generated point onto the screen, just like the previous point.
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.
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();
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
XEventstruct to receive X events. XNextEvent: Wait and get the next X event and store it in theevariable.- Use
ifto check whether the type of the event isMapNotify, indicating that the window has been successfully mapped to the screen. If the window mapping is complete (that is, the event type isMapNotify).
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.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.origin_points: Allocate memory and create an array ofquantityPointstructs that will be used to store the original point coordinates.points: Again, allocate memory and create a larger array to store the points in the animation.circlescontrols the number of points in the animation and is a constant value.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.
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.
usleepis used to control the animation speed.Clear everything on the window by calling the X11 function
XClearWindowso that it can be redrawn in the next frame.Add or shrink
framesbased on theextendandshrinkvalues to allow the animation to expand and shrink. The conditional operator? :to set a Boolean value.
If
extendistrue, check that theframeis equal to19. If so, the animation is about to switch from the extended state to the contracted state, setshrinktotrue, andextendtofalse. Otherwise, increment theframe.
If
extendisfalse, check thatframeis equal to0. If so, the animation is about to switch from the shrink state to the extended state, setshrinktofalseandextendtotrue. Otherwise, theframeis decremented.
Finally, release the resource and exit.
// Inside the main() function
XCloseDisplay(display);
free(origin_points);
free(points);
return 0;
Compile and Run the Project
- Open your terminal and navigate to the project directory.
cd ~/project
- Compile the code using the following command:
gcc -o dynamic_heart dynamic_heart.c -lX11 -lm
- Run the application:
./dynamic_heart

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.



