Créer une animation dynamique de cœur avec le langage C

CCBeginner
Pratiquer maintenant

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Dans ce projet, vous apprendrez à créer une animation captivante d'un cœur dynamique en utilisant le langage de programmation C. Le projet utilise le système X Window pour afficher des visuels animés. En suivant les instructions étape par étape, vous configurerez le projet, générerez des données et créerez une animation captivante qui fera revivre un cœur dynamique sur votre écran.

👀 Aperçu

Dynamic Heart

🎯 Tâches

Dans ce projet, vous apprendrez :

  • Comment configurer un projet de programmation en C pour créer une animation d'un cœur dynamique
  • Comment utiliser les bibliothèques du système X Window pour créer et gérer des fenêtres graphiques
  • Comment générer des points aléatoires et les animer pour former une forme de cœur
  • Comment contrôler l'animation pour qu'elle s'étende et se rétracte, créant un effet visuel captivant

🏆 Réalisations

Après avoir terminé ce projet, vous serez en mesure de :

  • Utiliser les bibliothèques du système X Window pour la programmation graphique en C
  • Générer et manipuler des points aléatoires en C
  • Créer une animation dynamique en combinant des techniques de génération de données et de rendu

Créer les fichiers du projet

Assurez-vous d'avoir installé les bibliothèques requises. Vous aurez besoin des bibliothèques de développement X11. Vous pouvez les installer en utilisant la commande suivante :

sudo apt update
sudo apt-get install libx11-dev

Ensuite, créez un nouveau fichier nommé dynamic_heart.c et ouvrez-le dans votre éditeur de code préféré.

cd ~/project
touch dynamic_heart.c
✨ Vérifier la solution et pratiquer

Définir les variables nécessaires

Tout d'abord, nous devons écrire le code en C. La première étape consiste à inclure les fichiers d'en-tête :

#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>

Définissez la structure pour stocker les informations sur les points, y compris ses coordonnées x, y et sa couleur. Utilisée pour représenter les points dans un graphique.

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

Ensuite, définissez quelques variables globales :

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;

Utilisez les variables liées au système X Window pour créer et gérer les fenêtres graphiques et les contextes graphiques.

Display *display;
Window win;
GC gc;

display représente la connexion au serveur X, win représente la fenêtre et gc représente le contexte graphique, qui est utilisé pour dessiner les éléments graphiques.

✨ Vérifier la solution et pratiquer

Implémenter les fonctions de coordonnées d'écran

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

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

Le but des fonctions screen_x et screen_y est de mapper les coordonnées réelles sur les coordonnées d'écran pour s'assurer que les éléments graphiques de l'animation se trouvent dans la zone visible de l'écran et sont affichés à la bonne position. Plus précisément :

  1. screen_x :
  • Cette fonction prend une valeur de coordonnée x réelle en argument, puis l'ajuste pour qu'elle soit dessinée correctement sur l'écran. Elle ajoute la valeur de la coordonnée x d'entrée à la moitié de xScreen, de sorte qu'elle se trouve au centre horizontal de l'écran, car xScreen représente la largeur de l'écran, divisée par 2 pour trouver le centre horizontal de l'écran.
  • La fonction renvoie la valeur de la coordonnée x ajustée pour dessiner des points ou des formes sur l'écran.
  1. screen_y :
  • Cette fonction est similaire à screen_x, mais gère les coordonnées y. Elle prend la valeur de la coordonnée y réelle en paramètre et la convertit dans le système de coordonnées d'écran. Tout d'abord, elle inverse la coordonnée y pour que l'origine du système de coordonnées soit dans le coin supérieur gauche de l'écran, puis l'ajoute à la moitié de yScreen pour que la coordonnée se trouve au centre vertical de l'écran.
  • La fonction renvoie la valeur de la coordonnée y ajustée pour que le point ou la forme soit dessiné correctement sur l'écran.
✨ Vérifier la solution et pratiquer

Implémenter la fonction de génération de nombres aléatoires

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

Il s'agit d'une fonction utilisée pour générer des entiers aléatoires dans une plage spécifiée. Elle est utilisée pour randomiser la couleur et la position des points générés afin d'accroître la variété visuelle de l'animation.

