我在C#中寻找一个带有位图实例输入的距离转换实现。
这是一个有效的解决方案,扩散值因图像大小而异。它此时没有8bpp位图输出。
示例输出。。
public class DistanceField
{
public int Spread = 8;
public int WhiteColor = Color.White.ToArgb();
public DistanceField()
{}
/**
* Caclulate the squared distance between two points
*
* @param x1 The x coordinate of the first point
* @param y1 The y coordiante of the first point
* @param x2 The x coordinate of the second point
* @param y2 The y coordinate of the second point
* @return The squared distance between the two points
*/
private int squareDist(int x1, int y1, int x2, int y2)
{
int dx = x1 - x2;
int dy = y1 - y2;
return dx * dx + dy * dy;
}
/**
* Process the image into a distance field.
*
* The input image should be binary (black/white), but if not, see {@link #isInside(int)}.
*
* The returned image is a factor of {@code upscale} smaller than {@code inImage}.
* Opaque pixels more than {@link #spread} away in the output image from white remain opaque;
* transparent pixels more than {@link #spread} away in the output image from black remain transparent.
* In between, we get a smooth transition from opaque to transparent, with an alpha value of 0.5
* when we are exactly on the edge.
*
* @param inImage the image to process.
* @return the distance field image
*/
public Bitmap GenerateDistanceField(Bitmap inImage)
{
Bitmap outImage = new Bitmap(inImage.Width, inImage.Height, PixelFormat.Format32bppArgb);
// Note: coordinates reversed to mimic storage of BufferedImage, for memory locality
bool[,] bitmap = new bool[inImage.Height, inImage.Width];
for(int y = 0; y < inImage.Height; ++y)
{
for(int x = 0; x < inImage.Width; ++x)
{
bitmap[y, x] = isInside(inImage.GetPixel(x, y).ToArgb());
}
}
for (int y = 0; y < inImage.Height; ++y)
{
for (int x = 0; x < inImage.Width; ++x)
{
float signedDistance = findSignedDistance(x, y, ref bitmap);
outImage.SetPixel(x, y, Color.FromArgb(distanceToRGB(signedDistance)));
}
}
return outImage;
}
public Bitmap ConvertToGrayScale(Bitmap oldbmp)
{
using (var ms = new MemoryStream())
{
oldbmp.Save(ms, ImageFormat.Gif);
ms.Position = 0;
return (Bitmap)Image.FromStream(ms);
}
}
/**
* Returns {@code true} if the color is considered as the "inside" of the image,
* {@code false} if considered "outside".
*
* <p> Any color with one of its color channels at least 128
* <em>and</em> its alpha channel at least 128 is considered "inside".
*/
private bool isInside(int rgb)
{
return (rgb & 0x808080) != 0 && (rgb & 0x80000000) != 0;
}
/**
* Returns the signed distance for a given point.
*
* For points "inside", this is the distance to the closest "outside" pixel.
* For points "outside", this is the <em>negative</em> distance to the closest "inside" pixel.
* If no pixel of different color is found within a radius of {@code spread}, returns
* the {@code -spread} or {@code spread}, respectively.
*
* @param centerX the x coordinate of the center point
* @param centerY the y coordinate of the center point
* @param bitmap the array representation of an image, {@code true} representing "inside"
* @return the signed distance
*/
private float findSignedDistance(int centerX, int centerY, ref bool[,] bitmap)
{
int width = bitmap.GetLength(1);
int height = bitmap.GetLength(0);
bool baseVal = bitmap[centerY, centerX];
int delta = this.Spread;
int startX = Math.Max(0, centerX - delta);
int endX = Math.Min(width - 1, centerX + delta);
int startY = Math.Max(0, centerY - delta);
int endY = Math.Min(height - 1, centerY + delta);
int closestSquareDist = delta * delta;
for (int y = startY; y <= endY; ++y)
{
for (int x = startX; x <= endX; ++x)
{
if (baseVal != bitmap[y, x])
{
int sqDist = squareDist(centerX, centerY, x, y);
if (sqDist < closestSquareDist)
{
closestSquareDist = sqDist;
}
}
}
}
float closestDist = (float)Math.Sqrt(closestSquareDist);
return (baseVal ? 1 : -1) * Math.Min(closestDist, this.Spread);
}
/**
* For a distance as returned by {@link #findSignedDistance}, returns the corresponding "RGB" (really ARGB) color value.
*
* @param signedDistance the signed distance of a pixel
* @return an ARGB color value suitable for {@link BufferedImage#setRGB}.
*/
private int distanceToRGB(float signedDistance)
{
float alpha = 0.5f + 0.5f * (signedDistance / this.Spread);
alpha = Math.Min(1, Math.Max(0, alpha)); // compensate for rounding errors
int alphaByte = (int)(alpha * 0xFF); // no unsigned byte in Java :(
return (alphaByte << 24) | (this.WhiteColor & 0xFFFFFF);
}
}