Erstellen einer dynamischen Herzanimation mit C

CCBeginner
Jetzt üben

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

In diesem Projekt lernen Sie, wie Sie mit der Programmiersprache C eine faszinierende dynamische Herzanimation erstellen können. Das Projekt nutzt das X Window System, um animierte Visualisierungen zu rendern. Indem Sie den schrittweisen Anweisungen folgen, richten Sie das Projekt ein, generieren Daten und erstellen eine fesselnde Animation, die ein dynamisches Herz auf Ihrem Bildschirm zum Leben erweckt.

👀 Vorschau

Dynamisches Herz

🎯 Aufgaben

In diesem Projekt lernen Sie:

  • Wie Sie ein C-Programmierprojekt einrichten, um eine dynamische Herzanimation zu erstellen
  • Wie Sie die Bibliotheken des X Window Systems verwenden, um grafische Fenster zu erstellen und zu verwalten
  • Wie Sie zufällige Punkte generieren und animieren, um eine Herzform zu bilden
  • Wie Sie die Animation steuern, um sie erweitern und zusammenziehen zu lassen, wodurch ein fesselnder visueller Effekt entsteht

🏆 Errungenschaften

Nach Abschluss dieses Projekts können Sie:

  • Die Bibliotheken des X Window Systems für die grafische Programmierung in C nutzen
  • Zufällige Punkte in C generieren und manipulieren
  • Eine dynamische Animation mithilfe einer Kombination aus Datengenerierung und Rendering-Techniken erstellen

Erstellen der Projekt-Dateien

Stellen Sie sicher, dass Sie die erforderlichen Bibliotheken installiert haben. Sie benötigen die X11-Entwicklungsbibliotheken. Sie können sie mit dem folgenden Befehl installieren:

sudo apt update
sudo apt-get install libx11-dev

Erstellen Sie dann eine neue Datei mit dem Namen dynamic_heart.c und öffnen Sie sie in Ihrem bevorzugten Code-Editor.

cd ~/project
touch dynamic_heart.c
✨ Lösung prüfen und üben

Definieren der erforderlichen Variablen

Zunächst müssen wir den C-Code schreiben. Der erste Schritt besteht darin, die Header-Dateien einzubinden:

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

Definieren Sie die Struktur, um Punktinformationen zu speichern, einschließlich seiner x-Koordinate, y-Koordinate und Farbe. Wird verwendet, um Punkte in einem Graphen darzustellen.

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

Definieren Sie dann einige globale Variablen:

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;

Verwenden Sie Variablen, die mit dem X Window System zusammenhängen, um grafische Fenster und grafische Kontexte zu erstellen und zu verwalten.

Display *display;
Window win;
GC gc;

display repräsentiert die Verbindung zum X-Server, win repräsentiert das Fenster und gc repräsentiert den Grafikkontext, der zum Zeichnen der grafischen Elemente verwendet wird.

✨ Lösung prüfen und üben

Implementierung von Bildschirmkoordinaten-Funktionen

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

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

Ziel der Funktionen screen_x und screen_y ist es, die tatsächlichen Koordinaten auf die Bildschirmkoordinaten abzubilden, um sicherzustellen, dass die grafischen Elemente in der Animation sich im sichtbaren Bereich des Bildschirms befinden und an der richtigen Position angezeigt werden. Genauer gesagt:

  1. screen_x:
  • Diese Funktion nimmt einen tatsächlichen x-Koordinatenwert als Argument und passt ihn dann so an, dass er korrekt auf dem Bildschirm gezeichnet wird. Sie addiert den eingegebenen x-Koordinatenwert zu der Hälfte von xScreen, damit er in der horizontalen Mitte des Bildschirms liegt, da xScreen die Breite des Bildschirms darstellt und durch 2 geteilt wird, um die horizontale Mitte des Bildschirms zu finden.
  • Die Funktion gibt den angepassten x-Koordinatenwert zurück, um Punkte oder Formen auf dem Bildschirm zu zeichnen.
  1. screen_y:
  • Diese Funktion ähnelt screen_x, behandelt jedoch die y-Koordinaten. Sie nimmt den tatsächlichen y-Koordinatenwert als Parameter und wandelt ihn in das Bildschirm-Koordinatensystem um. Zunächst invertiert sie die y-Koordinate, damit der Ursprung des Koordinatensystems in der oberen linken Ecke des Bildschirms liegt, und addiert sie dann zu der Hälfte von yScreen, damit die Koordinate in der vertikalen Mitte des Bildschirms liegt.
  • Die Funktion gibt den angepassten y-Koordinatenwert zurück, damit ein Punkt oder eine Form korrekt auf dem Bildschirm gezeichnet wird.
✨ Lösung prüfen und üben

Implementierung der Zufallszahlengenerator-Funktion

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

Dies ist eine Funktion, die verwendet wird, um zufällige Ganzzahlen innerhalb eines angegebenen Bereichs zu generieren. Sie wird eingesetzt, um die Farbe und Position der generierten Punkte zu randomisieren und so die visuelle Vielfalt der Animation zu erhöhen.