x1 et x2 sont les deux entiers passés en arguments. La fonction renvoie un entier aléatoire compris entre x1 et x2, x1 et x2 inclus. Si x2 est supérieur à x1, un entier aléatoire est généré à l'aide de la fonction rand(), qui est restreinte à une plage valide à l'aide de l'arithmétique modulaire. La fonction rand() renvoie généralement un entier aléatoire compris entre 0 et RAND_MAX (généralement 32767). Enfin, elle ajoute le résultat de l'opération modulo à x1 pour s'assurer que l'entier aléatoire tombe dans la plage spécifiée.

✨ Vérifier la solution et pratiquer

Initialiser et générer des ensembles de points en forme de cœur :

La fonction create_data génère les données pour l'animation. Elle calcule les points d'une forme de cœur et les anime en fonction de l'algorithme défini.

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;
        }
    }
  • Utilisez une boucle pour parcourir une série de valeurs de radians (de 0.1 à 2 * PI) et calculez les coordonnées x et y de chaque point.

  • La distance entre deux points est calculée, et si la distance est supérieure à average_distance, le point est stocké dans le tableau origin_points.

✨ Vérifier la solution et pratiquer

Colorer les points pour générer une 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);
        }
    }
}
  • Il y a deux boucles imbriquées. La boucle externe augmente progressivement la valeur de size, et la boucle interne traite chaque point de quantity.

  • Avec une probabilité donnée par success_p, décidez si vous générez un point et stockez la couleur et les coordonnées du point dans le tableau points.

✨ Vérifier la solution et pratiquer

Génération et mise à jour de l'animation

// 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 est utilisé pour obtenir le nombre de points dans la trame d'animation actuelle, calculé à partir de la section de code précédente. index est le nombre de points générés précédemment.

  2. La boucle externe for (int frame = 0; frame < frames; ++frame) est utilisée pour parcourir chaque trame de l'animation, et frames spécifie le nombre total de trames.

  3. La boucle interne for (index = 0; index < points_size; ++index) est utilisée pour traiter chaque point dans la trame actuelle. Dans chaque trame, elle effectue les opérations suivantes :

  • Tout d'abord, calculez la nouvelle position de chaque point. Cela se fait en utilisant la formule suivante :
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;

Ces calculs sont utilisés pour mettre à jour les coordonnées x et y du point afin d'obtenir le mouvement du point dans l'animation. distance_increase contrôle la vitesse à laquelle le point se déplace, qui varie en fonction de la distance par rapport à la position originale du point.

  • Dessinez les points à l'aide des fonctions XSetForeground et XFillArc. Cela dessine le point sur l'écran, XSetForeground pour définir la couleur de peinture, XFillArc pour dessiner un point rempli, et les coordonnées du centre du cercle sont converties par les fonctions screen_x et screen_y.
  1. La deuxième partie de la boucle interne for (double size = 17; size < 23; size += 0.3) est utilisée pour générer des points supplémentaires dans la trame actuelle. Dans cette boucle, chaque point est généré, coloré et dessiné sur l'écran.
  • Les coordonnées et les couleurs des nouveaux points sont générées aléatoirement selon les conditions suivantes :

Si size >= 20 et que le nombre aléatoire est supérieur à 0.6, ou size < 20 et que le nombre aléatoire est supérieur à 0.95, un nouveau point est généré.

  • Les coordonnées x et y des points générés sont calculées à partir de la position du point d'origine et de certains décalages aléatoires.
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);
}
  • Enfin, utilisez les fonctions XSetForeground et XFillArc pour dessiner le point généré sur l'écran, tout comme le point précédent.
✨ Vérifier la solution et pratiquer

Créer une fenêtre X et l'initialiser

Le principal objectif de cette fonction main est de créer une fenêtre X, d'initialiser les données, puis de générer et de dessiner chaque trame de l'animation en forme de cœur dans une boucle infinie.

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 : Ouvre la connexion au serveur X.
  • blackColor : Obtient la valeur du pixel noir affiché.
  • win : Crée une fenêtre et spécifie ses propriétés, sa position, sa taille et la couleur de sa bordure.
  • XSelectInput : Spécifie le masque d'événement d'entrée pour la fenêtre.
  • XMapWindow : Affiche la fenêtre à l'écran.
  • gc : Crée un contexte graphique (Graphics Context).
✨ Vérifier la solution et pratiquer

