From 7c484a9af3fdcd0959721492cd6cf4ba020bfb38 Mon Sep 17 00:00:00 2001 From: mm00 Date: Tue, 1 Apr 2025 18:48:58 +0200 Subject: [PATCH] completed refactoring, added 32x32 test images --- SDFMapCreator/ImageUtil.cs | 51 ++++++++++++++--------------- SDFMapCreator/Program.cs | 36 +++++++++++++++----- SDFMapCreator/SDFMapCreator.csproj | 12 +++++++ SDFMapCreator/spherecut32.png | Bin 0 -> 680 bytes SDFMapCreator/sphereempty.png | Bin 0 -> 11030 bytes SDFMapCreator/spherefull32.png | Bin 0 -> 693 bytes SDFMapCreator/spherehalf32.png | Bin 0 -> 540 bytes 7 files changed, 64 insertions(+), 35 deletions(-) create mode 100644 SDFMapCreator/spherecut32.png create mode 100644 SDFMapCreator/sphereempty.png create mode 100644 SDFMapCreator/spherefull32.png create mode 100644 SDFMapCreator/spherehalf32.png diff --git a/SDFMapCreator/ImageUtil.cs b/SDFMapCreator/ImageUtil.cs index c9c218d..75839e4 100644 --- a/SDFMapCreator/ImageUtil.cs +++ b/SDFMapCreator/ImageUtil.cs @@ -7,10 +7,6 @@ namespace SDFMapCreator; public static class ImageUtil { public static T[,] LoadImage(string path) where T : struct, IEquatable { var image = SixLabors.ImageSharp.Image.Load(path); - using var image16 = image as Image ?? throw new NotSupportedException($"Image format not supported"); - int width = image.Width; - int height = image.Height; - //result = new T[image.Width, image.Height]; return image switch { Image img => img.ProcessPixelsRgba64(), Image img => img.ProcessPixelsRgb24(), @@ -21,6 +17,7 @@ public static class ImageUtil { } static T[,] ProcessPixelsRgba64(this Image image) where T : struct, IEquatable { + var max = 65535f; int width = image.Width; int height = image.Height; var result = new T[image.Width, image.Height]; @@ -31,16 +28,16 @@ public static class ImageUtil { for (int x = 0; x < width; x++) { switch (result) { case float[,] f: - f[x, y] = span[x].R; + f[x, y] = span[x].R / max; break; case Vector2[,] f: - f[x, y] = new(span[x].R, span[x].G); + f[x, y] = new(span[x].R / max, span[x].G / max); break; case Vector3[,] f: - f[x, y] = new(span[x].R, span[x].G, span[x].B); + f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max); break; case Vector4[,] f: - f[x, y] = new(span[x].R, span[x].G, span[x].B, 1f); + f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, span[x].A / max); break; } } @@ -50,6 +47,7 @@ public static class ImageUtil { } static T[,] ProcessPixelsRgb24(this Image image) where T : struct, IEquatable { + var max = 255f; int width = image.Width; int height = image.Height; var result = new T[image.Width, image.Height]; @@ -60,16 +58,16 @@ public static class ImageUtil { for (int x = 0; x < width; x++) { switch (result) { case float[,] f: - f[x, y] = span[x].R; + f[x, y] = span[x].R / max; break; case Vector2[,] f: - f[x, y] = new(span[x].R, span[x].G); + f[x, y] = new(span[x].R / max, span[x].G / max); break; case Vector3[,] f: - f[x, y] = new(span[x].R, span[x].G, span[x].B); + f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max); break; case Vector4[,] f: - f[x, y] = new(span[x].R, span[x].G, span[x].B, 1f); + f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, 1f); break; } } @@ -79,6 +77,7 @@ public static class ImageUtil { } static T[,] ProcessPixelsRgba32(this Image image) where T : struct, IEquatable { + var max = 255f; int width = image.Width; int height = image.Height; var result = new T[image.Width, image.Height]; @@ -89,16 +88,16 @@ public static class ImageUtil { for (int x = 0; x < width; x++) { switch (result) { case float[,] f: - f[x, y] = span[x].R; + f[x, y] = span[x].R / max; break; case Vector2[,] f: - f[x, y] = new(span[x].R, span[x].G); + f[x, y] = new(span[x].R / max, span[x].G / max); break; case Vector3[,] f: - f[x, y] = new(span[x].R, span[x].G, span[x].B); + f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max); break; case Vector4[,] f: - f[x, y] = new(span[x].R, span[x].G, span[x].B, 1f); + f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, span[x].A / max); break; } } @@ -108,27 +107,27 @@ public static class ImageUtil { } static T[,] ProcessPixelsRgb48(this Image image) where T : struct, IEquatable { - using var image16 = image as Image ?? throw new NotSupportedException($"Image format not supported"); + var max = 65535f; int width = image.Width; int height = image.Height; var result = new T[image.Width, image.Height]; - image16.ProcessPixelRows(accessor => { + image.ProcessPixelRows(accessor => { //we use Y as the row index and X as the column index for (int y = 0; y < height; y++) { var span = accessor.GetRowSpan(y); for (int x = 0; x < width; x++) { switch (result) { case float[,] f: - f[x, y] = span[x].R; + f[x, y] = span[x].R / max; break; case Vector2[,] f: - f[x, y] = new(span[x].R, span[x].G); + f[x, y] = new(span[x].R / max, span[x].G / max); break; case Vector3[,] f: - f[x, y] = new(span[x].R, span[x].G, span[x].B); + f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max); break; case Vector4[,] f: - f[x, y] = new(span[x].R, span[x].G, span[x].B, 1f); + f[x, y] = new(span[x].R / max, span[x].G / max, span[x].B / max, 1f); break; } } @@ -155,16 +154,16 @@ public static class ImageUtil { for (int x = 0; x < width; x++) { switch (array) { case float[,] f: - span[x] = new Rgb48((ushort)f[x, y], (ushort)f[x, y], (ushort)f[x,y]); + span[x] = new Rgb48((ushort)(f[x, y] * 65535), (ushort)(f[x, y] * 65535), (ushort)(f[x,y] * 65535)); break; case Vector2[,] f: - span[x] = new Rgb48((ushort)f[x,y].X, (ushort)f[x,y].Y, 0); + span[x] = new Rgb48((ushort)(f[x,y].X * 65535), (ushort)(f[x,y].Y * 65535), 0); break; case Vector3[,] f: - span[x] = new Rgb48((ushort)f[x,y].X, (ushort)f[x,y].Y, (ushort)f[x,y].Z); + span[x] = new Rgb48((ushort)(f[x,y].X * 65535), (ushort)(f[x,y].Y * 65535), (ushort)(f[x,y].Z * 65535)); break; case Vector4[,] f: - span[x] = new Rgb48((ushort)f[x,y].X, (ushort)f[x,y].Y, (ushort)f[x,y].Z); + span[x] = new Rgb48((ushort)(f[x,y].X * 65535), (ushort)(f[x,y].Y * 65535), (ushort)(f[x,y].Z * 65535)); break; } } diff --git a/SDFMapCreator/Program.cs b/SDFMapCreator/Program.cs index 9f4680b..31c6890 100644 --- a/SDFMapCreator/Program.cs +++ b/SDFMapCreator/Program.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; using SDFMapCreator; public class Program { - private const float MAX = 65535f; + private const float MAX = 1f; private const float MIN = 0f; private static readonly int MAX_THREADS = Environment.ProcessorCount - 2; private const bool outputMasks = true; @@ -24,10 +24,23 @@ public class Program { var imagesPath = "images"; + for (int i = 0; i < 8; i++) { var pixels = ImageUtil.LoadImage($"./{imagesPath}{Path.DirectorySeparatorChar}{i + 1:00}.png"); Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1))); } + + /* + var pixels = ImageUtil.LoadImage($"./sphereempty.png"); + Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1))); + pixels = ImageUtil.LoadImage($"./spherehalf.png"); + Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1))); + pixels = ImageUtil.LoadImage($"./spherecut.png"); + Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1))); + pixels = ImageUtil.LoadImage($"./spherefull.png"); + Images.Add(new(pixels, pixels.GetLength(0), pixels.GetLength(1))); + */ + //check if all the images in Images are the same resolution if (Images.Select(img => (img.Width, img.Height)).Distinct().Count() > 1) { @@ -61,7 +74,7 @@ public class Program { for (var i = 0; i < ImageMasks.Count; i++) { var mask = ImageMasks[i]; SDFs.Add(SDF(mask)); - if (outputSDFs) mask.Mask.SaveImage($"sdf{i}.png"); + if (outputSDFs) SDFs[i].SDF.SaveImage($"sdf{i}.png"); } Console.WriteLine("Creating gradients..."); @@ -75,13 +88,14 @@ public class Program { // generate final image var finalImage = new Vector3[width, height]; var currStep = 0f; - var stepIncrement = MAX / (Gradients.Count + 1); + var stepIncrement = MAX / (Gradients.Count); for (var i = 0; i < Gradients.Count; i++) { var mask = ImageMasks[i + 1]; var gradient = Gradients[i]; + Console.WriteLine($"Applying gradient {i}..., {currStep} -> {currStep + stepIncrement}"); 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; + if (mask.Mask[x, y].X == 0) continue; finalImage[x, y] = new(Remap(gradient[x, y].X, MAX, MIN, currStep, currStep + stepIncrement)); } } @@ -116,7 +130,7 @@ public class Program { }); sw.Stop(); Console.WriteLine( - $"\nEdge pixels: {maskData.Edges.Count} | {maskData.Edges.Count / sw.ElapsedMilliseconds} pixels/s\n Time: {sw.Elapsed.TotalSeconds:F4}s"); + $"\nEdge pixels: {maskData.Edges.Count} | {maskData.Edges.Count / (sw.ElapsedMilliseconds+1)} pixels/s\n Time: {sw.Elapsed.TotalSeconds:F4}s"); } static Vector3[,] Gradient(TransitionMaskData mask, SDFData sdfA, SDFData sdfB) { @@ -167,16 +181,20 @@ public class Program { //convert 1D index to 2D index var x = (int)(i % width); var y = (int)(i / width); - Vector2 p = new(x, y); + Vector2 p = new(x/(float)width, y/(float)height); //get the pixel position as a Vector2 - float minDist = MAX; //initialize the minimum distance to the maximum possible value + float minDist = MAX + 1; //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 = Vector2.DistanceSquared(p, edge); - if (dist < minDist) minDist = mask.Mask[x, y].X == 0 ? dist : dist; + Vector2 edgeNorm = new(edge.X / (float)width, edge.Y / (float)height); + float dist = Vector2.DistanceSquared(p, edgeNorm); + if (dist < minDist) minDist = dist; } + if (minDist > MAX) + minDist = MAX; + temp[x, y] = new(minDist); if (minDist > AbsMax) AbsMax = minDist; iterCount++; diff --git a/SDFMapCreator/SDFMapCreator.csproj b/SDFMapCreator/SDFMapCreator.csproj index e0fb3ee..8872035 100644 --- a/SDFMapCreator/SDFMapCreator.csproj +++ b/SDFMapCreator/SDFMapCreator.csproj @@ -55,6 +55,18 @@ Always + + Always + + + Always + + + Always + + + Always + diff --git a/SDFMapCreator/spherecut32.png b/SDFMapCreator/spherecut32.png new file mode 100644 index 0000000000000000000000000000000000000000..8f688c9a7d0485c2b485e8732b8b7290f4b4d907 GIT binary patch literal 680 zcmV;Z0$2TsP)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rk0~`?&6{tNgVgLXE z~28+d_uIrP@#Imezw<`z&E_#om zD9vW`dcEHNH=oZHMZwiv1VN})tGC+?svk_#OeT{!#wZqxEfx!`d~!SJ7n6NY&OHlLq?+!W}HmZJRA;~KFG5Cn>XG4ejks=So#IoY&L_z z0An7M`}6NHEtks}^I$r~cDs#N&MciyW8E`Jl6Jctjxso%POR?&RaI@<#)4N`triwh zaw$Q1n%6_kG+kWR$RgxseGuL$;huLK^nSQ@d`476T`~DZ|gDlIXQmNPL zt=H?vW{%@5m&;0}f~&vt2a9*h7ohxFMF0Q*4rN$LW=%~1DgXcg2mk;800000(o>TF O0000M_G9*b-#?0JXo>C-5hB9Y*WF}?4Bq1b8 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