Dabei sind x1 und x2 die beiden als Argumente übergebenen Ganzzahlen. Die Funktion gibt eine zufällige Ganzzahl zwischen x1 und x2 zurück, einschließlich x1 und x2. Wenn x2 größer als x1 ist, wird mit der rand()-Funktion eine zufällige Ganzzahl generiert, die mithilfe von Modulo-Arithmetik auf einen gültigen Bereich eingeschränkt wird. Die rand()-Funktion gibt normalerweise eine zufällige Ganzzahl zwischen 0 und RAND_MAX (üblicherweise 32767) zurück. Schließlich wird das Ergebnis der Modulo-Operation zu x1 addiert, um sicherzustellen, dass die zufällige Ganzzahl innerhalb des angegebenen Bereichs liegt.

✨ Lösung prüfen und üben

Initialisierung und Generierung von Herzförmigen Punktmengen:

Die Funktion create_data generiert die Daten für die Animation. Sie berechnet die Punkte für eine Herzform und animiert sie basierend auf dem definierten Algorithmus.

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;
        }
    }
  • Verwenden Sie eine Schleife, um eine Reihe von Winkelwerten (von 0.1 bis 2 * PI) zu durchlaufen und die x- und y-Koordinaten jedes Punktes zu berechnen.

  • Die Entfernung zwischen zwei Punkten wird berechnet, und wenn die Entfernung größer als average_distance ist, wird der Punkt im origin_points-Array gespeichert.

✨ Lösung prüfen und üben

Punktfarben zur Generierung einer 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);
        }
    }
}
  • Es gibt zwei verschachtelte Schleifen. Die äußere Schleife erhöht allmählich den Wert von size, und die innere Schleife verarbeitet jeden der quantity Punkte.

  • Mit der Wahrscheinlichkeit von success_p wird entschieden, ob ein Punkt generiert wird, und die Farbe und Koordinaten des Punktes werden im points-Array gespeichert.

✨ Lösung prüfen und üben

Animationsgenerierung und -aktualisierung

// 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 wird verwendet, um die Anzahl der Punkte im aktuellen Animationsframe zu erhalten, die aus dem vorherigen Codeabschnitt berechnet wird. index ist die Anzahl der zuvor generierten Punkte.

  2. Die äußere Schleife for (int frame = 0; frame < frames; ++frame) wird verwendet, um jeden Frame der Animation zu durchlaufen. frames gibt an, wie viele Frames es insgesamt gibt.

  3. Die innere Schleife for (index = 0; index < points_size; ++index) wird verwendet, um jeden Punkt im aktuellen Frame zu verarbeiten. In jedem Frame wird Folgendes durchgeführt:

  • Zunächst wird die neue Position jedes Punktes berechnet. Dies geschieht mit der folgenden Formel:
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;

Diese Berechnungen werden verwendet, um die x- und y-Koordinaten des Punktes zu aktualisieren, um die Bewegung des Punktes in der Animation zu erreichen. distance_increase steuert die Geschwindigkeit, mit der der Punkt sich bewegt, die sich je nach Entfernung von der ursprünglichen Position des Punktes ändert.

  • Punkte werden mit den Funktionen XSetForeground und XFillArc gezeichnet. Dadurch wird der Punkt auf den Bildschirm gezeichnet. XSetForeground wird verwendet, um die Zeichenfarbe festzulegen, XFillArc um einen gefüllten Punkt zu zeichnen, und die Koordinaten des Kreismittelpunkts werden von den Funktionen screen_x und screen_y umgewandelt.
  1. Der zweite Teil der inneren Schleife for (double size = 17; size < 23; size += 0.3) wird verwendet, um zusätzliche Punkte im aktuellen Frame zu generieren. In dieser Schleife wird jeder Punkt generiert, eingefärbt und auf den Bildschirm gezeichnet.
  • Die Koordinaten und Farben der neuen Punkte werden zufällig gemäß den folgenden Bedingungen generiert:

Wenn size >= 20 und die Zufallszahl größer als 0.6 ist, oder size < 20 und die Zufallszahl größer als 0.95 ist, wird ein neuer Punkt generiert.

  • Die x- und y-Koordinaten der generierten Punkte werden aus der Position des ursprünglichen Punktes und einigen zufälligen Abweichungen berechnet.
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);
}
  • Schließlich werden die Funktionen XSetForeground und XFillArc verwendet, um den generierten Punkt auf den Bildschirm zu zeichnen, genau wie der vorherige Punkt.
✨ Lösung prüfen und üben

Erstellen und Initialisieren eines X-Fensters

Das Hauptziel dieser main-Funktion besteht darin, ein X-Fenster zu erstellen, die Daten zu initialisieren und dann in einer endlosen Schleife jeden Frame der herzförmigen Animation zu generieren und zu zeichnen.

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: Öffnet die Verbindung zum X-Server.
  • blackColor: Ruft den angezeigten schwarzen Pixelwert ab.
  • win: Erstellt ein Fenster und gibt seine Eigenschaften, Position, Größe und Rahmenfarbe an.
  • XSelectInput: Gibt die Eingabeereignismaske für das Fenster an.
  • XMapWindow: Ordnet das Fenster dem Bildschirm zu.
  • gc: Erstellt einen Grafikkontext (Graphics Context).