Initialise les données et crée des groupes de points

// 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) est une boucle infinie qui attend que la mise en carte (mapping) de la fenêtre X soit terminée. C'est la phase d'initialisation avant que la fenêtre ne soit affichée à l'écran.
  • Définit une structure XEvent pour recevoir les événements X.
  • XNextEvent : Attend et obtient le prochain événement X et le stocke dans la variable e.
  • Utilise if pour vérifier si le type d'événement est MapNotify, ce qui indique que la fenêtre a été correctement mise en carte à l'écran. Si la mise en carte de la fenêtre est terminée (c'est-à-dire que le type d'événement est MapNotify).
  1. XFlush : Vide le tampon de sortie du serveur X pour s'assurer que les commandes de dessin précédentes prennent effet et ne sont pas retardées jusqu'au dessin d'une animation ultérieure.

  2. srand : Initialise le générateur de nombres aléatoires. Utilise l'heure actuelle comme graine pour un générateur de nombres aléatoires afin de générer des effets aléatoires dans une animation.

  3. origin_points : Alloue de la mémoire et crée un tableau de quantity structures Point qui seront utilisées pour stocker les coordonnées des points d'origine.

  4. points : Alloue à nouveau de la mémoire et crée un tableau plus grand pour stocker les points de l'animation. circles contrôle le nombre de points dans l'animation et est une valeur constante.

  5. Enfin, la fonction create_data() est appelée pour initialiser les données, générer et définir les coordonnées des points d'origine, et initialiser la couleur et les coordonnées initiales des points animés.

✨ Vérifier la solution et pratiquer

Boucle principale de l'animation

Le but de cette boucle principale est de contrôler l'état du motif en forme de cœur en changeant la valeur de frame pour permettre à l'effet d'animation de s'étendre et de se rétracter. La fonction usleep est utilisée pour contrôler le taux de trames afin que l'animation soit jouée à une certaine vitesse.

// 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;
}

Tout d'abord, définissez deux variables booléennes, extend et shrink, et initialisez-les respectivement à true et false, représentant les états étendu et rétracté du motif en forme de cœur.

Ensuite, une boucle infinie est lancée, et la variable frame dans la boucle est utilisée pour suivre le compte des trames d'animation actuelles.

  • usleep est utilisée pour contrôler la vitesse de l'animation.

  • Effacez tout sur la fenêtre en appelant la fonction X11 XClearWindow afin qu'elle puisse être redessinée dans la prochaine trame.

  • Augmentez ou diminuez frames en fonction des valeurs de extend et shrink pour permettre à l'animation de s'étendre et de se rétracter. L'opérateur conditionnel ? : est utilisé pour définir une valeur booléenne.

Si extend est true, vérifiez que frame est égal à 19. Si c'est le cas, l'animation est sur le point de passer de l'état étendu à l'état rétracté, définissez shrink sur true et extend sur false. Sinon, incrémentez frame.

Si extend est false, vérifiez que frame est égal à 0. Si c'est le cas, l'animation est sur le point de passer de l'état rétracté à l'état étendu, définissez shrink sur false et extend sur true. Sinon, décrémentez frame.

Enfin, libérez les ressources et quittez.

// Inside the main() function
XCloseDisplay(display);
free(origin_points);
free(points);
return 0;
✨ Vérifier la solution et pratiquer

Compiler et exécuter le projet

  1. Ouvrez votre terminal et accédez au répertoire du projet.
cd ~/project
  1. Compilez le code en utilisant la commande suivante :
gcc -o dynamic_heart dynamic_heart.c -lX11 -lm
  1. Exécutez l'application :
./dynamic_heart
Dynamic Heart
✨ Vérifier la solution et pratiquer

Résumé

Dans ce projet, vous avez appris à créer une animation captivante d'un cœur dynamique en utilisant le langage de programmation C. Vous avez configuré le projet, défini des variables, implémenté des fonctions de coordonnées d'écran, des fonctions de générateur de nombres aléatoires et des fonctions de génération de données. Le programme utilise le système de fenêtrage X (X Window System) pour afficher le cœur animé. Enfin, vous avez créé une boucle d'animation, fermé l'affichage et libéré la mémoire allouée. Vous pouvez maintenant profiter de votre propre animation de cœur dynamique créée en C.