BaseLine
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
/packages/
|
||||||
|
riderModule.iml
|
||||||
|
/_ReSharper.Caches/
|
||||||
13
.idea/.idea.SDFMapCreator/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.SDFMapCreator/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/modules.xml
|
||||||
|
/contentModel.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/.idea.SDFMapCreator.iml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
8
.idea/.idea.SDFMapCreator/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.SDFMapCreator/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/.idea.SDFMapCreator/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.SDFMapCreator/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
16
SDFMapCreator.sln
Normal file
16
SDFMapCreator.sln
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDFMapCreator", "SDFMapCreator\SDFMapCreator.csproj", "{915A479D-55CC-4B48-B7C0-75E0B8978698}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{915A479D-55CC-4B48-B7C0-75E0B8978698}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{915A479D-55CC-4B48-B7C0-75E0B8978698}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{915A479D-55CC-4B48-B7C0-75E0B8978698}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{915A479D-55CC-4B48-B7C0-75E0B8978698}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
BIN
SDFMapCreator/1.png
Normal file
BIN
SDFMapCreator/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
BIN
SDFMapCreator/2.png
Normal file
BIN
SDFMapCreator/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
254
SDFMapCreator/Program.cs
Normal file
254
SDFMapCreator/Program.cs
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using ImageMagick;
|
||||||
|
|
||||||
|
public struct float2(float x, float y) {
|
||||||
|
public float x = x, y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct float3(float r, float g, float b) {
|
||||||
|
public float r = r, g = g, b = b;
|
||||||
|
public float3(float value) : this(value, value, value) {}
|
||||||
|
public float3(float r, float g) : this(r, g, 0f) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct float4(float r, float g, float b, float a) {
|
||||||
|
public float r = r, g = g, b = b, a = a;
|
||||||
|
public float4(float value) : this(value, value, value, value) {}
|
||||||
|
public float4(float r, float g, float b) : this(r, g, b, 1f) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ImageData(MagickImage Image, float3[,] Pixels, List<float2> Edges);
|
||||||
|
public record MaskData(float3[,] Mask, ImageData A, ImageData B, List<float2> Edges);
|
||||||
|
|
||||||
|
public record SDFData(float3[,] SDF);
|
||||||
|
|
||||||
|
public static class ArrayExt {
|
||||||
|
public static float3[,] To2DFloat3(this float[] array, uint width, uint height) {
|
||||||
|
float3[,] result = new float3[width, height];
|
||||||
|
for(int i = 0; i < width*height; i++) {
|
||||||
|
uint x = (uint)(i % width);
|
||||||
|
uint y = (uint)(i / width);
|
||||||
|
result[y, x] = new (array[i*3], array[i*3+1], array[i*3+2]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float[] ToFloatArray(this float3[,] array) {
|
||||||
|
float[] result = new float[array.GetLength(0) * array.GetLength(1) * 3];
|
||||||
|
for(int x = 0; x < array.GetLength(0); x++) {
|
||||||
|
for(int y = 0; y < array.GetLength(1); y++) {
|
||||||
|
result[x*array.GetLength(1)*3 + y*3] = array[x, y].r;
|
||||||
|
result[x*array.GetLength(1)*3 + y*3+1] = array[x, y].g;
|
||||||
|
result[x*array.GetLength(1)*3 + y*3+2] = array[x, y].b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Program {
|
||||||
|
private const float MAX = 65535f;
|
||||||
|
private const float MIN = 0f;
|
||||||
|
private const bool outputMasks = true;
|
||||||
|
static List<ImageData> Images = new();
|
||||||
|
static List<MaskData> Masks = new();
|
||||||
|
static List<SDFData> SDFs = new();
|
||||||
|
|
||||||
|
static void LoadImage(string imgPath) {
|
||||||
|
var image = new MagickImage(imgPath);
|
||||||
|
float3[,] pixels;
|
||||||
|
if(image.Channels.Count() ==4)
|
||||||
|
//skip ever 4th value if we have an alpha channel
|
||||||
|
pixels = image.GetPixels().ToArray()!
|
||||||
|
.Where((_, i) => (i + 1) % 4 != 0).ToArray()
|
||||||
|
.To2DFloat3(image.Width, image.Height);
|
||||||
|
else
|
||||||
|
pixels = image.GetPixels().ToArray()!
|
||||||
|
.To2DFloat3(image.Width, image.Height);
|
||||||
|
Images.Add(new (image, pixels, new()));
|
||||||
|
Console.WriteLine($"Loaded image: {imgPath}");
|
||||||
|
ImageData(image, image.GetPixels());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Main(string[] args) {
|
||||||
|
Console.WriteLine("Reading images...");
|
||||||
|
//foreach image in arguments load the image
|
||||||
|
LoadImage("01.png");
|
||||||
|
LoadImage("02.png");
|
||||||
|
LoadImage("03.png");
|
||||||
|
LoadImage("04.png");
|
||||||
|
LoadImage("05.png");
|
||||||
|
LoadImage("06.png");
|
||||||
|
LoadImage("07.png");
|
||||||
|
LoadImage("08.png");
|
||||||
|
|
||||||
|
//LoadImage("1.png");
|
||||||
|
//LoadImage("2.png");
|
||||||
|
//check if all the images in Images are the same resolution
|
||||||
|
if (Images.Select(img => (img.Image.Width, img.Image.Height)).Distinct().Count() > 1) {
|
||||||
|
Console.WriteLine("Error: Not all images have the same resolution.");
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Creating masks...");
|
||||||
|
//for each image pair, create a mask
|
||||||
|
for (int i = 0; i < Images.Count - 1; i++) {
|
||||||
|
var mask = GetABMask(Images[i].Pixels, Images[i + 1].Pixels, Images[i].Image.Width, Images[i].Image.Height);
|
||||||
|
Masks.Add(new(mask, Images[i], Images[i + 1], new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Edge detecting masks...");
|
||||||
|
//EdgeDetect all masks
|
||||||
|
foreach (var t in Masks) { EdgeDetect(t); }
|
||||||
|
|
||||||
|
if(outputMasks) {
|
||||||
|
Console.WriteLine("Writing masks...");
|
||||||
|
for (int i = 0; i < Masks.Count; i++) {
|
||||||
|
var mask = new MagickImage(MagickColors.Black, (uint)Masks[i].Mask.GetLength(0), (uint)Masks[i].Mask.GetLength(1));
|
||||||
|
mask.GetPixels().SetPixels(Masks[i].Mask.ToFloatArray());
|
||||||
|
mask.Write($"mask{i}.png", MagickFormat.Png24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Creating SDFs...");
|
||||||
|
for (var i = 0; i < Masks.Count; i++) {
|
||||||
|
var mask = Masks[i];
|
||||||
|
var sdf = new MagickImage(MagickColors.Black, (uint)mask.Mask.GetLength(0), (uint)mask.Mask.GetLength(1));
|
||||||
|
SDFs.Add(SDF(mask));
|
||||||
|
sdf.GetPixels().SetPixels(SDFs[i].SDF.ToFloatArray());
|
||||||
|
sdf.Write($"sdf{i}.png", MagickFormat.Png48);
|
||||||
|
}
|
||||||
|
Console.WriteLine("Done!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EdgeDetect(MaskData maskData) {
|
||||||
|
uint width = maskData.A.Image.Width;
|
||||||
|
uint height = maskData.A.Image.Height;
|
||||||
|
int iterCount = 0;
|
||||||
|
var sw = new Stopwatch();
|
||||||
|
sw.Start();
|
||||||
|
Console.WriteLine("Running edge detection...");
|
||||||
|
Parallel.For(0, width * height, (i) => {
|
||||||
|
int x = (int)(i % width);
|
||||||
|
int y = (int)(i / width);
|
||||||
|
|
||||||
|
if (!EdgeKernel(maskData.Mask, x, y, width, height)) return;
|
||||||
|
var color = maskData.Mask[x, y];
|
||||||
|
color.g = MAX;
|
||||||
|
maskData.Mask[x, y] = color;
|
||||||
|
lock(maskData.Edges) maskData.Edges.Add(new(x, y));
|
||||||
|
iterCount++;
|
||||||
|
if (iterCount % (width * height / 100) == 0) {
|
||||||
|
Console.WriteLine($"Progress: {iterCount/(width*height):P}% | {iterCount/(sw.Elapsed.TotalSeconds):N0} pixels/s");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sw.Stop();
|
||||||
|
Console.WriteLine($"Edge pixels: {maskData.Edges.Count} | {maskData.Edges.Count/sw.ElapsedMilliseconds} pixels/s\n Time: {sw.Elapsed.TotalSeconds:F4}s");
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDFData SDF(MaskData mask) {
|
||||||
|
var width = (uint)mask.Mask.GetLength(0);
|
||||||
|
var height = (uint)mask.Mask.GetLength(1);
|
||||||
|
var temp = new float3[width, height];
|
||||||
|
float AbsMax = MIN;
|
||||||
|
int iterCount = 0;
|
||||||
|
var sw = new Stopwatch();
|
||||||
|
sw.Start();
|
||||||
|
Parallel.For(0, width * height, (i) => {
|
||||||
|
//convert 1D index to 2D index
|
||||||
|
var x = (int)(i % width);
|
||||||
|
var y = (int)(i / width);
|
||||||
|
float2 p = new(x, y);
|
||||||
|
//skip all pixels we don't care about
|
||||||
|
if(mask.Mask[x, y].r == 0) {
|
||||||
|
temp[x, y] = new(MIN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float minDist = MAX; //initialize the minimum distance to the maximum possible value
|
||||||
|
|
||||||
|
//loop through all the pixels in the mask
|
||||||
|
foreach (var edge in mask.Edges) {
|
||||||
|
float dist = EuclideanDistance(p, edge);
|
||||||
|
if (dist < minDist) minDist = dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp[x, y] = new(float.Abs(minDist));
|
||||||
|
if (minDist > AbsMax) AbsMax = minDist;
|
||||||
|
iterCount++;
|
||||||
|
if (iterCount % (width * height / 100) == 0) {
|
||||||
|
Console.WriteLine($"Progress: {iterCount/(width*height):P}% | {iterCount/(sw.Elapsed.TotalSeconds):N0} pixels/s");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Console.WriteLine($"SDF Generation Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/sw.Elapsed.TotalSeconds:N0} pixels/s)");
|
||||||
|
sw.Restart();
|
||||||
|
Parallel.For(0, width * height, (i) => {
|
||||||
|
//convert 1D index to 2D index
|
||||||
|
var x = (int)(i % width);
|
||||||
|
var y = (int)(i / width);
|
||||||
|
temp[x, y] = new(Remap(temp[x, y].r, 0, AbsMax, MIN, MAX));
|
||||||
|
});
|
||||||
|
Console.WriteLine($"SDF Normalization Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/sw.Elapsed.TotalSeconds:N0} pixels/s)");
|
||||||
|
Console.WriteLine("AbsMax: " + AbsMax);
|
||||||
|
return new(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static float EuclideanDistance(float2 a, float2 b) =>
|
||||||
|
MathF.Sqrt(MathF.Pow(a.x - b.x, 2) + MathF.Pow(a.y - b.y, 2));
|
||||||
|
|
||||||
|
|
||||||
|
private static void ImageData(MagickImage image1, IPixelCollection<float> pixels1) {
|
||||||
|
Console.WriteLine(
|
||||||
|
$"""
|
||||||
|
Image file: {image1.Format.ToString()}
|
||||||
|
Resolution: {image1.Width}x{image1.Height}
|
||||||
|
Total Pixels: {pixels1.Count()} |{pixels1.Channels} channels, {image1.Depth} bits per channel
|
||||||
|
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
static float3[,] GetABMask(float3[,] A, float3[,] B, uint resX, uint resY) {
|
||||||
|
var temp = new float3[resX, resY];
|
||||||
|
Parallel.For(0, resX*resY, (i) => {
|
||||||
|
uint x = (uint)(i % resX);
|
||||||
|
uint y = (uint)(i / resX);
|
||||||
|
var pixelA = A[x, y];
|
||||||
|
var pixelB = B[x, y];
|
||||||
|
float lumaA = (pixelA.r+pixelA.g+pixelA.b)/3;
|
||||||
|
float lumaB = (pixelB.r+pixelB.g+pixelB.b)/3;
|
||||||
|
float resultPixel = lumaB > lumaA ? MAX : MIN;
|
||||||
|
temp[x, y] = new(resultPixel, 0, 0);
|
||||||
|
});
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool EdgeKernel(float3[,] mask, int x, int y, uint width, uint height) {
|
||||||
|
//if we are already empty, return false
|
||||||
|
if (mask[x, y].r == 0) return false;
|
||||||
|
//if we are on the edge of the image, return false
|
||||||
|
if (x == 0 || y == 0 || x == width - 1 || y == height - 1) return false;
|
||||||
|
//check the 3x3 kernel
|
||||||
|
for (int xi = x - 1; xi <= x + 1; xi++) {
|
||||||
|
for (int yi = y - 1; yi <= y + 1; yi++) {
|
||||||
|
if (xi < 0 || xi >= width || yi < 0 || yi >= height)
|
||||||
|
continue; //skip out of bounds pixels
|
||||||
|
if (mask[xi, yi].r == 0)
|
||||||
|
return true; //if we find a black pixel, return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//if we didn't find any black pixels, return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static T Lerp<T>(T a, T b, float t)
|
||||||
|
where T : INumber<T>, IMultiplyOperators<T, float, T>, IAdditionOperators<T, T, T>
|
||||||
|
=> a * (1 - t) + b * t;
|
||||||
|
|
||||||
|
static T Remap<T>(T value, T min, T max, T newMin, T newMax)
|
||||||
|
where T : INumber<T>, ISubtractionOperators<T, T, T>, IMultiplyOperators<T, T, T>, IAdditionOperators<T, T, T>
|
||||||
|
=> (value - min) / (max - min) * (newMax - newMin) + newMin;
|
||||||
|
}
|
||||||
26
SDFMapCreator/SDFMapCreator.csproj
Normal file
26
SDFMapCreator/SDFMapCreator.csproj
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Magick.NET-Q16-HDRI-OpenMP-x64" Version="14.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="1.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="2.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="TestPattern.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
BIN
SDFMapCreator/TestPattern.png
Normal file
BIN
SDFMapCreator/TestPattern.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
7
global.json
Normal file
7
global.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"rollForward": "latestMinor",
|
||||||
|
"allowPrerelease": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user