diff --git a/PhotoLocator/BitmapOperations/ColorToneAdjustOperation.cs b/PhotoLocator/BitmapOperations/ColorToneAdjustOperation.cs index 58edfe4..1bf0e6e 100644 --- a/PhotoLocator/BitmapOperations/ColorToneAdjustOperation.cs +++ b/PhotoLocator/BitmapOperations/ColorToneAdjustOperation.cs @@ -104,9 +104,9 @@ public static void ColorTransformHSI2RGB(float h, float s, float i, out float r, public static void ColorTransformRGB2HSI(float r, float g, float b, out float h, out float s, out float i) { - r = RealMath.Clamp(r, 0f, 1f); - g = RealMath.Clamp(g, 0f, 1f); - b = RealMath.Clamp(b, 0f, 1f); + r = Math.Clamp(r, 0f, 1f); + g = Math.Clamp(g, 0f, 1f); + b = Math.Clamp(b, 0f, 1f); i = (r + g + b) / 3f; if (i == 0) { @@ -115,17 +115,17 @@ public static void ColorTransformRGB2HSI(float r, float g, float b, out float h, } else { - var D = r; - if (g < D) - D = g; - if (b < D) - D = b; - s = Math.Max(0, 1 - 3f / (r + g + b) * D); + var min = r; + if (g < min) + min = g; + if (b < min) + min = b; + s = Math.Max(0, 1 - 3f / (r + g + b) * min); if (s == 0) h = 0; else { - var a = 0.5 * (r - g + (r - b)) / Math.Sqrt(RealMath.Sqr(r - g) + (r - b) * (g - b)); + var a = 0.5 * (r - g + (r - b)) / Math.Sqrt((r - g) * (r - g) + (r - b) * (g - b)); double rh; if (a <= -1) rh = Math.PI; @@ -177,13 +177,14 @@ public override void Apply() Parallel.For(0, _srcHSI.Height, y => { var toneAdjustments = ToneAdjustments; + var width = _srcHSI.Width; unsafe { fixed (float* src = &_srcHSI.Elements[y, 0]) fixed (float* dst = &DstBitmap.Elements[y, 0]) { int xx = 0; - for (var x = 0; x < _srcHSI.Width; x++) + for (var x = 0; x < width; x++) { var tone = (src[xx] - Rotation) * NumberOfTones; if (tone < 0) diff --git a/PhotoLocator/BitmapOperations/FloatBitmap.cs b/PhotoLocator/BitmapOperations/FloatBitmap.cs index 152697e..66f12e2 100644 --- a/PhotoLocator/BitmapOperations/FloatBitmap.cs +++ b/PhotoLocator/BitmapOperations/FloatBitmap.cs @@ -84,12 +84,12 @@ public void Assign(BitmapSource source, double gamma) Parallel.For(0, Height, y => { fixed (float* gamma = gammaLut) - fixed (float* elements = &Elements[y, 0]) - fixed (ushort* sourceRow = &sourcePixels[y * Stride]) + fixed (float* dstRow = &Elements[y, 0]) + fixed (ushort* srcRow = &sourcePixels[y * Stride]) { var stride = Stride; for (var x = 0; x < stride; x++) - elements[x] = gamma[sourceRow[x]]; + dstRow[x] = gamma[srcRow[x]]; } }); } @@ -106,14 +106,15 @@ public void Assign(BitmapSource source, double gamma) Parallel.For(0, Height, y => { fixed (float* gamma = gammaLut) - fixed (float* elements = &Elements[y, 0]) - fixed (byte* sourceRow = &sourcePixels[y * Stride]) + fixed (float* dstRow = &Elements[y, 0]) + fixed (byte* srcRow = &sourcePixels[y * Stride]) { - for (var x = 0; x < Width; x++) + var width = Width; + for (var x = 0; x < width; x++) { - elements[x * 3 + 2] = gamma[sourceRow[x * 3 + 0]]; - elements[x * 3 + 1] = gamma[sourceRow[x * 3 + 1]]; - elements[x * 3 + 0] = gamma[sourceRow[x * 3 + 2]]; + dstRow[x * 3 + 2] = gamma[srcRow[x * 3 + 0]]; + dstRow[x * 3 + 1] = gamma[srcRow[x * 3 + 1]]; + dstRow[x * 3 + 0] = gamma[srcRow[x * 3 + 2]]; } } }); @@ -133,14 +134,15 @@ public void Assign(BitmapSource source, double gamma) Parallel.For(0, Height, y => { fixed (float* gamma = gammaLut) - fixed (float* elements = &Elements[y, 0]) - fixed (byte* sourceRow = &sourcePixels[y * Width * 4]) + fixed (float* dstRow = &Elements[y, 0]) + fixed (byte* srcRow = &sourcePixels[y * Width * 4]) { - for (var x = 0; x < Width; x++) + var width = Width; + for (var x = 0; x < width; x++) { - elements[x * 3 + 2] = gamma[sourceRow[x * 4 + 0]]; - elements[x * 3 + 1] = gamma[sourceRow[x * 4 + 1]]; - elements[x * 3 + 0] = gamma[sourceRow[x * 4 + 2]]; + dstRow[x * 3 + 2] = gamma[srcRow[x * 4 + 0]]; + dstRow[x * 3 + 1] = gamma[srcRow[x * 4 + 1]]; + dstRow[x * 3 + 0] = gamma[srcRow[x * 4 + 2]]; } } }); @@ -168,12 +170,12 @@ public void Assign(BitmapSource source, double gamma) Parallel.For(0, Height, y => { fixed (float* gamma = gammaLut) - fixed (float* elements = &Elements[y, 0]) - fixed (byte* sourceRow = &sourcePixels[y * Stride]) + fixed (float* dstRow = &Elements[y, 0]) + fixed (byte* srcRow = &sourcePixels[y * Stride]) { var stride = Stride; for (var x = 0; x < stride; x++) - elements[x] = gamma[sourceRow[x]]; + dstRow[x] = gamma[srcRow[x]]; } }); } @@ -192,7 +194,8 @@ public void Assign(BitmapPlaneInt16 src, Func remap) fixed (Int16* srcPix = &src.Elements[y, 0]) fixed (float* dstPix = &Elements[y, 0]) { - for (int x = 0; x < Width; x++) + var width = Width; + for (int x = 0; x < width; x++) dstPix[x] = remap(srcPix[x]); } }); @@ -214,8 +217,8 @@ public BitmapSource ToBitmapSource(double dpiX, double dpiY, double gamma, Pixel return (bitmap, Task.Run(() => { var histogram = new int[256]; - var length = Height * Stride; - for (int i = 0; i < length; i++) + var size = Size; + for (int i = 0; i < size; i++) histogram[pixels[i]]++; ArrayPool.Shared.Return(pixels); return histogram; @@ -228,17 +231,19 @@ private byte[] ToPixels8(double gamma) var gammaLut = CreateGammaLookupFloatToByte(gamma); unsafe { - //gamma = 1 / gamma; Parallel.For(0, Height, y => { + var stride = Stride; fixed (byte* gamma = gammaLut) - fixed (float* elements = &Elements[y, 0]) - fixed (byte* destRow = &pixels[y * Stride]) + fixed (float* srcRow = &Elements[y, 0]) + fixed (byte* dstRow = &pixels[y * stride]) { - var stride = Stride; for (var x = 0; x < stride; x++) - //destRow[x] = (byte)IntMath.Clamp((int)(Math.Pow(elements[x], gamma) * 255 + 0.5), 0, 255); - destRow[x] = gamma[IntMath.Clamp((int)(elements[x] * FloatToByteGammaLutRange + 0.5f), 0, FloatToByteGammaLutRange)]; + { + int idx = (int)(srcRow[x] * FloatToByteGammaLutRange + 0.5f); + if (idx < 0) idx = 0; else if (idx > FloatToByteGammaLutRange) idx = FloatToByteGammaLutRange; + dstRow[x] = gamma[idx]; + } } }); } @@ -266,7 +271,7 @@ public void SaveToFile(string fileName, double gamma = 1) GeneralFileFormatHandler.SaveToFile(ToBitmapSource(96, 96, gamma), fileName); } - static float[] CreateDeGammaLookup(double gamma, int range) + internal static float[] CreateDeGammaLookup(double gamma, int range) { var gammaLUT = ArrayPool.Shared.Rent(range); var scale = 1.0 / (range - 1); @@ -374,12 +379,28 @@ public float Min() { fixed (float* elements = Elements) { - var size = Size; - for (var i = 0; i < size; i++) + var p = elements; + // Process in blocks of 8 to reduce loop overhead and branch misprediction + var remaining = Size; + while (remaining >= 8) + { + var v0 = p[0]; if (v0 < min) min = v0; if (v0 > max) max = v0; + var v1 = p[1]; if (v1 < min) min = v1; if (v1 > max) max = v1; + var v2 = p[2]; if (v2 < min) min = v2; if (v2 > max) max = v2; + var v3 = p[3]; if (v3 < min) min = v3; if (v3 > max) max = v3; + var v4 = p[4]; if (v4 < min) min = v4; if (v4 > max) max = v4; + var v5 = p[5]; if (v5 < min) min = v5; if (v5 > max) max = v5; + var v6 = p[6]; if (v6 < min) min = v6; if (v6 > max) max = v6; + var v7 = p[7]; if (v7 < min) min = v7; if (v7 > max) max = v7; + p += 8; + remaining -= 8; + } + // Finish remaining elements + for (var i = 0; i < remaining; i++) { - var value = elements[i]; - min = Math.Min(min, value); - max = Math.Max(max, value); + var value = *p++; + if (value < min) min = value; + if (value > max) max = value; } } } @@ -394,8 +415,16 @@ public double Mean() { fixed (float* elements = Elements) { - for (var i = 0; i < size; i++) - sum += elements[i]; + float* p = elements; + var remaining = size; + while (remaining >= 8) + { + sum += p[0] + p[1] + p[2] + p[3] + p[4] + p[5] + p[6] + p[7]; + p += 8; + remaining -= 8; + } + for (var i = 0; i < remaining; i++) + sum += *p++; } } return sum / size; diff --git a/PhotoLocator/BitmapOperations/LanczosResizeOperation.cs b/PhotoLocator/BitmapOperations/LanczosResizeOperation.cs index 2c6a216..3257f75 100644 --- a/PhotoLocator/BitmapOperations/LanczosResizeOperation.cs +++ b/PhotoLocator/BitmapOperations/LanczosResizeOperation.cs @@ -1,9 +1,10 @@ -using System; +using PhotoLocator.Helpers; +using System; +using System.Buffers; using System.Threading; using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; -using PhotoLocator.Helpers; namespace PhotoLocator.BitmapOperations { @@ -131,15 +132,11 @@ public unsafe void Apply(byte* source, int srcOffset, byte* dest, int dstOffset, class LineResamplerFixedPoint { - record struct SourcePixelWeight - { - public int SourceIndex; - public int SourceWeight; - } - + // Store indices and weights in separate arrays to reduce indirection and enable JIT optimization record struct Weight { - public SourcePixelWeight[] SourcePixelWeights; + public int[] SourceIndices; + public int[] SourceWeights; } readonly Weight[] _weights; @@ -155,23 +152,25 @@ internal LineResamplerFixedPoint(Func filterFunc, float filterWind { double sum = 0; float[] rawWeights; - SourcePixelWeight[] sourceWeights; + int[] sourceIndices; if (dstWidth < srcWidth) // Downscale { var center = i * scaleWN; var pMin = (int)Math.Floor(center - reduceWindow); var pMax = (int)Math.Ceiling(center + reduceWindow); - sourceWeights = new SourcePixelWeight[pMax - pMin + 1]; - rawWeights = new float[sourceWeights.Length]; + var len = pMax - pMin + 1; + sourceIndices = new int[len]; + rawWeights = new float[len]; for (var p = pMin; p <= pMax; p++) { + var idx = p - pMin; if (p < 0) - sourceWeights[p - pMin].SourceIndex = 0; + sourceIndices[idx] = 0; else if (p >= srcWidth) - sourceWeights[p - pMin].SourceIndex = (srcWidth - 1) * srcSampleDistance; + sourceIndices[idx] = (srcWidth - 1) * srcSampleDistance; else - sourceWeights[p - pMin].SourceIndex = p * srcSampleDistance; - sum += rawWeights[p - pMin] = filterFunc((p - center) * scaleNW) * scaleNW; + sourceIndices[idx] = p * srcSampleDistance; + sum += rawWeights[idx] = filterFunc((p - center) * scaleNW) * scaleNW; } } else // Upscale @@ -179,26 +178,35 @@ internal LineResamplerFixedPoint(Func filterFunc, float filterWind var center = i * scaleWN; var pMin = (int)Math.Floor(center - filterWindow); var pMax = (int)Math.Ceiling(center + filterWindow); - sourceWeights = new SourcePixelWeight[pMax - pMin + 1]; - rawWeights = new float[sourceWeights.Length]; + var len = pMax - pMin + 1; + sourceIndices = new int[len]; + rawWeights = new float[len]; for (var p = pMin; p <= pMax; p++) { + var idx = p - pMin; if (p < 0) - sourceWeights[p - pMin].SourceIndex = 0; + sourceIndices[idx] = 0; else if (p >= srcWidth) - sourceWeights[p - pMin].SourceIndex = (srcWidth - 1) * srcSampleDistance; + sourceIndices[idx] = (srcWidth - 1) * srcSampleDistance; else - sourceWeights[p - pMin].SourceIndex = p * srcSampleDistance; - sum += rawWeights[p - pMin] = filterFunc(p - center); + sourceIndices[idx] = p * srcSampleDistance; + sum += rawWeights[idx] = filterFunc(p - center); } } if (sum > 0) { var scale = 65536 / sum; - for (var p = 0; p < sourceWeights.Length; p++) - sourceWeights[p].SourceWeight = IntMath.Round(rawWeights[p] * scale); + var weightsInt = new int[rawWeights.Length]; + for (var p = 0; p < rawWeights.Length; p++) + weightsInt[p] = IntMath.Round(rawWeights[p] * scale); + _weights[i].SourceIndices = sourceIndices; + _weights[i].SourceWeights = weightsInt; + } + else + { + _weights[i].SourceIndices = Array.Empty(); + _weights[i].SourceWeights = Array.Empty(); } - _weights[i].SourcePixelWeights = sourceWeights; }); } @@ -208,15 +216,26 @@ public unsafe void Apply(byte* source, int srcOffset, byte* dest, int dstOffset, for (var i = 0; i < weights.Length; i++) { int sum = 32768; - int length = weights[i].SourcePixelWeights.Length; - fixed (SourcePixelWeight* sourceWeights = weights[i].SourcePixelWeights) + var sourceIndices = weights[i].SourceIndices; + var sourceWeights = weights[i].SourceWeights; + int length = sourceIndices.Length; + byte* sourcePixels = source + srcOffset; + int j = 0; + // Unroll loop to reduce per-iteration overhead and improve IL/JIT optimizations + for (; j + 3 < length; j += 4) + sum += sourcePixels[sourceIndices[j]] * sourceWeights[j] + + sourcePixels[sourceIndices[j + 1]] * sourceWeights[j + 1] + + sourcePixels[sourceIndices[j + 2]] * sourceWeights[j + 2] + + sourcePixels[sourceIndices[j + 3]] * sourceWeights[j + 3]; + for (; j < length; j++) + sum += sourcePixels[sourceIndices[j]] * sourceWeights[j]; + if (sum > 0) { - var sourceWeight = sourceWeights; - for (var j = 0; j < length; j++, sourceWeight++) - sum += source[srcOffset + (*sourceWeight).SourceIndex] * (*sourceWeight).SourceWeight; + sum >>= 16; + if (sum > 255) + sum = 255; + dest[dstOffset + i * dstSampleDistance] = (byte)sum; } - if (sum > 0) - dest[dstOffset + i * dstSampleDistance] = (byte)Math.Min(sum >> 16, 255); } } } @@ -290,12 +309,13 @@ public byte[] Apply(byte[] pixels, int width, int height, int planes, int pixelS else return null; - var pixels = new byte[width * height * pixelSize]; - source.CopyPixels(pixels, width * pixelSize, 0); + var srcPixels = ArrayPool.Shared.Rent(width * height * pixelSize); + source.CopyPixels(srcPixels, width * pixelSize, 0); - pixels = Apply(pixels, width, height, planes, pixelSize, newWidth, newHeight, ct); + var dstPixels = Apply(srcPixels, width, height, planes, pixelSize, newWidth, newHeight, ct); + var result = BitmapSource.Create(newWidth, newHeight, newDpiX, newDpiY, source.Format, null, dstPixels, newWidth * pixelSize); + ArrayPool.Shared.Return(srcPixels); // Apply may return srcPixels so only return after creating result - var result = BitmapSource.Create(newWidth, newHeight, newDpiX, newDpiY, source.Format, null, pixels, newWidth * pixelSize); result.Freeze(); return result; } diff --git a/PhotoLocator/BitmapOperations/MaxFramesOperation.cs b/PhotoLocator/BitmapOperations/MaxFramesOperation.cs index a55014f..5868f68 100644 --- a/PhotoLocator/BitmapOperations/MaxFramesOperation.cs +++ b/PhotoLocator/BitmapOperations/MaxFramesOperation.cs @@ -15,6 +15,11 @@ public MaxFramesOperation(string? darkFramePath, CombineFramesRegistration? regi public override void ProcessImage(BitmapSource image) { var pixels = PrepareFrame(image); + if (ProcessedImages == 1) + { + Parallel.For(0, pixels.Length, i => _accumulatorPixels[i] = pixels[i]); + return; + } Parallel.For(0, pixels.Length, i => { if (pixels[i] > _accumulatorPixels![i]) diff --git a/PhotoLocator/Controls/CropControl.xaml.cs b/PhotoLocator/Controls/CropControl.xaml.cs index d16725f..e5451a5 100644 --- a/PhotoLocator/Controls/CropControl.xaml.cs +++ b/PhotoLocator/Controls/CropControl.xaml.cs @@ -46,7 +46,7 @@ public void Reset(BitmapSource? image, double imageScale, double cropWidthHeight } catch { } // Ignore unsupported pixel formats } - CropBorderColor = new SolidColorBrush(pixelMean > 0.2 ? Color.FromArgb(128, 0, 0, 0) : Color.FromArgb(64, 255, 255, 255)); + CropBorderColor = new SolidColorBrush(pixelMean > 0.25 ? Color.FromArgb(128, 0, 0, 0) : Color.FromArgb(64, 255, 255, 255)); Width = _imageWidth * imageScale; Height = _imageHeight * imageScale; var imageWidthHeightRatio = (double)_imageWidth / _imageHeight; diff --git a/PhotoLocatorTest/BitmapOperations/ColorToneAdjustOperationTest.cs b/PhotoLocatorTest/BitmapOperations/ColorToneAdjustOperationTest.cs index dbb8402..b256610 100644 --- a/PhotoLocatorTest/BitmapOperations/ColorToneAdjustOperationTest.cs +++ b/PhotoLocatorTest/BitmapOperations/ColorToneAdjustOperationTest.cs @@ -1,7 +1,6 @@ using PhotoLocator.PictureFileFormats; using System.Diagnostics; using System.Windows.Media.Imaging; -using static PhotoLocator.BitmapOperations.ColorToneAdjustOperation; namespace PhotoLocator.BitmapOperations { diff --git a/PhotoLocatorTest/BitmapOperations/FloatBitmapTest.cs b/PhotoLocatorTest/BitmapOperations/FloatBitmapTest.cs index 9606503..8739e70 100644 --- a/PhotoLocatorTest/BitmapOperations/FloatBitmapTest.cs +++ b/PhotoLocatorTest/BitmapOperations/FloatBitmapTest.cs @@ -1,4 +1,7 @@ -using System.Diagnostics; +using PhotoLocator.PictureFileFormats; +using System.Diagnostics; +using System.Windows.Media; +using System.Windows.Media.Imaging; namespace PhotoLocator.BitmapOperations { @@ -38,9 +41,86 @@ public void FloatToByteGammaLutRange_ShouldBeSufficient() } [TestMethod] - public void MinMax_ShouldWork() + [DataRow(6, 4, 1, 2.2, 1)] + [DataRow(6, 4, 3, 2.2, 1)] + [DataRow(6, 4, 4, 2.2, 1)] + public void Assign_ShouldAssign(int width, int height, int planes, double gamma, int iterations) { - var floatBitmap = new FloatBitmap(10, 10, 3); + var format = planes switch + { + 1 => PixelFormats.Gray8, + 3 => PixelFormats.Rgb24, + 4 => PixelFormats.Cmyk32, + _ => throw new ArgumentException("Unsupported pixel size") + }; + var sourcePixels = new byte[width * height * planes]; + for (int i = 0; i < sourcePixels.Length; i++) + sourcePixels[i] = (byte)(i & 255); + var source = BitmapSource.Create(width, height, 96, 96, format, null, sourcePixels, width * planes); + + var floatBitmap = new FloatBitmap(width, height, planes); + for (int i = 0; i < iterations; i++) + { + var sw = Stopwatch.StartNew(); + floatBitmap.Assign(source, gamma); + Console.WriteLine(sw.ElapsedMilliseconds); + } + + var gammaLut = FloatBitmap.CreateDeGammaLookup(gamma, 256); + Assert.AreEqual(gammaLut[sourcePixels[0]], floatBitmap.Elements[0, 0]); + Assert.AreEqual(gammaLut[sourcePixels[1]], floatBitmap.Elements[0, 1]); + Assert.AreEqual(gammaLut[sourcePixels[2]], floatBitmap.Elements[0, 2]); + } + + [TestMethod] + [DataRow(6, 4, 1, 1)] + public void Assign_ShouldAssignBgr32(int width, int height, double gamma, int iterations) + { + var sourcePixels = new byte[width * height * 4]; + for (int i = 0; i < sourcePixels.Length; i++) + sourcePixels[i] = (byte)(i & 255); + var source = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgr32, null, sourcePixels, width * 4); + + var floatBitmap = new FloatBitmap(width, height, 3); + for (int i = 0; i < iterations; i++) + { + var sw = Stopwatch.StartNew(); + floatBitmap.Assign(source, gamma); + Console.WriteLine(sw.ElapsedMilliseconds); + } + + var gammaLut = FloatBitmap.CreateDeGammaLookup(gamma, 256); + Assert.AreEqual(gammaLut[sourcePixels[2]], floatBitmap.Elements[0, 0]); + Assert.AreEqual(gammaLut[sourcePixels[1]], floatBitmap.Elements[0, 1]); + Assert.AreEqual(gammaLut[sourcePixels[0]], floatBitmap.Elements[0, 2]); + } + + [TestMethod] + [DataRow(6, 4, 1, 2.2, 1, null)] + [DataRow(6, 4, 3, 2.2, 1, null)] + [DataRow(6, 4, 4, 2.2, 1, null)] + public void ToBitmapSource_ShouldCreateBitmapSource(int width, int height, int planes, double gamma, int iterations, string? fileName) + { + var floatBitmap = new FloatBitmap(width, height, planes); + var scale = 1.0f / width; + floatBitmap.ProcessElementWise((x, y) => x * scale); + for (int i = 0; i < iterations; i++) + { + var sw = Stopwatch.StartNew(); + var bitmap = floatBitmap.ToBitmapSource(96, 96, gamma); + Console.WriteLine(sw.ElapsedMilliseconds); + + Assert.AreEqual(width, bitmap.PixelWidth); + Assert.AreEqual(height, bitmap.PixelHeight); + if (fileName is not null) + GeneralFileFormatHandler.SaveToFile(bitmap, fileName); + } + } + + [TestMethod] + public void MinMax_ShouldReturnMinMax() + { + var floatBitmap = new FloatBitmap(6, 4, 3); floatBitmap[0, 0] = 1; var sw = Stopwatch.StartNew(); @@ -50,5 +130,18 @@ public void MinMax_ShouldWork() Assert.AreEqual(0, minMax.Min); Assert.AreEqual(1, minMax.Max); } + + [TestMethod] + public void Mean_ShouldReturnMean() + { + var floatBitmap = new FloatBitmap(6, 4, 3); + floatBitmap.ProcessElementWise(p => 1); + + var sw = Stopwatch.StartNew(); + var mean = floatBitmap.Mean(); + Console.WriteLine(sw.ElapsedMilliseconds); + + Assert.AreEqual(1, mean); + } } } diff --git a/PhotoLocatorTest/BitmapOperations/MaxFramesOperationTest.cs b/PhotoLocatorTest/BitmapOperations/MaxFramesOperationTest.cs index 97a834e..b142761 100644 --- a/PhotoLocatorTest/BitmapOperations/MaxFramesOperationTest.cs +++ b/PhotoLocatorTest/BitmapOperations/MaxFramesOperationTest.cs @@ -6,6 +6,29 @@ namespace PhotoLocator.BitmapOperations; [TestClass] public class MaxFramesOperationTest { + [TestMethod] + [DataRow(0, 1)] + [DataRow(0.5f, 0)] + public void GetResult8_ShouldReturnMax(float firstImageValue, float secondImageValue) + { + var floatImage1 = new FloatBitmap(6, 4, 3); + floatImage1.ProcessElementWise(p => firstImageValue); + var floatImage2 = new FloatBitmap(floatImage1.Width, floatImage1.Height, floatImage1.PlaneCount); + floatImage2.ProcessElementWise(p => secondImageValue); + + var op = new MaxFramesOperation(null, null, default); + var sw = Stopwatch.StartNew(); + op.ProcessImage(floatImage1.ToBitmapSource(96, 96, 1)); + op.ProcessImage(floatImage2.ToBitmapSource(96, 96, 1)); + Console.WriteLine(sw.ElapsedMilliseconds); + + Assert.IsTrue(op.IsResultReady); + Assert.AreEqual(2, op.ProcessedImages); + + var result = new FloatBitmap(op.GetResult8(), 1); + Assert.AreEqual(Math.Max(firstImageValue, secondImageValue), result.Mean(), 0.01); + } + [TestMethod] public void ProcessImage_ShouldUseDarkFrame() {