C 언어로 동적 하트 애니메이션 만들기

CBeginner
지금 연습하기

소개

이 프로젝트에서는 C 프로그래밍 언어를 사용하여 매혹적인 동적 하트 애니메이션을 만드는 방법을 배우게 됩니다. 이 프로젝트는 X Window System 을 활용하여 애니메이션 시각 효과를 렌더링합니다. 단계별 지침에 따라 프로젝트를 설정하고, 데이터를 생성하며, 화면에서 동적인 하트를 생생하게 구현하는 매혹적인 애니메이션을 만들 수 있습니다.

👀 미리보기

Dynamic Heart

🎯 작업

이 프로젝트에서 다음을 배우게 됩니다:

  • 동적 하트 애니메이션을 만들기 위해 C 프로그래밍 프로젝트를 설정하는 방법
  • X Window System 라이브러리를 사용하여 그래픽 창을 생성하고 관리하는 방법
  • 하트 모양을 형성하기 위해 임의의 점을 생성하고 애니메이션하는 방법
  • 애니메이션을 제어하여 확장 및 축소하여 매혹적인 시각 효과를 만드는 방법

🏆 성과

이 프로젝트를 완료하면 다음을 수행할 수 있습니다:

  • C 에서 그래픽 프로그래밍을 위해 X Window System 라이브러리를 사용
  • C 에서 임의의 점을 생성하고 조작
  • 데이터 생성 및 렌더링 기술을 조합하여 동적 애니메이션 생성

프로젝트 파일 생성

필요한 라이브러리가 설치되어 있는지 확인하십시오. X11 개발 라이브러리가 필요합니다. 다음 명령을 사용하여 설치할 수 있습니다:

sudo apt update
sudo apt-get install libx11-dev

그런 다음, dynamic_heart.c라는 새 파일을 생성하고 선호하는 코드 편집기에서 엽니다.

cd ~/project
touch dynamic_heart.c
✨ 솔루션 확인 및 연습

필요한 변수 정의

먼저, C 코드를 작성해야 합니다. 첫 번째 단계는 헤더 파일을 포함하는 것입니다:

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

x 좌표, y 좌표 및 색상을 포함한 점 정보를 저장하기 위한 구조체를 정의합니다. 그래프에서 점을 나타내는 데 사용됩니다.

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

그런 다음, 몇 가지 전역 변수를 정의합니다:

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;

그래픽 창 및 그래픽 컨텍스트를 생성하고 관리하기 위해 X Window System 관련 변수를 사용합니다.

Display *display;
Window win;
GC gc;

display는 X 서버 연결을 나타내고, win은 창을 나타내며, gc는 그래픽 요소를 그리는 데 사용되는 그래픽 컨텍스트를 나타냅니다.

✨ 솔루션 확인 및 연습

화면 좌표 함수 구현

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

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

screen_xscreen_y 함수의 목적은 실제 좌표를 화면 좌표에 매핑하여 애니메이션의 그래픽 요소가 화면의 가시 영역 내에 있고 올바른 위치에 표시되도록 하는 것입니다. 구체적으로 다음과 같습니다:

  1. screen_x:
  • 이 함수는 실제 x 좌표 값을 인수로 받아 화면에 올바르게 그려지도록 조정합니다. 입력 x 좌표 값에 xScreen의 절반을 더하여 화면의 수평 중앙에 위치하도록 합니다. 여기서 xScreen은 화면의 너비를 나타내며, 2로 나누어 화면의 수평 중앙을 찾습니다.
  • 이 함수는 화면에 점이나 도형을 그리기 위한 조정된 x 좌표 값을 반환합니다.
  1. screen_y:
  • 이 함수는 screen_x와 유사하지만 y 좌표를 처리합니다. 실제 y 좌표 값을 매개변수로 받아 화면 좌표계로 변환합니다. 먼저, 좌표계의 원점이 화면의 왼쪽 상단 모서리에 있도록 y 좌표를 반전시킨 다음, yScreen의 절반을 더하여 좌표가 화면의 수직 중앙에 위치하도록 합니다.
  • 이 함수는 점이나 도형이 화면에 올바르게 그려지도록 조정된 y 좌표 값을 반환합니다.
✨ 솔루션 확인 및 연습

난수 생성기 함수 구현

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

이 함수는 지정된 범위 내에서 임의의 정수를 생성하는 데 사용되며, 생성된 점의 색상과 위치를 무작위로 지정하여 애니메이션의 시각적 다양성을 높이는 데 사용됩니다.

