Compare commits

...

2 Commits

Author SHA1 Message Date
Samuele Lorefice
809068e270 Gradient + Transitions 2025-03-26 06:42:46 +01:00
Samuele Lorefice
e3dbb82587 Removed extra reference in record 2025-03-26 06:38:43 +01:00

View File

@@ -20,7 +20,7 @@ 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 A, ImageData B, List<float2> Edges);
public record MaskData(float3[,] Mask, ImageData A, List<float2> Edges);
public record SDFData(float3[,] SDF);
@@ -51,10 +51,13 @@ public static class ArrayExt {
public class Program {
private const float MAX = 65535f;
private const float MIN = 0f;
private const bool outputMasks = true;
private const bool outputMasks = false;
private const bool outputSDFs = false;
private const bool outputGradients = false;
static List<ImageData> Images = new();
static List<MaskData> Masks = new();
static List<SDFData> SDFs = new();
static List<float3[,]> Gradients = new();
static void LoadImage(string imgPath) {
var image = new MagickImage(imgPath);
@@ -75,6 +78,7 @@ public class Program {
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");
@@ -93,10 +97,11 @@ public class Program {
}
Console.WriteLine("Creating masks...");
Masks.Add(new (SelfMask(Images[0].Pixels, Images[0].Image.Width, Images[0].Image.Height), Images[0], new()));
//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()));
for (int i = 1; i < Images.Count; i++) {
var mask = GetABMask(Images[i-1].Pixels, Images[i].Pixels, Images[i].Image.Width, Images[i].Image.Height);
Masks.Add(new(mask, Images[i], new()));
}
Console.WriteLine("Edge detecting masks...");
@@ -115,11 +120,44 @@ public class Program {
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));
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);
}
Console.WriteLine("Creating gradients...");
for (var i = 0; i < Masks.Count - 1; i++) {
var gradientData = Gradient(Masks[i], SDFs[i], SDFs[i + 1]);
Gradients.Add(gradientData);
if(!outputGradients) continue;
var gradient = new MagickImage(MagickColors.Black, (uint)Masks[i].Mask.GetLength(0), (uint)Masks[i].Mask.GetLength(1));
gradient.GetPixels().SetPixels(gradientData.ToFloatArray());
gradient.Write($"gradient{i}.png", MagickFormat.Png24);
}
Console.WriteLine("Preparing transitions...");
//for each gradient read the corresponding mask
//for each pixel in the mask, lerp the pixel value with the gradient value
//write the result to a new image
int width = Masks[0].Mask.GetLength(0);
int height = Masks[0].Mask.GetLength(1);
for (var i = 0; i < Gradients.Count; i++) {
var mask = Masks[i + 1];
var gradient = Gradients[i];
var transition = new float3[width, height];
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].r == 0) continue;
transition[x, y] = new(Lerp(Images[i].Pixels[x,y].r, MAX, gradient[x,y].r));
}
}
var transitionImage = new MagickImage(MagickColors.Black, (uint)mask.Mask.GetLength(0), (uint)mask.Mask.GetLength(1));
transitionImage.GetPixels().SetPixels(transition.ToFloatArray());
transitionImage.Write($"transition{i}.png", MagickFormat.Png24);
}
Console.WriteLine("Done!");
}
@@ -148,6 +186,32 @@ public class Program {
Console.WriteLine($"Edge pixels: {maskData.Edges.Count} | {maskData.Edges.Count/sw.ElapsedMilliseconds} pixels/s\n Time: {sw.Elapsed.TotalSeconds:F4}s");
}
static float3[,] Gradient(MaskData mask, SDFData sdfA, SDFData sdfB) {
uint width = mask.A.Image.Width;
uint height = mask.A.Image.Height;
int iterCount = 0;
var sw = new Stopwatch();
sw.Start();
float3[,] temp = new float3[width, height];
Console.WriteLine("Running edge detection...");
Parallel.For(0, width * height, (i) => {
int x = (int)(i % width);
int y = (int)(i / width);
var a = (sdfA.SDF[x,y].r + sdfB.SDF[x,y].g + sdfB.SDF[x,y].b )/ 3;
var b = (sdfB.SDF[x,y].r + sdfB.SDF[x,y].g + sdfB.SDF[x,y].b )/ 3;
var gradient = a / (a + b);
temp[x, y] = new(Remap(gradient, 0, 1, MIN, MAX));
});
Console.WriteLine($"Gradient Time: {sw.Elapsed.TotalSeconds:N4}s ({iterCount/sw.Elapsed.TotalSeconds:N0} pixels/s)");
var min = temp.Cast<float3>().Min(x => x.r);
var max = temp.Cast<float3>().Max(x => x.r);
Console.WriteLine($"Min: {min} | Max: {max}");
return temp;
}
static SDFData SDF(MaskData mask) {
var width = (uint)mask.Mask.GetLength(0);
var height = (uint)mask.Mask.GetLength(1);
@@ -161,11 +225,6 @@ public class Program {
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
@@ -226,6 +285,19 @@ public class Program {
return temp;
}
static float3[,] SelfMask(float3[,] A, 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];
float lumaA = (pixelA.r+pixelA.g+pixelA.b)/3;
float resultPixel = lumaA > 0 ? 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;