Compare commits

..

12 Commits

20 changed files with 339 additions and 171 deletions

16
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,16 @@
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: sudo apt-get install -y gcc libgomp1
- name: Build
run: make

4
.gitignore vendored
View File

@@ -1,2 +1,2 @@
wallpaper
distance_field_generator
pkl-wallpaper
distance_field_generator

View File

View File

@@ -1,7 +1,21 @@
all: wallpaper distance_field_generator
all: pkl-wallpaper distance_field_generator
wallpaper: src/wallpaper.c
gcc -O3 -Werror -Wall -Wextra -I./vendor/raylib-5.5_linux_amd64/include/ -o wallpaper src/wallpaper.c -L./vendor/raylib-5.5_linux_amd64/lib -l:libraylib.a -lm
pkl-wallpaper: src/wallpaper.c
gcc -O3 -Werror -Wall -Wextra -pedantic -I./vendor/raylib-5.5_linux_amd64/include/ -o pkl-wallpaper src/wallpaper.c -L./vendor/raylib-5.5_linux_amd64/lib -l:libraylib.a -lm
distance_field_generator: src/distance_field_generator.c
gcc -Werror -Wall -Wextra -fopenmp -I./vendor/raylib-5.5_linux_amd64/include/ -o distance_field_generator src/distance_field_generator.c -L./vendor/raylib-5.5_linux_amd64/lib -l:libraylib.a -lm
gcc -Werror -Wall -Wextra -pedantic -fopenmp -I./vendor/raylib-5.5_linux_amd64/include/ -o distance_field_generator src/distance_field_generator.c -L./vendor/raylib-5.5_linux_amd64/lib -l:libraylib.a -lm
install: pkl-wallpaper
sudo install -D -m 755 pkl-wallpaper /usr/local/bin/pkl-wallpaper
sudo mkdir -p /usr/local/share/pkl-wallpaper
sudo cp -r assets /usr/local/share/pkl-wallpaper/
uninstall:
sudo rm -f /usr/local/bin/pkl-wallpaper
sudo rm -rf /usr/local/share/pkl-wallpaper/
clean:
rm -f pkl-wallpaper distance_field_generator
.PHONY: all install uninstall clean

View File