여기서 x1x2는 인수로 전달된 두 개의 정수입니다. 이 함수는 x1x2를 포함하여 x1x2 사이의 임의의 정수를 반환합니다. x2x1보다 크면, 모듈로 연산을 사용하여 유효한 범위로 제한된 rand() 함수를 사용하여 임의의 정수가 생성됩니다. rand() 함수는 일반적으로 0RAND_MAX (일반적으로 32767) 사이의 임의의 정수를 반환합니다. 마지막으로, 모듈로 연산의 결과에 x1을 더하여 임의의 정수가 지정된 범위 내에 있도록 합니다.

✨ 솔루션 확인 및 연습

하트 모양 점 집합 초기화 및 생성

create_data 함수는 애니메이션에 대한 데이터를 생성합니다. 하트 모양의 점을 계산하고 정의된 알고리즘에 따라 애니메이션을 적용합니다.

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;
        }
    }
  • 일련의 라디안 값 ( 0.1에서 2 * PI까지) 을 반복하여 각 점의 x 및 y 좌표를 계산하기 위해 루프를 사용합니다.

  • 두 점 사이의 거리가 계산되고, 거리가 average_distance보다 크면 해당 점이 origin_points 배열에 저장됩니다.

✨ 솔루션 확인 및 연습

애니메이션 생성을 위한 점 색상 지정

// 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);
        }
    }
}
  • 두 개의 중첩된 루프가 있습니다. 외부 루프는 size의 값을 점진적으로 증가시키고, 내부 루프는 각 quantity 점을 처리합니다.

  • success_p의 확률로 점을 생성할지 여부를 결정하고, 점의 색상과 좌표를 points 배열에 저장합니다.

✨ 솔루션 확인 및 연습

애니메이션 생성 및 업데이트

// 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는 현재 애니메이션 프레임의 점 개수를 얻는 데 사용되며, 이전 코드 섹션에서 계산됩니다. index는 이전에 생성된 점의 수입니다.

  2. 외부 루프 for (int frame = 0; frame < frames; ++frame)는 애니메이션의 각 프레임을 반복하는 데 사용되며, frames는 총 프레임 수를 지정합니다.

  3. 내부 루프 for (index = 0; index < points_size; ++index)는 현재 프레임의 각 점을 처리하는 데 사용됩니다. 각 프레임에서 다음을 수행합니다.

  • 먼저, 각 점의 새로운 위치를 계산합니다. 이는 다음 공식을 사용하여 수행됩니다.
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;

이러한 계산은 애니메이션에서 점의 움직임을 달성하기 위해 점의 x 및 y 좌표를 업데이트하는 데 사용됩니다. distance_increase는 점의 원래 위치로부터의 거리에 따라 달라지는 점의 이동 속도를 제어합니다.

  • XSetForegroundXFillArc 함수를 사용하여 점을 그립니다. 이는 화면에 점을 그리는 것으로, XSetForeground는 페인트 색상을 설정하고, XFillArc는 채워진 점을 그리며, 원의 중심 좌표는 screen_xscreen_y 함수에 의해 변환됩니다.
  1. 내부 루프의 두 번째 부분 for (double size = 17; size < 23; size += 0.3)는 현재 프레임에서 추가 점을 생성하는 데 사용됩니다. 이 루프에서 각 점은 생성, 색상 지정 및 화면에 그려집니다.
  • 새로운 점의 좌표와 색상은 다음 조건에 따라 무작위로 생성됩니다.

size >= 20이고 난수가 0.6보다 크거나, size < 20이고 난수가 0.95보다 크면 새로운 점이 생성됩니다.

  • 생성된 점의 x 및 y 좌표는 원래 점의 위치와 일부 무작위 오프셋에서 계산됩니다.
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);
}
  • 마지막으로, XSetForegroundXFillArc 함수를 사용하여 생성된 점을 이전 점과 마찬가지로 화면에 그립니다.
✨ 솔루션 확인 및 연습

X Window 생성 및 초기화

main 함수의 주요 목표는 X 윈도우를 생성하고, 데이터를 초기화한 다음, 무한 루프에서 하트 모양 애니메이션의 모든 프레임을 생성하고 그리는 것입니다.

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: X 서버에 대한 연결을 엽니다.
  • blackColor: 표시된 검은색 픽셀 값을 가져옵니다.
  • win: 윈도우를 생성하고 속성, 위치, 크기 및 테두리 색상을 지정합니다.
  • XSelectInput: 윈도우에 대한 입력 이벤트 마스크를 지정합니다.
  • XMapWindow: 윈도우를 화면에 매핑합니다.
  • gc: 그래픽 컨텍스트 (Graphics Context) 를 생성합니다.
✨ 솔루션 확인 및 연습

데이터 초기화 및 포인트 그룹 생성

