feat: improve distance field generator perfs and quality using 1d squared euclidian distance
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 188 KiB |
@@ -1,5 +1,6 @@
|
|||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <omp.h>
|
#include <omp.h>
|
||||||
|
|
||||||
@@ -7,104 +8,164 @@ static bool is_inside_shape(Color pixel) {
|
|||||||
return pixel.r == 255 && pixel.g == 255 && pixel.b == 255;
|
return pixel.r == 255 && pixel.g == 255 && pixel.b == 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Image generate_distance_field(Image input_texture, int search_radius, int offset_y) {
|
// 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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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]];
|
||||||
|
}
|
||||||
|
memcpy(f, tmp, n * sizeof(float));
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
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 width = input_texture.width;
|
||||||
int height = input_texture.height;
|
int height = input_texture.height;
|
||||||
|
|
||||||
Image distance_field = GenImageColor(width, height, (Color){128, 128, 128, 255});
|
|
||||||
Color *input_pixels = LoadImageColors(input_texture);
|
Color *input_pixels = LoadImageColors(input_texture);
|
||||||
Color *output_pixels = LoadImageColors(distance_field);
|
bool *inside_mask = calloc(width * height, sizeof(bool));
|
||||||
|
|
||||||
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 += 1) {
|
for (int y = 0; y < height; y += 1) {
|
||||||
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 += 1) {
|
|
||||||
int index = y * width + x;
|
|
||||||
int src_y = y + offset_y;
|
int src_y = y + offset_y;
|
||||||
bool is_inside = (src_y >= 0 && src_y < height)
|
if (src_y < 0 || src_y >= height) continue;
|
||||||
? is_inside_shape(input_pixels[src_y * width + x])
|
for (int x = 0; x < width; x += 1) {
|
||||||
: false;
|
inside_mask[y * width + x] = is_inside_shape(input_pixels[src_y * width + x]);
|
||||||
|
|
||||||
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 + offset_y;
|
|
||||||
|
|
||||||
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 = is_inside ?
|
|
||||||
(unsigned char)(127.0f - 127.0f * normalized_distance) :
|
|
||||||
(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 += 1) {
|
|
||||||
ImageDrawPixel(&distance_field, i % width, i / width, output_pixels[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
UnloadImageColors(input_pixels);
|
UnloadImageColors(input_pixels);
|
||||||
UnloadImageColors(output_pixels);
|
|
||||||
|
|
||||||
return distance_field;
|
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[])
|
int main(int argc, char *argv[]) {
|
||||||
{
|
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
TraceLog(LOG_ERROR, "Usage: %s <input_image> <output_image> [search_radius] [ball_radius]", argv[0]);
|
TraceLog(LOG_ERROR, "Usage: %s <input_image> <output_image> [ball_radius]", argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *input_path = argv[1];
|
const char *input_path = argv[1];
|
||||||
const char *output_path = argv[2];
|
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 ball_radius = (argc >= 5) ? atoi(argv[4]) : 0;
|
int offset_y = (ball_radius + 10) / 2;
|
||||||
int offset_y = ball_radius / 2;
|
|
||||||
|
|
||||||
Image input_image = LoadImage(input_path);
|
Image input_image = LoadImage(input_path);
|
||||||
if (input_image.data == NULL) {
|
if (input_image.data == NULL) {
|
||||||
TraceLog(LOG_ERROR, "Error: Failed to load input image '%s'", input_path);
|
TraceLog(LOG_ERROR, "Failed to load input image '%s'", input_path);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Image distance_field_image = generate_distance_field(input_image, search_radius, offset_y);
|
TraceLog(LOG_INFO, "Generating distance field (%dx%d, offset_y=%d)...", input_image.width, input_image.height, offset_y);
|
||||||
if (!ExportImage(distance_field_image, output_path)) {
|
|
||||||
|
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);
|
TraceLog(LOG_ERROR, "Failed to save output image '%s'", output_path);
|
||||||
UnloadImage(input_image);
|
UnloadImage(input_image);
|
||||||
UnloadImage(distance_field_image);
|
UnloadImage(distance_field);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
TraceLog(LOG_INFO, "Distance field saved to '%s'", output_path);
|
TraceLog(LOG_INFO, "Distance field saved to '%s'", output_path);
|
||||||
|
|
||||||
UnloadImage(input_image);
|
UnloadImage(input_image);
|
||||||
UnloadImage(distance_field_image);
|
UnloadImage(distance_field);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user