feat: distance field approach

This commit is contained in:
2025-09-05 11:34:02 +02:00
parent 2aca6c5b1f
commit dc9de39aa5
12 changed files with 298 additions and 146 deletions

View File

@@ -0,0 +1,124 @@
#include <raylib.h>
#include <stdlib.h>
#include <math.h>
#include <omp.h>
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;
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 i = 0; i < width * height; i++)
{
ImageDrawPixel(&distance_field, i % width, i / width, output_pixels[i]);
}
UnloadImageColors(input_pixels);
UnloadImageColors(output_pixels);
return distance_field;
}
int main(int argc, char *argv[])
{
if (argc < 3)
{
TraceLog(LOG_ERROR, "Usage: %s <input_image> <output_image> [search_radius]", argv[0]);
return 1;
}
const char *input_path = argv[1];
const char *output_path = argv[2];
int search_radius = (argc >= 4) ? atoi(argv[3]) : 100;
Image input_image = LoadImage(input_path);
if (input_image.data == NULL)
{
TraceLog(LOG_ERROR, "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_ERROR, "Failed to save output image '%s'", output_path);
UnloadImage(input_image);
UnloadImage(distance_field_image);
return 1;
}
TraceLog(LOG_INFO, "Distance field saved to '%s'", output_path);
UnloadImage(input_image);
UnloadImage(distance_field_image);
return 0;
}

View File

@@ -1,92 +0,0 @@
#include <raylib.h>
#define WIDTH 800
#define HEIGHT 600
typedef struct
{
Vector2 points[6];
int numPoints;
float influence;
} Polygon;
Polygon poly = {
{{WIDTH / 2 + 50, HEIGHT / 2 - 50},
{WIDTH / 2 + 80, HEIGHT / 2},
{WIDTH / 2 + 50, HEIGHT / 2 + 50},
{WIDTH / 2 - 50, HEIGHT / 2 + 50},
{WIDTH / 2 - 80, HEIGHT / 2},
{WIDTH / 2 - 50, HEIGHT / 2 - 50}},
6,
3000.0f};
int main(void)
{
InitWindow(WIDTH, HEIGHT, "Wallpaper");
SetWindowState(FLAG_WINDOW_RESIZABLE);
SetTargetFPS(60);
// initialize shader
Shader shader = LoadShader("resources/shaders/basic.vs", "resources/shaders/basic.fs");
int resolutionLoc = GetShaderLocation(shader, "resolution");
int mousePosLoc = GetShaderLocation(shader, "mouse_pos");
int ballRadiusLoc = GetShaderLocation(shader, "ball_radius");
int polygonPointsLoc = GetShaderLocation(shader, "polygon_points");
int polygonInfluenceLoc = GetShaderLocation(shader, "polygon_influence");
Vector2 resolution = {WIDTH, HEIGHT};
SetShaderValue(shader, resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
// send ball data
float ballRadius = 40.0f;
SetShaderValue(shader, ballRadiusLoc, &ballRadius, SHADER_UNIFORM_FLOAT);
// send polygon data
float polygonInfluence = poly.influence;
SetShaderValue(shader, polygonInfluenceLoc, &polygonInfluence, SHADER_UNIFORM_FLOAT);
float polygonPoints[12];
for (int i = 0; i < poly.numPoints; i++)
{
polygonPoints[i * 2] = poly.points[i].x;
polygonPoints[i * 2 + 1] = poly.points[i].y;
}
SetShaderValueV(shader, polygonPointsLoc, polygonPoints, SHADER_UNIFORM_VEC2, poly.numPoints);
// initializee render texture
RenderTexture2D target = LoadRenderTexture(WIDTH, HEIGHT);
while (!WindowShouldClose())
{
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
if (IsWindowResized())
{
UnloadRenderTexture(target);
target = LoadRenderTexture(screenWidth, screenHeight);
Vector2 resolution = {screenWidth, screenHeight};
SetShaderValue(shader, resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
}
Vector2 mousePos = GetMousePosition();
mousePos.y = screenHeight - mousePos.y;
SetShaderValue(shader, mousePosLoc, &mousePos, SHADER_UNIFORM_VEC2);
BeginDrawing();
BeginShaderMode(shader);
DrawTextureRec(target.texture, (Rectangle){0, 0, screenWidth, -screenHeight}, (Vector2){0, 0}, WHITE);
EndShaderMode();
EndDrawing();
}
UnloadRenderTexture(target);
UnloadShader(shader);
CloseWindow();
return 0;
}

91
src/wallpaper.c Normal file
View File

@@ -0,0 +1,91 @@
#include <raylib.h>
#include <rlgl.h>
#define WIDTH 1920
#define HEIGHT 1080
Texture2D load_texture_from_file(const char *file_path)
{
Texture2D texture = LoadTexture(file_path);
if (texture.id == 0)
{
TraceLog(LOG_ERROR, "Failed to load texture from file: %s", file_path);
}
return texture;
}
int main(void)
{
InitWindow(WIDTH, HEIGHT, "wallpaper");
SetWindowState(FLAG_WINDOW_RESIZABLE);
SetTargetFPS(60);
// 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");
RenderTexture2D target = LoadRenderTexture(WIDTH, HEIGHT);
// initialize shader
Shader shader = LoadShader("resources/shaders/basic.vs", "resources/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 distanceFieldTexLoc = GetShaderLocation(shader, "distanceFieldTex");
Vector2 resolution = {WIDTH, HEIGHT};
SetShaderValue(shader, resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
float ballRadius = 100.0f;
SetShaderValue(shader, ballRadiusLoc, &ballRadius, SHADER_UNIFORM_FLOAT);
float shapeInfluence = 200.0f;
SetShaderValue(shader, shapeInfluenceLoc, &shapeInfluence, SHADER_UNIFORM_FLOAT);
int textureUnit = 1;
SetShaderValue(shader, distanceFieldTexLoc, &textureUnit, SHADER_UNIFORM_INT);
while (!WindowShouldClose())
{
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
if (IsWindowResized())
{
UnloadRenderTexture(target);
target = LoadRenderTexture(screenWidth, screenHeight);
Vector2 resolution = {screenWidth, screenHeight};
SetShaderValue(shader, resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
}
Vector2 mousePos = GetMousePosition();
mousePos.y = screenHeight - mousePos.y;
SetShaderValue(shader, mousePosLoc, &mousePos, SHADER_UNIFORM_VEC2);
BeginDrawing();
rlActiveTextureSlot(1);
rlEnableTexture(distanceFieldTex.id);
rlActiveTextureSlot(0);
BeginShaderMode(shader);
DrawTextureRec(target.texture, (Rectangle){0, 0, screenWidth, -screenHeight}, (Vector2){0, 0}, WHITE);
EndShaderMode();
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);
EndDrawing();
}
UnloadRenderTexture(target);
UnloadTexture(distanceFieldTex);
UnloadTexture(backgroundTex);
UnloadShader(shader);
CloseWindow();
return 0;
}