This commit is contained in:
Samuele Lorefice
2025-03-28 20:51:38 +01:00
parent 571bd81c0d
commit f3f01a2f85

View File

@@ -24,7 +24,9 @@ public struct float4(float r, float g, float b, float a) {
}*/
public record ImageData(MagickImage Image, float3[,] Pixels, List<float2> Edges);
public record MaskData(float3[,] Mask, ImageData Image, List<float2> Edges);
public record TransitionMaskData(float3[,] Mask, ImageData ImageA, ImageData ImageB);
public record SDFData(float3[,] SDF);
@@ -32,21 +34,21 @@ 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++) {
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]);
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].X;
result[x*array.GetLength(1)*3 + y*3+1] = array[x, y].Y;
result[x*array.GetLength(1)*3 + y*3+2] = array[x, y].Z;
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].X;
result[x * array.GetLength(1) * 3 + y * 3 + 1] = array[x, y].Y;
result[x * array.GetLength(1) * 3 + y * 3 + 2] = array[x, y].Z;
}
}
return result;
@@ -62,17 +64,17 @@ public class Program {
private const bool outputGradients = true;
static readonly ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = MAX_THREADS };
static List<ImageData> Images = new();
static List<TransitionMaskData> TransitionMasks = new();
static List<MaskData> ImageMasks = new();
static List<TransitionMaskData> TransitionMasks = new();
static List<SDFData> SDFs = new();
static List<float3[,]> Gradients = new();
static void ConsoleUpdateLine(string s) => Console.Write("\r"+s);
static void ConsoleUpdateLine(string s) => Console.Write("\r" + s);
static void LoadImage(string imgPath) {
var image = new MagickImage(imgPath);
float3[,] pixels;
if(image.Channels.Count() ==4)
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()
@@ -80,7 +82,7 @@ public class Program {
else
pixels = image.GetPixels().ToArray()!
.To2DFloat3(image.Width, image.Height);
Images.Add(new (image, pixels, new()));
Images.Add(new(image, pixels, new()));
Console.WriteLine($"Loaded image: {imgPath}");
ImageData(image, image.GetPixels());
}
@@ -93,7 +95,7 @@ public class Program {
for (int i = 0; i < 8; i++)
LoadImage($"./{imagesPath}{Path.DirectorySeparatorChar}{i+1:00}.png");
LoadImage($"./{imagesPath}{Path.DirectorySeparatorChar}{i + 1:00}.png");
//LoadImage("spherecut.png");
@@ -112,10 +114,9 @@ public class Program {
var width = Images[0].Image.Width;
var height = Images[0].Image.Height;
for (int i = 0; i < Images.Count; i++) {
ImageMasks.Add(new (SelfMask(Images[i].Pixels, width, height), Images[i], new()));
ImageMasks.Add(new(SelfMask(Images[i].Pixels, width, height), Images[i], new()));
if (i < Images.Count - 1)
{
if (i < Images.Count - 1) {
Console.WriteLine($"Creating mask {i}...");
var mask = GetABMask(Images[i].Pixels, Images[i + 1].Pixels, width, height);
TransitionMasks.Add(new(mask, Images[i], Images[i + 1]));
@@ -124,12 +125,15 @@ public class Program {
Console.WriteLine("Edge detecting masks...");
//EdgeDetect all masks
foreach (var t in ImageMasks) { EdgeDetect(t); }
foreach (var t in ImageMasks) {
EdgeDetect(t);
}
if(outputMasks) {
if (outputMasks) {
Console.WriteLine("Writing masks...");
for (int i = 0; i < TransitionMasks.Count; i++) {
var mask = new MagickImage(MagickColors.Black, (uint)TransitionMasks[i].Mask.GetLength(0), (uint)TransitionMasks[i].Mask.GetLength(1));
var mask = new MagickImage(MagickColors.Black, (uint)TransitionMasks[i].Mask.GetLength(0),
(uint)TransitionMasks[i].Mask.GetLength(1));
mask.GetPixels().SetPixels(TransitionMasks[i].Mask.ToFloatArray());
mask.Write($"mask{i}.png", MagickFormat.Png24);
}
@@ -139,7 +143,7 @@ public class Program {
for (var i = 0; i < ImageMasks.Count; i++) {
var mask = ImageMasks[i];
SDFs.Add(SDF(mask));
if(!outputSDFs) continue;
if (!outputSDFs) continue;
var sdf = new MagickImage(MagickColors.Black, (uint)mask.Mask.GetLength(0), (uint)mask.Mask.GetLength(1));
sdf.GetPixels().SetPixels(SDFs[i].SDF.ToFloatArray());
sdf.Write($"sdf{i}.png", MagickFormat.Png48);
@@ -150,8 +154,9 @@ public class Program {
Console.WriteLine($"Generating gradient {i}...");
var gradientData = Gradient(TransitionMasks[i], SDFs[i], SDFs[i + 1]);
Gradients.Add(gradientData);
if(!outputGradients) continue;
var gradient = new MagickImage(MagickColors.Black, (uint)TransitionMasks[i].Mask.GetLength(0), (uint)TransitionMasks[i].Mask.GetLength(1));
if (!outputGradients) continue;
var gradient = new MagickImage(MagickColors.Black, (uint)TransitionMasks[i].Mask.GetLength(0),
(uint)TransitionMasks[i].Mask.GetLength(1));
gradient.GetPixels().SetPixels(gradientData.ToFloatArray());
gradient.Write($"gradient{i}.png", MagickFormat.Png24);
}
@@ -166,7 +171,7 @@ public class Program {
for (var x = 0; x < mask.Mask.GetLength(0); x++) {
for (var y = 0; y < mask.Mask.GetLength(1); y++) {
if (mask.Mask[x, y].X == 0 || mask.Mask[x, y].Y > 0) continue;
finalImage[x, y] = new(Remap(gradient[x,y].X, MAX, MIN, currStep, currStep + stepIncrement));
finalImage[x, y] = new(Remap(gradient[x, y].X, MAX, MIN, currStep, currStep + stepIncrement));
}
}
currStep += stepIncrement;
@@ -186,7 +191,8 @@ public class Program {
var sw = new Stopwatch();
sw.Start();
Console.WriteLine("Running edge detection...");
Parallel.For(0, width * height, parallelOptions, (i) => {
Parallel.For(0, width * height, parallelOptions, (i) =>
{
int x = (int)(i % width);
int y = (int)(i / width);
@@ -194,14 +200,16 @@ public class Program {
var color = maskData.Mask[x, y];
color.Y = MAX;
maskData.Mask[x, y] = color;
lock(maskData.Edges) maskData.Edges.Add(new(x, y));
lock (maskData.Edges) maskData.Edges.Add(new(x, y));
iterCount++;
if (iterCount % (width * height / 100) == 0) {
ConsoleUpdateLine($"Progress: {iterCount/(float)(width*height):P} | {iterCount/(sw.Elapsed.TotalSeconds):N0} pixels/s");
ConsoleUpdateLine(
$"Progress: {iterCount / (float)(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");
Console.WriteLine(
$"\nEdge pixels: {maskData.Edges.Count} | {maskData.Edges.Count / sw.ElapsedMilliseconds} pixels/s\n Time: {sw.Elapsed.TotalSeconds:F4}s");
}
static float3[,] Gradient(TransitionMaskData mask, SDFData sdfA, SDFData sdfB) {
@@ -216,11 +224,12 @@ public class Program {
var max = MIN;
Console.WriteLine("Running gradient generation...");
Parallel.For(0, width * height, parallelOptions, (i) => {
Parallel.For(0, width * height, parallelOptions, (i) =>
{
int x = (int)(i % width);
int y = (int)(i / width);
if(mask.Mask[x,y].X == 0 || mask.Mask[x,y].Y > 0) return;
if (mask.Mask[x, y].X == 0 || mask.Mask[x, y].Y > 0) return;
var a = sdfA.SDF[x, y].X;
var b = sdfB.SDF[x, y].X;
@@ -232,7 +241,8 @@ public class Program {
if (gradient > max) max = gradient;
});
Console.WriteLine($"Gradient Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/(float)sw.Elapsed.TotalSeconds:N0} pixels/s)");
Console.WriteLine(
$"Gradient Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount / (float)sw.Elapsed.TotalSeconds:N0} pixels/s)");
Console.WriteLine($"Min: {min} | Max: {max}");
return temp;
}
@@ -245,7 +255,8 @@ public class Program {
int iterCount = 0;
var sw = new Stopwatch();
sw.Start();
Parallel.For(0, width * height, parallelOptions, (i) => {
Parallel.For(0, width * height, parallelOptions, (i) =>
{
//convert 1D index to 2D index
var x = (int)(i % width);
var y = (int)(i / width);
@@ -263,13 +274,16 @@ public class Program {
if (minDist > AbsMax) AbsMax = minDist;
iterCount++;
if (iterCount % (width * height / 100) == 0) {
ConsoleUpdateLine($"Progress: {iterCount/(width*height):P}% | {iterCount/(sw.Elapsed.TotalSeconds):N0} pixels/s");
ConsoleUpdateLine(
$"Progress: {iterCount / (float)(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)");
Console.WriteLine(
$"\nSDF Generation Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount / sw.Elapsed.TotalSeconds:N0} pixels/s)");
sw.Restart();
Parallel.For(0, width * height, parallelOptions, (i) => {
Parallel.For(0, width * height, parallelOptions, (i) =>
{
//convert 1D index to 2D index
var x = (int)(i % width);
var y = (int)(i / width);
@@ -277,7 +291,8 @@ public class Program {
});
sw.Stop();
Console.WriteLine($"SDF Normalization Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/sw.Elapsed.TotalSeconds:N0} pixels/s)");
Console.WriteLine(
$"SDF Normalization Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount / sw.Elapsed.TotalSeconds:N0} pixels/s)");
Console.WriteLine("AbsMax: " + AbsMax);
return new(temp);
}
@@ -298,13 +313,14 @@ public class Program {
static float3[,] GetABMask(float3[,] A, float3[,] B, uint resX, uint resY) {
var temp = new float3[resX, resY];
Parallel.For(0, resX*resY, parallelOptions, (i) => {
Parallel.For(0, resX * resY, parallelOptions, (i) =>
{
uint x = (uint)(i % resX);
uint y = (uint)(i / resX);
var pixelA = A[x, y];
var pixelB = B[x, y];
float lumaA = (pixelA.X+pixelA.Y+pixelA.Z)/3;
float lumaB = (pixelB.X+pixelB.Y+pixelB.Z)/3;
float lumaA = (pixelA.X + pixelA.Y + pixelA.Z) / 3;
float lumaB = (pixelB.X + pixelB.Y + pixelB.Z) / 3;
float resultPixel = lumaB > lumaA ? MAX : MIN;
temp[x, y] = new(resultPixel, 0, 0);
});
@@ -313,11 +329,12 @@ public class Program {
static float3[,] SelfMask(float3[,] A, uint resX, uint resY) {
var temp = new float3[resX, resY];
Parallel.For(0, resX*resY, parallelOptions, (i) => {
Parallel.For(0, resX * resY, parallelOptions, (i) =>
{
uint x = (uint)(i % resX);
uint y = (uint)(i / resX);
var pixelA = A[x, y];
float lumaA = (pixelA.X+pixelA.Y+pixelA.Z)/3;
float lumaA = (pixelA.X + pixelA.Y + pixelA.Z) / 3;
float resultPixel = lumaA > 0 ? MAX : MIN;
temp[x, y] = new(resultPixel, 0, 0);
});