Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Computing a 2D signed distance field

I'm trying to compute a signed distance field of an black and white images pixels, but I think I've managed to get my code wrong somewhere. As this is my input and output:

Input

Input

Output

Output

The issue I'm having is the black line in the middle of the S, my understanding leaves me to believe that it should be completely light gray?

This is the code I'm using:

    for (int x = 0; x < source.width; ++x)
    {
        for(int y = 0; y < source.height; ++y) 
        {
            // Get pixel
            float a = source.GetPixel(x, y).r;

            // Distance to closest pixel which is the inverse of a
            // start on float.MaxValue so we can be sure we found something
            float distance = float.MaxValue;

            // Search coordinates, x min/max and y min/max
            int fxMin = Math.Max(x - searchDistance, 0);
            int fxMax = Math.Min(x + searchDistance, source.width);
            int fyMin = Math.Max(y - searchDistance, 0);
            int fyMax = Math.Min(y + searchDistance, source.height);

            for (int fx = fxMin; fx < fxMax; ++fx)
            {
                for (int fy = fyMin; fy < fyMax; ++fy)
                {
                    // Get pixel to compare to
                    float p = source.GetPixel(fx, fy).r;

                    // If not equal a
                    if (a != p)
                    {
                        // Calculate distance
                        float xd = x - fx;
                        float yd = y - fy;
                        float d = Math.Sqrt((xd * xd) + (yd * yd));

                        // Compare absolute distance values, and if smaller replace distnace with the new oe
                        if (Math.Abs(d) < Math.Abs(distance))
                        {
                            distance = d;
                        }
                    }
                }
            }

            // If we found a new distance, otherwise we'll just use A 

            if (distance != float.MaxValue)
            {

                // Clamp distance to -/+ 
                distance = Math.Clamp(distance, -searchDistance, +searchDistance);

                // Convert from -search,+search to 0,+search*2 and then convert to 0.0, 1.0 and invert
                a = 1f - Math.Clamp((distance + searchDistance) / (searchDistance + searchDistance), 0, 1);
            }

            // Write pixel out
            target.SetPixel(x, y, new Color(a, a, a, 1));
        }
    }
like image 777
thr Avatar asked Aug 09 '12 21:08

thr


People also ask

How do you create a signed distance field?

To make the stroke style create signed distance fields, Position should be set to Center. Next, set Fill Type to Gradient and set the Style to Shape Burst. The result is a Signed Distance Field , meaning the original hard boundary will be encoded as 0.5, and distance is stored on both sides of the edge.

How do signed distance fields work?

A signed distance field is represented as a grid sampling of the closest distance to the surface of an object represented as a polygonal model. Usually the convention of using negative values inside the object and positive values outside the object is applied.

What is Euclidean signed distance field?

Euclidean Signed Distance Field (ESDF) is useful for online motion planning of aerial robots since it can easily query the distance and gradient information against obstacles. Fast incrementally built ESDF map is the bottleneck for conducting real-time motion planning.

What is Houdini SDF?

What is it? SDF: Scalar Distance Field. ​ A SDF is a function which calculates the distance to the surface of an object by using the space that object contains. You may notice that SDFs usually function with anything that has a volume or a VDB attribute.


1 Answers

your culprit is this condition statement:

// If not equal a
if (a != p)
{

This means that you are only interested on the shortest distance from a Black pixel to a White pixel, or if 'a' is white, then you are looking for the closest Black pixel.

If you change that test to just see:

if ( p == white )
{

Then you will probably get what you expect.

(I didn't test this, so hopefully its correct).

(Also, if it wasn't correct, it would be nice to post your Math.Clamp method since it isn't a built in library method in the Math class.)

One last thing, not sure if the algorithm wants you to compare a pixel to itself or not, so you might need to account for that within your nested for loops.

(basically, what would you expect the output should look like of an entirely black image with one white pixel in the middle? should the output of the middle pixel be black since there is no nearby white pixels, or should it be white.)

like image 187
Xantix Avatar answered Sep 17 '22 19:09

Xantix