✨ Lösung prüfen und üben

Initialisierung der Daten und Erstellung von Punktgruppen

// 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) ist eine endlose Schleife, die darauf wartet, dass die Zuordnung (Mapping) des X-Fensters abgeschlossen ist. Dies ist die Initialisierungsphase, bevor das Fenster auf dem Bildschirm angezeigt wird.
  • Definiert eine XEvent-Struktur, um X-Ereignisse zu empfangen.
  • XNextEvent: Wartet und erhält das nächste X-Ereignis und speichert es in der Variablen e.
  • Nutzt if, um zu prüfen, ob der Typ des Ereignisses MapNotify ist, was bedeutet, dass das Fenster erfolgreich auf den Bildschirm zugeordnet wurde. Wenn die Fensterzuordnung abgeschlossen ist (d. h., der Ereignistyp ist MapNotify).
  1. XFlush: Leert den Ausgabepuffer des X-Servers, um sicherzustellen, dass vorherige Zeichenbefehle wirksam werden und nicht auf ein nachfolgendes Animationszeichnen verzögert werden.

  2. srand: Initialisiert den Zufallszahlengenerator. Nutzt die aktuelle Zeit als Seed für einen Zufallszahlengenerator, um Zufallseffekte in einer Animation zu erzeugen.

  3. origin_points: Allokiert Speicher und erstellt ein Array von quantity Point-Strukturen, die zur Speicherung der ursprünglichen Punktkoordinaten verwendet werden.

  4. points: Allokiert erneut Speicher und erstellt ein größeres Array, um die Punkte in der Animation zu speichern. circles steuert die Anzahl der Punkte in der Animation und ist ein konstanter Wert.

  5. Schließlich wird die Funktion create_data() aufgerufen, um die Daten zu initialisieren, die Koordinaten der ursprünglichen Punkte zu generieren und festzulegen sowie die Farbe und die Anfangskoordinaten der animierten Punkte zu initialisieren.

✨ Lösung prüfen und üben

Hauptschleife der Animation

Ziel dieser Hauptschleife ist es, den Zustand des Herzmusters zu steuern, indem der Wert von frame geändert wird, um die Animationseinwirkung zu erweitern und zu verkleinern. Die Funktion usleep wird verwendet, um die Bildrate zu steuern, damit die Animation mit einer bestimmten Geschwindigkeit abgespielt wird.

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

Zunächst werden zwei boolesche Variablen, extend und shrink, definiert und auf true bzw. false initialisiert, die den erweiterten und den zusammengezogenen Zustand des Herzmusters repräsentieren.

Dann wird eine endlose Schleife gestartet, und die Variable frame in der Schleife wird verwendet, um die Anzahl der aktuellen Animationsbilder zu verfolgen.

  • usleep wird verwendet, um die Animationsgeschwindigkeit zu steuern.

  • Alles auf dem Fenster wird gelöscht, indem die X11-Funktion XClearWindow aufgerufen wird, damit es im nächsten Bild neu gezeichnet werden kann.

  • Die Anzahl der frames wird basierend auf den Werten von extend und shrink erhöht oder verringert, um die Animation zu erweitern und zu verkleinern. Der bedingte Operator ? : wird verwendet, um einen booleschen Wert festzulegen.

Wenn extend true ist, wird überprüft, ob frame gleich 19 ist. Wenn ja, wechselt die Animation vom erweiterten in den zusammengezogenen Zustand, shrink wird auf true und extend auf false gesetzt. Andernfalls wird frame erhöht.

Wenn extend false ist, wird überprüft, ob frame gleich 0 ist. Wenn ja, wechselt die Animation vom zusammengezogenen in den erweiterten Zustand, shrink wird auf false und extend auf true gesetzt. Andernfalls wird frame verringert.

Schließlich werden die Ressourcen freigegeben und das Programm beendet.

// Inside the main() function
XCloseDisplay(display);
free(origin_points);
free(points);
return 0;
✨ Lösung prüfen und üben

Projekt kompilieren und ausführen

  1. Öffnen Sie Ihr Terminal und navigieren Sie in das Projektverzeichnis.
cd ~/project
  1. Kompilieren Sie den Code mit dem folgenden Befehl:
gcc -o dynamic_heart dynamic_heart.c -lX11 -lm
  1. Führen Sie die Anwendung aus:
./dynamic_heart
Dynamic Heart
✨ Lösung prüfen und üben

Zusammenfassung

In diesem Projekt haben Sie gelernt, wie Sie mit der Programmiersprache C eine faszinierende dynamische Herzanimation erstellen können. Sie haben das Projekt eingerichtet, Variablen definiert, Funktionen für Bildschirmkoordinaten, Zufallszahlengeneratoren und Datengenerierung implementiert. Das Programm nutzt das X Window System, um das animierte Herz zu rendern. Schließlich haben Sie eine Animationsschleife erstellt, die Anzeige geschlossen und den zugewiesenen Speicher freigegeben. Jetzt können Sie Ihre eigene dynamische Herzanimation in C genießen.