@@ -1,14 +1,60 @@
# wallpaper
<div align="center">
<h1>pihkaal's wallpaper</h1>
<img src="docs/demo.gif" alt="demo" />
</div>
An animated and interactive wallpaper I'm building for myself, designed to run on [Hyprland](https://hyprland.org/).
## What it does
- Animated stars
- Metaballs-like effect with the "clouds"
## Limitations
- The wallpaper will only work on one monitor and on 1920x1080px for now
## Stack
- [C](https://en.wikipedia.org/wiki/C_(programming_language))
- [Raylib](https://www.raylib.com/)
- [Hyprland](https://hypr.land/)
## Quick start
### Build and install
```console
$ make
# generate distance field with 50px search radius
# note that i already ship a generated distance field
$ ./distance_field_generator ./resources/textures/background_mask.png ./resources/textures/background_distance_field.png 50
# generate distance field with 100px ball radius (needed to offset vertically the effect)
# NOTE: I already ship a generated distance field
# USAGE: distance_field_generator <mask_path> <output_path> <ball_radius>
$ ./distance_field_generator ./assets/textures/background_mask.png ./assets/textures/background_distance_field.png 100
# run wallpaper, image paths are customizables
$ ./wallpaper
# install
$ make install
# uninstall
$ make uninstall
```
### Run as wallpaper
You'll need to install [hyprwinwrap](https://code.hyprland.org/hyprwm/hyprland-plugins/src/branch/main/hyprwinwrap), then add this to your hyprland configuration:
```ini
plugin {
hyprwinwrap {
class = pkl-wallpaper
}
}
```
### Run as debug
Even if you've configured the app to always run as wallpaper using hyprwinwrap, you can still run it in debug mode:
```console
$ DEBUG=1 ./pkl-wallpaper
```

View File

@@ -9,10 +9,9 @@ uniform mat4 mvp;
out vec2 fragTexCoord;
out vec4 fragColor;
void main()
{
void main() {
fragTexCoord = vertexTexCoord;
fragColor = vertexColor;
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
}

View File

@@ -13,8 +13,8 @@ uniform float shapeInfluence;
out vec4 finalColor;
float getBallInfluence(vec2 pos, vec2 center, float radius) {
vec2 diff = pos - center;
float distSq = dot(diff, diff);
vec2 diff = pos - center;
float distSq = dot(diff, diff);
float radiusSq = radius * radius;
if (distSq > radiusSq) return 0.0;
float dist = sqrt(distSq);
@@ -23,38 +23,35 @@ float getBallInfluence(vec2 pos, vec2 center, float radius) {
float getShapeInfluence(vec2 pos) {
vec2 uv = pos / resolution;
uv.y = 1 - uv.y + 0.045; // invert y and offset to match with wallpaper texture
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) {
return 0.0;
}
uv.y = 1.0 - uv.y;
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) return 0.0;
float signedDistance = texture(distanceFieldTex, uv).r * 255.0 - 128.0;
float isInside = step(signedDistance, 0.0);
float isInside = step(signedDistance, 0.0);
float isOutside = 1.0 - isInside;
// inside calculation
float normalizedDist = abs(signedDistance) * 0.015625; // 1/64
float normalizedDist = abs(signedDistance) * 0.015625; // 1/64
float insideInfluence = shapeInfluence * exp(-normalizedDist * 0.5) * 3.0;
// outside calculation
float adjustedDistance = signedDistance * 0.5;
float shapeInfluenceSq = shapeInfluence * shapeInfluence;
float outsideInfluence = shapeInfluenceSq / (adjustedDistance * adjustedDistance + shapeInfluence) +
float outsideInfluence = shapeInfluenceSq / (adjustedDistance * adjustedDistance + shapeInfluence) +
shapeInfluence * 0.8 * exp(-signedDistance * 0.025); // 1/40
return isInside * insideInfluence + isOutside * outsideInfluence;
}
void main()
{
void main() {
vec2 pixelPos = fragTexCoord * resolution;
float ballValue = getBallInfluence(pixelPos, mousePos, ballRadius);
float ballValue = getBallInfluence(pixelPos, mousePos, ballRadius);
float shapeValue = getShapeInfluence(pixelPos);
float totalValue = ballValue + shapeValue * 0.01;
float mask = step(0.75, totalValue);
finalColor = vec4(mask, mask, mask, 1.0);
}
}

View File

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 MiB

After

Width:  |  Height:  |  Size: 7.9 MiB

BIN
assets/textures/star_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

BIN
assets/textures/star_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
assets/textures/star_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
assets/textures/star_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

BIN
assets/textures/star_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
docs/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

View File

@@ -1,124 +1,171 @@
#include <raylib.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <omp.h>
bool is_inside_shape(Color pixel)
{
static bool is_inside_shape(Color pixel) {
return pixel.r == 255 && pixel.g == 255 && pixel.b == 255;
}
Image generate_distance_field(Image input_texture, int search_radius)
{
int width = input_texture.width;
int height = input_texture.height;
// 1D squared Euclidean distance transform (felzenszwalb & huttenlocher)
static void edt_1d(float *f, int n, int *v, float *z, float *tmp) {
int k = 0;
v[0] = 0;
z[0] = -1e30f;
z[1] = 1e30f;
Image distance_field = GenImageColor(width, height, (Color){128, 128, 128, 255});
Color *input_pixels = LoadImageColors(input_texture);
Color *output_pixels = LoadImageColors(distance_field);
TraceLog(LOG_INFO, "Using %d threads for parallel processing", omp_get_max_threads());
#pragma omp parallel for schedule(dynamic, 64)
for (int y = 0; y < height; y++)
{
if (omp_get_thread_num() == 0 && y % (height / 10) == 0)
{
TraceLog(LOG_INFO, "Progress: %d%%", (y * 100) / height);
}
for (int x = 0; x < width; x++)
{
int index = y * width + x;
bool is_inside = is_inside_shape(input_pixels[index]);
float min_distance_sq = search_radius * search_radius;
bool found_boundary = false;
for (int dy = -search_radius; dy <= search_radius; dy += 1)
{
for (int dx = -search_radius; dx <= search_radius; dx += 1)
{
int sample_x = x + dx;
int sample_y = y + dy;
if (sample_x < 0 || sample_x >= width || sample_y < 0 || sample_y >= height) continue;
int sample_index = sample_y * width + sample_x;
bool sample_is_inside = is_inside_shape(input_pixels[sample_index]);
if (sample_is_inside != is_inside)
{
float distance_sq = dx * dx + dy * dy;
if (distance_sq < min_distance_sq)
{
min_distance_sq = distance_sq;
found_boundary = true;
}
}
}
}
float distance = found_boundary ? sqrtf(min_distance_sq) : (float)search_radius;
float normalized_distance = distance / (float)search_radius;
unsigned char distance_value;
if (is_inside)
{
distance_value = (unsigned char)(127.0f - 127.0f * normalized_distance);
}
else
{
distance_value = (unsigned char)(128.0f + 127.0f * normalized_distance);
}
output_pixels[index] = (Color){distance_value, distance_value, distance_value, 255};
for (int q = 1; q < n; q += 1) {
float fq = f[q];
float s = ((fq + (float)(q * q)) - (f[v[k]] + (float)(v[k] * v[k]))) / (float)(2 * (q - v[k]));
while (s <= z[k]) {
k -= 1;
s = ((fq + (float)(q * q)) - (f[v[k]] + (float)(v[k] * v[k]))) / (float)(2 * (q - v[k]));
}
k += 1;
v[k] = q;
z[k] = s;
z[k + 1] = 1e30f;
}
for (int i = 0; i < width * height; i++)
{
ImageDrawPixel(&distance_field, i % width, i / width, output_pixels[i]);
int j = 0;
for (int q = 0; q < n; q += 1) {
while (z[j + 1] < (float)q) j += 1;
float d = (float)(q - v[j]);
tmp[q] = d * d + f[v[j]];
}
UnloadImageColors(input_pixels);
UnloadImageColors(output_pixels);
return distance_field;
memcpy(f, tmp, n * sizeof(float));
}
int main(int argc, char *argv[])
{
if (argc < 3)
static float *compute_edt(bool *seeds, int width, int height) {
float large = (float)((width + height) * (width + height));
float *grid = malloc(width * height * sizeof(float));
for (int i = 0; i < width * height; i += 1) {
grid[i] = seeds[i] ? 0.0f : large;
}
int maxdim = width > height ? width : height;
#pragma omp parallel
{
TraceLog(LOG_ERROR, "Usage: %s <input_image> <output_image> [search_radius]", argv[0]);
int *v = malloc(maxdim * sizeof(int));
float *z = malloc((maxdim + 1) * sizeof(float));
float *tmp = malloc(maxdim * sizeof(float));
float *col = malloc(maxdim * sizeof(float));
// 1D EDT along each column
#pragma omp for nowait
for (int x = 0; x < width; x += 1) {
for (int y = 0; y < height; y += 1) col[y] = grid[y * width + x];
edt_1d(col, height, v, z, tmp);
for (int y = 0; y < height; y += 1) grid[y * width + x] = col[y];
}
#pragma omp barrier
// 1D EDT along each row
#pragma omp for nowait
for (int y = 0; y < height; y += 1) {
memcpy(col, &grid[y * width], width * sizeof(float));
edt_1d(col, width, v, z, tmp);
memcpy(&grid[y * width], col, width * sizeof(float));
}
free(v);
free(z);
free(tmp);
free(col);
}
return grid;
}
static Image generate_distance_field(Image input_texture, int offset_y) {
int width = input_texture.width;
int height = input_texture.height;
Color *input_pixels = LoadImageColors(input_texture);
bool *inside_mask = calloc(width * height, sizeof(bool));
for (int y = 0; y < height; y += 1) {
int src_y = y + offset_y;
if (src_y < 0 || src_y >= height) continue;
for (int x = 0; x < width; x += 1) {
inside_mask[y * width + x] = is_inside_shape(input_pixels[src_y * width + x]);
}
}
UnloadImageColors(input_pixels);
bool *outside_mask = malloc(width * height * sizeof(bool));
for (int i = 0; i < width * height; i += 1) outside_mask[i] = !inside_mask[i];
TraceLog(LOG_INFO, "Computing inside EDT...");
float *dist_to_outside = compute_edt(outside_mask, width, height);
TraceLog(LOG_INFO, "Computing outside EDT...");
float *dist_to_inside = compute_edt(inside_mask, width, height);
free(outside_mask);
// pixel_value = clamp(sdf, -128, 127) + 128
Color *out_pixels = malloc(width * height * sizeof(Color));
for (int i = 0; i < width * height; i += 1) {
float sdf = inside_mask[i]
? -sqrtf(dist_to_outside[i]) // negative = inside
: +sqrtf(dist_to_inside[i]); // positive = outside
float clamped = fmaxf(-128.0f, fminf(127.0f, sdf));
unsigned char v = (unsigned char)(clamped + 128.0f);
out_pixels[i] = (Color){v, v, v, 255};
}
free(inside_mask);
free(dist_to_inside);
free(dist_to_outside);
Image out = {
.data = out_pixels,
.width = width,
.height = height,
.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
.mipmaps = 1,
};
return out;
}
int main(int argc, char *argv[]) {
if (argc < 3) {
TraceLog(LOG_ERROR, "Usage: %s <input_image> <output_image> [ball_radius]", argv[0]);
return 1;
}
const char *input_path = argv[1];
const char *input_path = argv[1];
const char *output_path = argv[2];
int search_radius = (argc >= 4) ? atoi(argv[3]) : 100;
int ball_radius = (argc >= 4) ? atoi(argv[3]) : 0;
int offset_y = (ball_radius + 10) / 2;
Image input_image = LoadImage(input_path);
if (input_image.data == NULL)
{
TraceLog(LOG_ERROR, "Error: Failed to load input image '%s'", input_path);
if (input_image.data == NULL) {
TraceLog(LOG_ERROR, "Failed to load input image '%s'", input_path);
return 1;
}
Image distance_field_image = generate_distance_field(input_image, search_radius);
if (!ExportImage(distance_field_image, output_path))
{
TraceLog(LOG_INFO, "Generating distance field (%dx%d, offset_y=%d)...", input_image.width, input_image.height, offset_y);
Image distance_field = generate_distance_field(input_image, offset_y);
if (!ExportImage(distance_field, output_path)) {
TraceLog(LOG_ERROR, "Failed to save output image '%s'", output_path);
UnloadImage(input_image);
UnloadImage(distance_field_image);
UnloadImage(distance_field);
return 1;
}
TraceLog(LOG_INFO, "Distance field saved to '%s'", output_path);
UnloadImage(input_image);
UnloadImage(distance_field_image);
UnloadImage(distance_field);
return 0;
}
}

View File

@@ -6,23 +6,41 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <math.h>
#define WIDTH 1920
#define WIDTH 1920
#define HEIGHT 1080
char *hyprland_socket_path;
#define BALL_RADIUS 100.0f
#define INFLUENCE 200.0f
Vector2 hyprland_get_cursor_position()
{
Vector2 pos = {0};
typedef struct {
const char* name;
Vector2 pos;
Vector2 origin;
float angle;
float angle_step;
} Star;
static Star stars[] = {
{ .name = "star_1.png", .pos = { 4, 528 }, .origin = {165, 166}, .angle = 90.0, .angle_step = -0.025 },
{ .name = "star_2.png", .pos = { 215, 809 }, .origin = {82, 83}, .angle = 90.0, .angle_step = 0.05 },
{ .name = "star_3.png", .pos = { 333, 781 }, .origin = {25, 31}, .angle = 90.0, .angle_step = 0.15 },
{ .name = "star_4.png", .pos = { 956, 405 }, .origin = {166, 167}, .angle = 90.0, .angle_step = -0.1 },
{ .name = "star_5.png", .pos = { 1164, 570 }, .origin = {66, 70}, .angle = 90.0, .angle_step = 0.075 },
};
#define STARS_COUNT (int)(sizeof(stars) / sizeof(stars[0]))
static char *hyprland_socket_path;
static Vector2 hyprland_get_cursor_position() {
Vector2 pos = {0, 0};
int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd == -1)
{
perror("socket");
if (sock_fd == -1) {
TraceLog(LOG_ERROR, "socket: %s", strerror(errno));
return pos;
}
@@ -30,27 +48,23 @@ Vector2 hyprland_get_cursor_position()
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, hyprland_socket_path, sizeof(addr.sun_path) - 1);
if (connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
printf("Using hyprland socket: %s\n", hyprland_socket_path);
perror("connect");
if (connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
TraceLog(LOG_ERROR, "connect %s: %s", hyprland_socket_path, strerror(errno));
close(sock_fd);
return pos;
}
const char *message = "/cursorpos";
if (send(sock_fd, message, strlen(message), 0) == -1)
{
perror("send");
if (send(sock_fd, message, strlen(message), 0) == -1) {
TraceLog(LOG_ERROR, "send: %s", strerror(errno));
close(sock_fd);
return pos;
}
char buffer[256] = {0};
ssize_t bytes_received = recv(sock_fd, buffer, sizeof(buffer) - 1, 0);
if (bytes_received == -1)
{
perror("recv");
if (bytes_received == -1) {
TraceLog(LOG_ERROR, "recv: %s", strerror(errno));
close(sock_fd);
return pos;
}
@@ -58,67 +72,91 @@ Vector2 hyprland_get_cursor_position()
// format is x, y so parse here
buffer[bytes_received] = '\0';
if (sscanf(buffer, "%f, %f", &pos.x, &pos.y) != 2)
{
fprintf(stderr, "Failed to parse cursor position\n");
if (sscanf(buffer, "%f, %f", &pos.x, &pos.y) != 2) {
TraceLog(LOG_ERROR, "Failed to parse cursor position");
}
close(sock_fd);
return pos;
}
Texture2D load_texture_from_file(const char *file_path)
{
Texture2D texture = LoadTexture(file_path);
if (texture.id == 0)
{
static const char *resolve_asset_path(const char *path) {
if (FileExists(path)) return path;
const char *system_path = TextFormat("/usr/local/share/pkl-wallpaper/%s", path);
if (FileExists(system_path)) return system_path;
return path;
}
static Texture2D load_texture_from_file(const char *file_path) {
Texture2D texture = LoadTexture(resolve_asset_path(file_path));
if (texture.id == 0) {
TraceLog(LOG_ERROR, "Failed to load texture from file: %s", file_path);
abort();
}
return texture;
}
int main(void)
{
InitWindow(WIDTH, HEIGHT, "pihkaal-wallpaper");
static Shader load_shader_from_file(const char *vs_path, const char *fs_path) {
Shader shader = LoadShader(resolve_asset_path(vs_path), resolve_asset_path(fs_path));
if (shader.id == 0) {
TraceLog(LOG_ERROR, "Failed to load shader from files: %s, %s", vs_path, fs_path);
abort();
}
return shader;
}
static const char* get_window_title() {
return getenv("DEBUG") != NULL ? "pkl-wallpaper-debug" : "pkl-wallpaper";
}
static char* get_hyprland_socket_path() {
return strdup(TextFormat("%s/hypr/%s/.socket.sock", getenv("XDG_RUNTIME_DIR"), getenv("HYPRLAND_INSTANCE_SIGNATURE")));
}
int main(void) {
InitWindow(WIDTH, HEIGHT, get_window_title());
SetWindowState(FLAG_WINDOW_RESIZABLE);
SetTargetFPS(60);
// initialize hyprland socket path
hyprland_socket_path = strdup(TextFormat("%s/hypr/%s/.socket.sock", getenv("XDG_RUNTIME_DIR"), getenv("HYPRLAND_INSTANCE_SIGNATURE")));
hyprland_socket_path = get_hyprland_socket_path();
// initialize textures
Texture2D distanceFieldTex = load_texture_from_file("resources/textures/background_distance_field.png");
Texture2D backgroundTex = load_texture_from_file("resources/textures/background_transparent.png");
Texture2D distanceFieldTex = load_texture_from_file("assets/textures/background_distance_field.png");
Texture2D backgroundTex = load_texture_from_file("assets/textures/background_transparent.png");
Texture2D starTextures[STARS_COUNT] = {0};
for (int i = 0; i < STARS_COUNT; i++) {
starTextures[i] = load_texture_from_file(TextFormat("assets/textures/%s", stars[i].name));
}
RenderTexture2D target = LoadRenderTexture(WIDTH, HEIGHT);
// initialize shader
Shader shader = LoadShader("resources/shaders/basic.vs", "resources/shaders/distance_field.fs");
Shader shader = load_shader_from_file("assets/shaders/basic.vs", "assets/shaders/distance_field.fs");
int resolutionLoc = GetShaderLocation(shader, "resolution");
int mousePosLoc = GetShaderLocation(shader, "mousePos");
int ballRadiusLoc = GetShaderLocation(shader, "ballRadius");
int shapeInfluenceLoc = GetShaderLocation(shader, "shapeInfluence");
int resolutionLoc = GetShaderLocation(shader, "resolution");
int mousePosLoc = GetShaderLocation(shader, "mousePos");
int ballRadiusLoc = GetShaderLocation(shader, "ballRadius");
int shapeInfluenceLoc = GetShaderLocation(shader, "shapeInfluence");
int distanceFieldTexLoc = GetShaderLocation(shader, "distanceFieldTex");
Vector2 resolution = {WIDTH, HEIGHT};
SetShaderValue(shader, resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
float ballRadius = 100.0f;
float ballRadius = BALL_RADIUS;
SetShaderValue(shader, ballRadiusLoc, &ballRadius, SHADER_UNIFORM_FLOAT);
float shapeInfluence = 200.0f;
float shapeInfluence = INFLUENCE;
SetShaderValue(shader, shapeInfluenceLoc, &shapeInfluence, SHADER_UNIFORM_FLOAT);
int textureUnit = 1;
SetShaderValue(shader, distanceFieldTexLoc, &textureUnit, SHADER_UNIFORM_INT);
while (!WindowShouldClose())
{
while (!WindowShouldClose()) {
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
if (IsWindowResized())
{
if (IsWindowResized()) {
UnloadRenderTexture(target);
target = LoadRenderTexture(screenWidth, screenHeight);
@@ -142,7 +180,15 @@ int main(void)
DrawTextureRec(backgroundTex, (Rectangle){0, 0, screenWidth, screenHeight}, (Vector2){0, 0}, WHITE);
DrawText(TextFormat("Distance Field | x: %.0f, y: %.0f | frame: %.2f ms", mousePos.x, mousePos.y, GetFrameTime() * 1000), 10, 10, 20, WHITE);
for (int i = 0; i < STARS_COUNT; i++) {
Star* star = &stars[i];
star->angle = fmodf(star->angle + star->angle_step, 360.0f);
Texture2D tex = starTextures[i];
Rectangle source = {0, 0, (float)tex.width, (float)tex.height};
Rectangle dest = {star->pos.x, star->pos.y, (float)tex.width, (float)tex.height};
DrawTexturePro(tex, source, dest, star->origin, star->angle, WHITE);
}
EndDrawing();
}
@@ -150,10 +196,13 @@ int main(void)
UnloadRenderTexture(target);
UnloadTexture(distanceFieldTex);
UnloadTexture(backgroundTex);
for (int i = 0; i < STARS_COUNT; i++) {
UnloadTexture(starTextures[i]);
}
UnloadShader(shader);
CloseWindow();
free(hyprland_socket_path);
return 0;
}
}