feat: distance field approach
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
wallpaper
|
||||
wallpaper
|
||||
distance_field_generator
|
||||
9
Makefile
9
Makefile
@@ -1,2 +1,7 @@
|
||||
wallpaper: src/main.c
|
||||
gcc -Werror -Wall -Wextra -I./vendor/raylib-5.5_linux_amd64/include/ -o wallpaper src/main.c -L./vendor/raylib-5.5_linux_amd64/lib -l:libraylib.a -lm
|
||||
all: wallpaper distance_field_generator
|
||||
|
||||
wallpaper: src/wallpaper.c
|
||||
gcc -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
|
||||
|
||||
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
|
||||
15
README.md
15
README.md
@@ -1 +1,14 @@
|
||||
# wallpaper
|
||||
# wallpaper
|
||||
|
||||
## Quick start
|
||||
|
||||
```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
|
||||
|
||||
# run wallpaper, image paths are customizables
|
||||
$ ./wallpaper
|
||||
```
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
|
||||
uniform vec2 resolution;
|
||||
uniform vec2 mouse_pos;
|
||||
uniform float ball_radius;
|
||||
uniform vec2 polygon_points[6];
|
||||
uniform float polygon_influence;
|
||||
|
||||
out vec4 finalColor;
|
||||
|
||||
float distanceToPolygon(vec2 point, vec2 polyPoints[6]) {
|
||||
float minDist = 100000.0;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int j = (i + 1) % 6;
|
||||
vec2 a = polyPoints[i];
|
||||
vec2 b = polyPoints[j];
|
||||
|
||||
vec2 pa = point - a;
|
||||
vec2 ba = b - a;
|
||||
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
||||
float dist = length(pa - ba * h);
|
||||
minDist = min(minDist, dist);
|
||||
}
|
||||
|
||||
return minDist;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 pixel_pos = fragTexCoord * resolution;
|
||||
|
||||
vec2 ball_pixel_pos = mouse_pos;
|
||||
float ball_dist = distance(pixel_pos, ball_pixel_pos);
|
||||
float ball_value = (ball_radius * ball_radius) / (ball_dist * ball_dist + 1.0);
|
||||
|
||||
float poly_dist = distanceToPolygon(pixel_pos, polygon_points);
|
||||
float poly_value = polygon_influence / (poly_dist * poly_dist + 1.0);
|
||||
|
||||
float total_value = ball_value + poly_value;
|
||||
|
||||
if (total_value > 0.7) {
|
||||
finalColor = vec4(1.0, 1.0, 1.0, 1.0); // White
|
||||
} else {
|
||||
finalColor = vec4(0.0, 0.0, 0.0, 1.0); // Black
|
||||
}
|
||||
}
|
||||
60
resources/shaders/distance_field.fs
Normal file
60
resources/shaders/distance_field.fs
Normal file
@@ -0,0 +1,60 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
|
||||
uniform sampler2D texture0;
|
||||
uniform sampler2D distanceFieldTex;
|
||||
uniform vec2 resolution;
|
||||
uniform vec2 mousePos;
|
||||
uniform float ballRadius;
|
||||
uniform float shapeInfluence;
|
||||
|
||||
out vec4 finalColor;
|
||||
|
||||
float getBallInfluence(vec2 pos, vec2 center, float radius) {
|
||||
vec2 diff = pos - center;
|
||||
float distSq = dot(diff, diff);
|
||||
float radiusSq = radius * radius;
|
||||
if (distSq > radiusSq) return 0.0;
|
||||
float dist = sqrt(distSq);
|
||||
return 1.0 - (dist / 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;
|
||||
}
|
||||
|
||||
float signedDistance = texture(distanceFieldTex, uv).r * 255.0 - 128.0;
|
||||
|
||||
float isInside = step(signedDistance, 0.0);
|
||||
float isOutside = 1.0 - isInside;
|
||||
|
||||
// inside calculation
|
||||
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) +
|
||||
shapeInfluence * 0.8 * exp(-signedDistance * 0.025); // 1/40
|
||||
|
||||
return isInside * insideInfluence + isOutside * outsideInfluence;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 pixelPos = fragTexCoord * resolution;
|
||||
|
||||
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);
|
||||
}
|
||||
BIN
resources/textures/background.png
Normal file
BIN
resources/textures/background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 211 KiB |
BIN
resources/textures/background_distance_field.png
Normal file
BIN
resources/textures/background_distance_field.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 193 KiB |
BIN
resources/textures/background_mask.png
Normal file
BIN
resources/textures/background_mask.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
resources/textures/background_transparent.png
Normal file
BIN
resources/textures/background_transparent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 MiB |
124
src/distance_field_generator.c
Normal file
124
src/distance_field_generator.c
Normal 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;
|
||||
}
|
||||
92
src/main.c
92
src/main.c
@@ -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
91
src/wallpaper.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user