Introdução
Neste projeto, você aprenderá como criar uma hipnotizante animação de coração dinâmico usando a linguagem de programação C. O projeto utiliza o X Window System para renderizar visuais animados. Seguindo as instruções passo a passo, você configurará o projeto, gerará dados e criará uma animação cativante que dá vida a um coração dinâmico na sua tela.
👀 Pré-visualização

🎯 Tarefas
Neste projeto, você aprenderá:
- Como configurar um projeto de programação C para criar uma animação de coração dinâmico
- Como usar bibliotecas do X Window System para criar e gerenciar janelas gráficas
- Como gerar pontos aleatórios e animá-los para formar um formato de coração
- Como controlar a animação para expandir e contrair, criando um efeito visual cativante
🏆 Conquistas
Após concluir este projeto, você será capaz de:
- Usar bibliotecas do X Window System para programação gráfica em C
- Gerar e manipular pontos aleatórios em C
- Criar uma animação dinâmica usando uma combinação de geração de dados e técnicas de renderização
Criar os Arquivos do Projeto
Certifique-se de ter as bibliotecas necessárias instaladas. Você precisará das bibliotecas de desenvolvimento X11. Você pode instalá-las usando o seguinte comando:
sudo apt update
sudo apt-get install libx11-dev
Em seguida, crie um novo arquivo chamado dynamic_heart.c e abra-o no seu editor de código preferido.
cd ~/project
touch dynamic_heart.c
Definir as Variáveis Necessárias
Primeiramente, precisamos escrever o código C. O primeiro passo é incluir os arquivos de cabeçalho:
#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>
Defina a estrutura para armazenar informações de pontos, incluindo sua coordenada x, coordenada y e cor. Usado para representar pontos em um gráfico.
struct Point {
double x, y;
unsigned long color;
};
Em seguida, defina algumas variáveis globais:
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 variáveis relacionadas ao X Window System para criar e gerenciar janelas gráficas e contextos gráficos.
Display *display;
Window win;
GC gc;
display representa a conexão com o servidor X, win representa a janela e gc representa o contexto gráfico, que é usado para desenhar os elementos gráficos.
Implementar Funções de Coordenadas da Tela
double screen_x(double x) {
x += xScreen / 2;
return x;
}
double screen_y(double y) {
y = -y + yScreen / 2;
return y;
}
O propósito das funções screen_x e screen_y é mapear as coordenadas reais para as coordenadas da tela, garantindo que os elementos gráficos na animação estejam dentro da área visível da tela e sejam exibidos na posição correta. Para ser mais específico:
screen_x:
- Esta função recebe um valor de coordenada x real como argumento e, em seguida, o ajusta para que seja desenhado corretamente na tela. Ela adiciona o valor da coordenada x de entrada à metade de
xScreen, de modo que fique no centro horizontal da tela, uma vez quexScreenrepresenta a largura da tela, dividida por2para encontrar o centro horizontal da tela. - A função retorna o valor da coordenada x ajustada para desenhar pontos ou formas na tela.
screen_y:
- Esta função é semelhante a
screen_x, mas lida com as coordenadas y. Ela recebe o valor da coordenada y real como parâmetro e o converte para o sistema de coordenadas da tela. Primeiro, ela inverte a coordenada y para que a origem do sistema de coordenadas esteja no canto superior esquerdo da tela, em seguida, adiciona-a à metade deyScreenpara que a coordenada esteja no centro vertical da tela. - A função retorna o valor da coordenada y ajustada para que um ponto ou forma seja desenhado corretamente na tela.
Implementar Função Geradora de Números Aleatórios
int create_random(int x1, int x2) {
if (x2 > x1) {
return rand() % (x2 - x1 + 1) + x1;
}
return 0;
}
Esta é uma função usada para gerar inteiros aleatórios dentro de um intervalo especificado, e é utilizada para randomizar a cor e a posição dos pontos gerados, a fim de aumentar a variedade visual da animação.
Onde x1 e x2 são os dois inteiros passados como argumentos. A função retorna um inteiro aleatório entre x1 e x2, incluindo x1 e x2. Se x2 for maior que x1, um inteiro aleatório é gerado usando a função rand(), que é restrita a um intervalo válido usando aritmética modular. A função rand() geralmente retorna um inteiro aleatório entre 0 e RAND_MAX (geralmente 32767). Finalmente, ela adiciona o resultado da operação modular a x1 para garantir que o inteiro aleatório caia dentro do intervalo especificado.
Inicializar e Gerar Conjuntos de Pontos em Forma de Coração
A função create_data gera dados para a animação. Ela calcula pontos para uma forma de coração e os anima com base no algoritmo definido.
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 um loop para iterar por uma série de valores de radianos (de
0.1a2 * PI) e calcular as coordenadas x e y de cada ponto.A distância entre dois pontos é calculada, e se a distância for maior que
average_distance, o ponto é armazenado no arrayorigin_points.
Colorir Pontos para Gerar Animação
// 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);
}
}
}
Existem dois loops aninhados. O loop externo aumenta gradualmente o valor de
size, e o loop interno processa cada pontoquantity.Com a probabilidade de
success_p, decide-se se um ponto deve ser gerado e armazena-se a cor e as coordenadas do ponto no arraypoints.
Geração e Atualização da Animação
// 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_sizeé usado para obter o número de pontos no quadro de animação atual, calculado a partir da seção de código anterior.indexé o número de pontos gerados anteriormente.O loop externo
for (int frame = 0; frame < frames; ++frame)é usado para iterar sobre cada quadro da animação, eframesespecifica quantos quadros existem no total.O loop interno
for (index = 0; index < points_size; ++index)é usado para processar cada ponto no quadro atual. Em cada quadro, ele faz o seguinte:
- Primeiro, calcula a nova posição de cada ponto. Isso é feito pela seguinte fórmula:
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;
Esses cálculos são usados para atualizar as coordenadas x e y do ponto para alcançar o movimento do ponto na animação. distance_increase controla a velocidade com que o ponto se move, que varia com a distância da posição original do ponto.
- Desenha pontos usando as funções
XSetForegroundeXFillArc. Isso desenha o ponto na tela,XSetForegroundpara definir a cor da pintura,XFillArcpara desenhar um ponto preenchido, e as coordenadas do centro do círculo são convertidas pelas funçõesscreen_xescreen_y.
- A segunda parte do loop interno
for (double size = 17; size < 23; size += 0.3)é usada para gerar pontos adicionais no quadro atual. Neste loop, cada ponto é gerado, colorido e desenhado na tela.
- As coordenadas e cores dos novos pontos são geradas aleatoriamente de acordo com as seguintes condições:
Se
size >= 20e o número aleatório for maior que0.6, ousize < 20e o número aleatório for maior que0.95, um novo ponto é gerado.
- As coordenadas x e y dos pontos gerados são calculadas a partir da posição do ponto original e alguns deslocamentos aleatórios.
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);
}
- Finalmente, use as funções
XSetForegroundeXFillArcpara desenhar o ponto gerado na tela, assim como o ponto anterior.
Criar Janela X e Inicializar
O objetivo principal desta função main é criar uma janela X, inicializar os dados e, em seguida, gerar e desenhar cada quadro da animação em forma de coração em um loop infinito.
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: Abre a conexão com o servidor X.blackColor: Obtém o valor do pixel preto exibido.win: Cria uma janela e especifica suas propriedades, localização, tamanho e cor da borda.XSelectInput: Especifica a máscara de evento de entrada para a janela.XMapWindow: Mapeia a janela para a tela.gc: Cria um Contexto Gráfico (Graphics Context).
Inicializa Dados e Cria Grupos de Pontos
// 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)é um loop infinito que espera a conclusão do mapeamento da janela X, que é a fase de inicialização antes que a janela seja exibida na tela.
- Define uma estrutura
XEventpara receber eventos X. XNextEvent: Espera e obtém o próximo evento X e o armazena na variávele.- Usa
ifpara verificar se o tipo do evento éMapNotify, indicando que a janela foi mapeada com sucesso para a tela. Se o mapeamento da janela estiver completo (ou seja, o tipo de evento éMapNotify).
XFlush: Esvazia o buffer de saída do servidor X para garantir que os comandos de desenho anteriores entrem em vigor e não sejam adiados para um desenho de animação subsequente.srand: Inicializa o gerador de números aleatórios. Usa o tempo atual como semente para um gerador de números aleatórios para gerar efeitos aleatórios em uma animação.origin_points: Aloca memória e cria um array dequantityestruturasPointque serão usadas para armazenar as coordenadas originais dos pontos.points: Novamente, aloca memória e cria um array maior para armazenar os pontos na animação.circlescontrola o número de pontos na animação e é um valor constante.Finalmente, a função
create_data()é chamada para inicializar os dados, gerar e definir as coordenadas dos pontos originais e inicializar a cor e as coordenadas iniciais dos pontos animados.
Loop Principal da Animação
O objetivo deste loop principal é controlar o estado do padrão do coração, alterando o valor de frame para permitir que o efeito de animação se expanda e contraia. A função usleep é usada para controlar a taxa de quadros, de modo que a animação seja reproduzida a uma determinada velocidade.
// 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;
}
Primeiro, define-se duas variáveis booleanas, extend e shrink, e inicializa-se com true e false, representando os estados expandido e contraído do padrão do coração, respectivamente.
Em seguida, um loop infinito é iniciado, e a variável frame no loop é usada para rastrear a contagem dos quadros de animação atuais.
usleepé usado para controlar a velocidade da animação.Limpa tudo na janela chamando a função X11
XClearWindowpara que possa ser redesenhado no próximo quadro.Adiciona ou diminui
framescom base nos valoresextendeshrinkpara permitir que a animação se expanda e contraia. O operador condicional? :é usado para definir um valor booleano.
Se
extendfortrue, verifica-se seframeé igual a19. Se for, a animação está prestes a mudar do estado expandido para o estado contraído, define-seshrinkcomotrueeextendcomofalse. Caso contrário, incrementa-seframe.
Se
extendforfalse, verifica-se seframeé igual a0. Se for, a animação está prestes a mudar do estado contraído para o estado expandido, define-seshrinkcomofalseeextendcomotrue. Caso contrário,frameé decrementado.
Finalmente, libera-se o recurso e sai-se.
// Inside the main() function
XCloseDisplay(display);
free(origin_points);
free(points);
return 0;
Compilar e Executar o Projeto
- Abra seu terminal e navegue até o diretório do projeto.
cd ~/project
- Compile o código usando o seguinte comando:
gcc -o dynamic_heart dynamic_heart.c -lX11 -lm
- Execute a aplicação:
./dynamic_heart

Resumo
Neste projeto, você aprendeu como criar uma animação de coração dinâmica cativante usando a linguagem de programação C. Você configurou o projeto, definiu variáveis, implementou funções de coordenadas de tela, funções de gerador de números aleatórios e funções de geração de dados. O programa utiliza o X Window System para renderizar o coração animado. Finalmente, você criou um loop de animação e fechou a exibição, liberando a memória alocada. Agora você pode desfrutar de sua própria animação de coração dinâmica criada em C.