// 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)은 X 윈도우 맵 (Map) 이 완료될 때까지 대기하는 무한 루프입니다. 이는 윈도우가 화면에 표시되기 전의 초기화 단계입니다.
  • XEvent 구조체를 정의하여 X 이벤트를 수신합니다.
  • XNextEvent: 다음 X 이벤트를 대기하고 가져와서 e 변수에 저장합니다.
  • if를 사용하여 이벤트 유형이 MapNotify인지 확인합니다. 이는 윈도우가 화면에 성공적으로 매핑되었음을 나타냅니다. 윈도우 매핑이 완료되면 (즉, 이벤트 유형이 MapNotify인 경우) 루프를 종료합니다.
  1. XFlush: X 서버의 출력 버퍼를 비워 이전 그리기 명령이 적용되도록 하고, 후속 애니메이션 그리기로 지연되지 않도록 합니다.

  2. srand: 난수 생성기를 초기화합니다. 현재 시간을 난수 생성기의 시드로 사용하여 애니메이션에서 무작위 효과를 생성합니다.

  3. origin_points: 메모리를 할당하고 원래 점 좌표를 저장하는 데 사용될 quantity개의 Point 구조체 배열을 생성합니다.

  4. points: 다시 메모리를 할당하고 애니메이션의 점을 저장하기 위해 더 큰 배열을 생성합니다. circles는 애니메이션의 점 수를 제어하며 상수 값입니다.

  5. 마지막으로, create_data() 함수를 호출하여 데이터를 초기화하고, 원래 점의 좌표를 생성 및 설정하고, 애니메이션 점의 색상과 초기 좌표를 초기화합니다.

✨ 솔루션 확인 및 연습

애니메이션 메인 루프 실행

이 메인 루프의 목적은 frame 값을 변경하여 하트 패턴의 상태를 제어하여 애니메이션 효과가 확장 및 축소되도록 하는 것입니다. usleep 함수는 애니메이션이 특정 속도로 재생되도록 프레임 속도를 제어하는 데 사용됩니다.

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

먼저, 두 개의 부울 변수 extendshrink를 정의하고 각각 하트 패턴의 확장 및 축소 상태를 나타내는 truefalse로 초기화합니다.

그런 다음, 무한 루프가 시작되고 루프 내의 frame 변수는 현재 애니메이션 프레임의 수를 추적하는 데 사용됩니다.

  • usleep는 애니메이션 속도를 제어하는 데 사용됩니다.

  • X11 함수 XClearWindow를 호출하여 윈도우의 모든 내용을 지워 다음 프레임에서 다시 그릴 수 있도록 합니다.

  • extendshrink 값에 따라 frame을 증가시키거나 감소시켜 애니메이션이 확장 및 축소되도록 합니다. 조건 연산자 ? :를 사용하여 부울 값을 설정합니다.

extendtrue인 경우, frame19와 같은지 확인합니다. 그렇다면 애니메이션이 확장 상태에서 축소 상태로 전환하려는 것이므로 shrinktrue로 설정하고 extendfalse로 설정합니다. 그렇지 않으면 frame을 증가시킵니다.

extendfalse인 경우, frame0과 같은지 확인합니다. 그렇다면 애니메이션이 축소 상태에서 확장 상태로 전환하려는 것이므로 shrinkfalse로 설정하고 extendtrue로 설정합니다. 그렇지 않으면 frame을 감소시킵니다.

마지막으로, 리소스를 해제하고 종료합니다.

// Inside the main() function
XCloseDisplay(display);
free(origin_points);
free(points);
return 0;
✨ 솔루션 확인 및 연습

프로젝트 컴파일 및 실행

  1. 터미널을 열고 프로젝트 디렉토리로 이동합니다.
cd ~/project
  1. 다음 명령을 사용하여 코드를 컴파일합니다.
gcc -o dynamic_heart dynamic_heart.c -lX11 -lm
  1. 애플리케이션을 실행합니다.
./dynamic_heart
Dynamic Heart
✨ 솔루션 확인 및 연습

요약

이 프로젝트에서 C 프로그래밍 언어를 사용하여 매력적인 동적 하트 애니메이션을 만드는 방법을 배웠습니다. 프로젝트를 설정하고, 변수를 정의하고, 화면 좌표 함수, 난수 생성기 함수 및 데이터 생성 함수를 구현했습니다. 이 프로그램은 X Window System 을 사용하여 애니메이션 하트를 렌더링합니다. 마지막으로, 애니메이션 루프를 생성하고 디스플레이를 닫아 할당된 메모리를 해제했습니다. 이제 C 로 생성된 나만의 동적 하트 애니메이션을 즐길 수 있습니다.