diff --git a/DigitalImageProcessing/Alograms.cs b/DigitalImageProcessing/Alograms.cs index f63ad1d..0eec32d 100644 --- a/DigitalImageProcessing/Alograms.cs +++ b/DigitalImageProcessing/Alograms.cs @@ -61,9 +61,10 @@ namespace DigitalImageProcessing fequency.DrawTo(outputDirectoryPath + @"fequency.png"); angle.DrawTo(outputDirectoryPath + @"angle.png"); } - public static void Noises(string filepath) + + public static SKBitmap GetDefaultBitmap(SKSizeI size) { - using SKBitmap bitmap = new(1024, 1024);//SKBitmap.Decode(filepath); + using SKBitmap bitmap = new(size.Width, size.Height);//SKBitmap.Decode(filepath); using SKCanvas canvas = new SKCanvas(bitmap); canvas.Clear(SKColors.Black); const int gap = 128; @@ -81,9 +82,16 @@ namespace DigitalImageProcessing using FileStream stream = new(@"C:\Users\Surface\Downloads\" + @"original.png", FileMode.Create, FileAccess.Write); bitmap.Encode(stream, SKEncodedImageFormat.Png, 100); - using SKBitmap noiseBitmap = new(bitmap.Width, bitmap.Height); + return bitmap.Copy(); + } + public static SKBitmap Noises(SKBitmap input) + { + using SKBitmap bitmap = input.Copy(); + + SKBitmap noiseBitmap = new(bitmap.Width, bitmap.Height); using SKCanvas noiseCanvas = new(noiseBitmap); - Func noiseFunc = (v) => ApplyGaussianToRandomValue(new(), v, 10f);//NoiseFuncs.Gaussian(v, new Random(), 20f); + Func noiseFunc = (v) => ApplyGaussianToRandomValue(new(), v, 10f); + Func noiseFunc2 = (v) => NoiseFuncs.Rayleigh(v, new(), 0.01f); int[] changes = new int[256]; //NoiseFuncs.GetFunc(NoiseType.Gaussian, new Random()); for (int x = 0; x < bitmap.Width; x++) @@ -97,40 +105,41 @@ namespace DigitalImageProcessing noised)); changes[noised]++; } - using FileStream fs = new(@"C:\Users\Surface\Downloads\" + @"noise.png", FileMode.Create, FileAccess.Write); - noiseBitmap.Encode(fs, SKEncodedImageFormat.Png, 100); - - using SKBitmap c = new(256, 256); - using SKCanvas cc = new(c); - cc.Clear(SKColors.Black); - int max = changes[1..].Max() + 1; - for (int i = 0; i < 255; i++) - { - cc.DrawLine(i, 256, i, 256 - (changes[i] * 256 / max), new SKPaint() - { - Color = i is 0 or 127 or 255 ? SKColors.Red : SKColors.White, - StrokeWidth = 1, - }); - //cc.DrawLine(i, 256, i, 256 - (NoiseFuncs.gaussianKerel[i] * 256), new SKPaint() - //{ - // Color = SKColors.LightBlue.WithAlpha(127), - // StrokeWidth = 1, - //}); - } - using FileStream fsc = new(@"C:\Users\Surface\Downloads\" + @"changes.png", FileMode.Create, FileAccess.Write); - c.Encode(fsc, SKEncodedImageFormat.Png, 100); + return noiseBitmap; } + + public static SKBitmap Filt(SKBitmap input) + { + using SKBitmap bitmap = input.Copy(); + + SKBitmap filtedBitmap = new(bitmap.Width, bitmap.Height); + using SKCanvas filtedCanvas = new(filtedBitmap); + byte filterFunc(SKPointI p) => + FilterFuncs.Mean(SKRectI.Create(p, new SKSizeI(4, 4)), + (int x, int y) => Sampler.NearestNeighbor(bitmap, x, y, + (SKColor c) => c.Red) + ); + + for (int x = 0; x < bitmap.Width; x++) + for (int y = 0; y < bitmap.Height; y++) + { + byte gray = filterFunc(new(x, y)); + SKColor c = new(gray, gray, gray); + filtedBitmap.SetPixel(x, y, c); + + } + + return filtedBitmap; + } + public static byte ApplyGaussianToRandomValue(Random random, float mean, float sigma) { - // Generate a random value using the Box-Muller transform - double u1 = 1.0 - random.NextDouble(); // Uniform(0,1] random doubles + double u1 = 1.0 - random.NextDouble(); double u2 = 1.0 - random.NextDouble(); double standardNormal = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); - // Scale the standard normal value to the desired mean and standard deviation - double gaussianValue = mean + sigma * standardNormal; + double gaussianValue = mean + (sigma * standardNormal); - // Clamp the result to the byte range [0, 255] return (byte)Math.Clamp((int)gaussianValue, 0, 255); } } @@ -175,11 +184,11 @@ namespace DigitalImageProcessing //int randomIndex = random.Next(0, 256); float r = random.NextSingle(); - float f = value* GaussianKernel(r * 255, value, sigma); + float f = value * GaussianKernel(r * 255, value, sigma); int newValue = (int)f;//(int)(value + (32 * (f))); return (byte)Math.Clamp(newValue, 0, 255); } - private static float GaussianKernel(float value, float mu, float sigma) + public static float GaussianKernel(float value, float mu, float sigma) { float exp = -(value - mu) * (value - mu) / (2 * sigma * sigma); float peak = 1 / (sigma * float.Sqrt(2 * float.Pi)); @@ -204,26 +213,89 @@ namespace DigitalImageProcessing return (byte)newValue; } } - //public static class FilterFuncs - //{ - // public static Func GetFunc() - // { + public static class FilterFuncs + { + //public static Func GetFunc() + //{ - // } - // private static byte Mean(SKBitmap bitmap, SKRectI size, Func channel) - // { - // for(int x=size.Left;x<=size.Right;x++) - // for(int y=size.Top; y <= size.Bottom; y++) - // { - // byte value = channel(bitmap.GetPixel(x, y)); - - // } - // } - // private static byte Median(SKBitmap bitmap, SKRectI size) - // { - // } - - //} + //} + public static byte Mean(SKRectI size, Func channel) + { + double sum = 0; + for (int x = size.Left; x <= size.Right; x++) + for (int y = size.Top; y <= size.Bottom; y++) + { + byte value = channel(x, y); + sum += value; + } + byte v = (byte)(sum / ((size.Width + 1) * (size.Height + 1))); + return v; + } + public static byte Median(SKRectI size, Func channel) + { + byte[] bs = new byte[(size.Width + 1) * (size.Height + 1)]; + for (int x = size.Left; x <= size.Right; x++) + for (int y = size.Top; y <= size.Bottom; y++) + { + byte value = channel(x, y); + bs[((y - size.Top) * size.Width) + (x - size.Left)] = value; + } + Array.Sort(bs); + if ((bs.Length & 1) == 0) + return bs[bs.Length / 2]; + return (byte)((bs[bs.Length / 2] + bs[(bs.Length / 2) + 1]) / 2); + } + public static byte Gaussian(SKRectI size, Func channel) + { + double sum = 0; + double weightSum = 0; + int centerX = size.Left + size.Width / 2; + int centerY = size.Top + size.Height / 2; + float sigma = size.Width / 6f; // Example sigma value + for (int x = size.Left; x <= size.Right; x++) + for (int y = size.Top; y <= size.Bottom; y++) + { + byte value = channel(x, y); + float weight = NoiseFuncs.GaussianKernel(float.Sqrt((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY)), 0, sigma); + sum += value * weight; + weightSum += weight; + } + byte v = (byte)(sum / weightSum); + return v; + } + } + public static class Sampler + { + public static byte NearestNeighbor(SKBitmap bitmap, float x, float y, Func channel) + { + int ix = (int)MathF.Round(x); + int iy = (int)MathF.Round(y); + ix = Math.Clamp(ix, 0, bitmap.Width - 1); + iy = Math.Clamp(iy, 0, bitmap.Height - 1); + return channel(bitmap.GetPixel(ix, iy)); + } + public static byte Bilinear(SKBitmap bitmap, float x, float y, Func channel) + { + int x1 = (int)MathF.Floor(x); + int y1 = (int)MathF.Floor(y); + int x2 = (int)MathF.Ceiling(x); + int y2 = (int)MathF.Ceiling(y); + x1 = Math.Clamp(x1, 0, bitmap.Width - 1); + y1 = Math.Clamp(y1, 0, bitmap.Height - 1); + x2 = Math.Clamp(x2, 0, bitmap.Width - 1); + y2 = Math.Clamp(y2, 0, bitmap.Height - 1); + float fx = x - x1; + float fy = y - y1; + byte c11 = channel(bitmap.GetPixel(x1, y1)); + byte c12 = channel(bitmap.GetPixel(x1, y2)); + byte c21 = channel(bitmap.GetPixel(x2, y1)); + byte c22 = channel(bitmap.GetPixel(x2, y2)); + float c1 = (c11 * (1 - fx)) + (c21 * fx); + float c2 = (c12 * (1 - fx)) + (c22 * fx); + float c = (c1 * (1 - fy)) + (c2 * fy); + return (byte)Math.Clamp((int)c, 0, 255); + } + } public enum NoiseType { SaltAndPepper, diff --git a/DigitalImageProcessing/DigitalImageProcessing.csproj b/DigitalImageProcessing/DigitalImageProcessing.csproj index 5a6df2e..89b7cc2 100644 --- a/DigitalImageProcessing/DigitalImageProcessing.csproj +++ b/DigitalImageProcessing/DigitalImageProcessing.csproj @@ -5,6 +5,7 @@ net8.0 enable enable + preview diff --git a/DigitalImageProcessing/Extensions.cs b/DigitalImageProcessing/Extensions.cs index 1feacd6..d7bc4c2 100644 --- a/DigitalImageProcessing/Extensions.cs +++ b/DigitalImageProcessing/Extensions.cs @@ -23,11 +23,152 @@ namespace DigitalImageProcessing { float color = (values[x, y] - min) / (max - min); output.SetPixel(x, y, new SKColor( - (byte)(color * 128 + 128), - (byte)(color * 128 + 128), - (byte)(color * 128 + 128))); + (byte)((color * 128) + 128), + (byte)((color * 128) + 128), + (byte)((color * 128) + 128))); } output.Encode(fs, SKEncodedImageFormat.Png, 100); } - } + public static void ToCmy(this SKColor color, out byte c, out int m, out int y) + { + c = (byte)(255 - color.Red); + m = (byte)(255 - color.Green); + y = (byte)(255 - color.Blue); + } + public static void FromCmy(this ref SKColor color, byte c, byte m, byte y) + { + color = new SKColor( + (byte)(255 - c), + (byte)(255 - m), + (byte)(255 - y)); + } + public static void ToCmyk(this SKColor color, out byte c, out byte m, out byte y, out byte k) + { + k = byte.Min(byte.Min((byte)(255 - color.Red), (byte)(255 - color.Green)), (byte)(255 - color.Blue)); + if (k == 255) + { + c = 0; + m = 0; + y = 0; + } + else + { + c = (byte)((255 - color.Red - k) * 255 / (255 - k)); + m = (byte)((255 - color.Green - k) * 255 / (255 - k)); + y = (byte)((255 - color.Blue - k) * 255 / (255 - k)); + } + } + public static void FromCmyk(this ref SKColor color, byte c, byte m, byte y, byte k) + { + color = new SKColor( + (byte)(255 - Math.Min(255, c * (255 - k) / 255 + k)), + (byte)(255 - Math.Min(255, m * (255 - k) / 255 + k)), + (byte)(255 - Math.Min(255, y * (255 - k) / 255 + k))); + } + + public static void ToHsi(this SKColor color, out int h, out int s, out int i) + { + float r = color.Red / 255f; + float g = color.Green / 255f; + float b = color.Blue / 255f; + float min = MathF.Min(r, MathF.Min(g, b)); + i = (int)(((r + g + b) / 3f) * 255); + if (i == 0) + { + h = 0; + s = 0; + return; + } + s = (int)((1 - (min / (i / 255f))) * 255); + if (s == 0) + { + h = 0; + return; + } + float numerator = 0.5f * ((r - g) + (r - b)); + float denominator = MathF.Sqrt((r - g) * (r - g) + (r - b) * (g - b)); + float theta = MathF.Acos(numerator / denominator); + if (b <= g) + h = (int)((theta / (2 * MathF.PI)) * 360); + else + h = (int)(((2 * MathF.PI - theta) / (2 * MathF.PI)) * 360); + } + public static void FromHsi(this ref SKColor color, int h, int s, int i) + { + float r = 0; + float g = 0; + float b = 0; + float H = h; + float S = s / 255f; + float I = i / 255f; + if (S == 0) + { + r = I; + g = I; + b = I; + } + else + { + if (H < 120) + { + b = I * (1 - S); + r = I * (1 + (S * MathF.Cos(H * MathF.PI / 180) / MathF.Cos((60 - H) * MathF.PI / 180))); + g = 3 * I - (r + b); + } + else if (H < 240) + { + H = H - 120; + r = I * (1 - S); + g = I * (1 + (S * MathF.Cos(H * MathF.PI / 180) / MathF.Cos((60 - H) * MathF.PI / 180))); + b = 3 * I - (r + g); + } + else + { + H = H - 240; + g = I * (1 - S); + b = I * (1 + (S * MathF.Cos(H * MathF.PI / 180) / MathF.Cos((60 - H) * MathF.PI / 180))); + r = 3 * I - (g + b); + } + } + color = new SKColor( + (byte)(float.Clamp(r * 255, 0, 255)), + (byte)(float.Clamp(g * 255, 0, 255)), + (byte)(float.Clamp(b * 255, 0, 255))); + } + + + + + + + + //public static void DrawTo(string filename, params float[][,] values) + //{ + // int width = values.Max(i => i.GetLength(0)); + // int height = values.Max(i => i.GetLength(1)); + // float[] min = new float[values.Length]; + // float[] max = new float[values.Length]; + // Array.Fill(min, float.MaxValue); + // Array.Fill(max, float.MinValue); + // for (int i = 0; i < values.Length; i++) + // for (int y = 0; y < height; y++) + // for (int x = 0; x < width; x++) + // { + // if (values[i][x, y] < min[i]) min[i] = values[i][x, y]; + // if (values[i][x, y] > max[i]) max[i] = values[i][x, y]; + // } + // using SKBitmap output = new(width, height); + // using FileStream fs = new(filename, FileMode.Create, FileAccess.Write); + // for (int y = 0; y < height; y++) + // for (int x = 0; x < width; x++) + // { + // double color = (values[x, y] - min) / (max - min); + // output.SetPixel(x, y, new SKColor( + // (byte)((color * 128) + 128), + // (byte)((color * 128) + 128), + // (byte)((color * 128) + 128))); + // } + // output.Encode(fs, SKEncodedImageFormat.Png, 100); + //} + } } diff --git a/DigitalImageProcessing/Program.cs b/DigitalImageProcessing/Program.cs index 262a0a2..870abba 100644 --- a/DigitalImageProcessing/Program.cs +++ b/DigitalImageProcessing/Program.cs @@ -4,4 +4,14 @@ using SkiaSharp; string testImage = @"C:\Users\Surface\Downloads\" + @"1682575881230001.png"; //Alograms.Steps(@"C:\Users\Surface\Downloads\", @"C:\Users\Surface\Downloads\" + @"1682575881230001.png"); -Alograms.Noises(testImage); \ No newline at end of file +SKBitmap d = Alograms.GetDefaultBitmap(new(1024, 512)); + +var noised = Alograms.Noises(d); + +using FileStream fs = new(@"C:\Users\Surface\Downloads\" + @"noise.png", FileMode.Create, FileAccess.Write); +noised.Encode(fs, SKEncodedImageFormat.Png, 100); + +var filtered = Alograms.Filt(d); + +using FileStream fs2 = new(@"C:\Users\Surface\Downloads\" + @"filtered.png", FileMode.Create, FileAccess.Write); +filtered.Encode(fs2, SKEncodedImageFormat.Png, 100); \ No newline at end of file diff --git a/DigitalImageProcessing/Sampler.cs b/DigitalImageProcessing/Sampler.cs index 349e07c..3f7c605 100644 --- a/DigitalImageProcessing/Sampler.cs +++ b/DigitalImageProcessing/Sampler.cs @@ -9,27 +9,4 @@ namespace DigitalImageProcessing Mirror = 2, Clamp = 3, } - internal class Sampler - { - private SKBitmap bitmap; - public SamplerMethod Method { get; set; } - public Sampler(SKBitmap bitmap) - { - this.bitmap = bitmap; - } - public SKColor this[int x, int y] - { - get - { - return Method switch - { - SamplerMethod.None => bitmap.GetPixel(x, y), - SamplerMethod.Repeat => bitmap.GetPixel((x + bitmap.Width) % bitmap.Width, (y + bitmap.Height) % bitmap.Height), - - SamplerMethod.Clamp => bitmap.GetPixel(int.Clamp(x, 0, bitmap.Width - 1), int.Clamp(y, 0, bitmap.Height - 1)), - SamplerMethod.Mirror or _ => throw new NotImplementedException(), - }; - } - } - } }