/// <summary> /// Applies the a filter kernel to the specified image. /// </summary> /// <param name="image">The image.</param> /// <param name="kernel">The filter kernel. (Needs to be square.)</param> /// <param name="wrapMode">The texture address mode.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="image"/> or <paramref name="kernel"/> is <see langword="null"/>. /// </exception> /// <exception cref="ArgumentException"> /// <paramref name="kernel"/> is non-square. /// </exception> public static void Convolve(Image image, float[,] kernel, TextureAddressMode wrapMode) { if (image == null) throw new ArgumentNullException("image"); if (kernel == null) throw new ArgumentNullException("kernel"); if (kernel.GetLength(0) != kernel.GetLength(1)) throw new ArgumentException("Filter kernel needs to be square.", "kernel"); int width = image.Width; int height = image.Height; int kernelSize = kernel.GetLength(0); int kernelOffset = kernelSize / 2; var tmpImage = new Image(width, height, image.Format); Buffer.BlockCopy(image.Data, 0, tmpImage.Data, 0, image.Data.Length); // ReSharper disable AccessToDisposedClosure using (var tempImage4F = new ImageAccessor(tmpImage)) using (var image4F = new ImageAccessor(image)) { for (int y = 0; y < height; y++) #else Parallel.For(0, height, y => { for (int x = 0; x < width; x++) { // Apply 2D kernel at (x, y). Vector4 color = new Vector4(); for (int row = 0; row < kernelSize; row++) { int srcY = y + row - kernelOffset; for (int column = 0; column < kernelSize; column++) { int srcX = x + column - kernelOffset; color += kernel[row, column] * tempImage4F.GetPixel(srcX, srcY, wrapMode); } } image4F.SetPixel(x, y, color); } } ); } // ReSharper restore AccessToDisposedClosure }
private static void Resize2D(Image srcImage, Image dstImage, bool alphaTransparency, TextureAddressMode wrapMode, PolyphaseKernel kernelX, PolyphaseKernel kernelY) { var tmpImage = new Image(dstImage.Width, srcImage.Height, srcImage.Format); // ReSharper disable AccessToDisposedClosure using (var srcImage4F = new ImageAccessor(srcImage)) using (var tmpImage4F = new ImageAccessor(tmpImage)) using (var dstImage4F = new ImageAccessor(dstImage)) { // Resize horizontally: srcImage --> tmpImage { float scale = (float)tmpImage4F.Width / srcImage4F.Width; float inverseScale = 1.0f / scale; for (int y = 0; y < tmpImage4F.Height; y++) #else Parallel.For(0, tmpImage4F.Height, y => { // Apply polyphase kernel horizontally. for (int x = 0; x < tmpImage4F.Width; x++) { float center = (x + 0.5f) * inverseScale; int left = (int)Math.Floor(center - kernelX.Width); int right = (int)Math.Ceiling(center + kernelX.Width); Debug.Assert(right - left <= kernelX.WindowSize); float totalRgbWeights = 0.0f; Vector4 sum = new Vector4(); for (int i = 0; i < kernelX.WindowSize; i++) { Vector4 color = srcImage4F.GetPixel(left + i, y, wrapMode); //if (Numeric.IsNaN(color.X) || Numeric.IsNaN(color.Y) || Numeric.IsNaN(color.Z) || Numeric.IsNaN(color.W) // || color.X < 0 || color.Y < 0 || color.Z < 0 || color.W < 0 // || color.X > 1 || color.Y > 1 || color.Z > 1 || color.W > 1) // Debugger.Break(); const float alphaEpsilon = 1.0f / 256.0f; float alpha = alphaTransparency ? color.W + alphaEpsilon : 1.0f; float weight = kernelX.Weights[x, i]; float rgbWeight = weight * alpha; totalRgbWeights += rgbWeight; sum.X += color.X * rgbWeight; sum.Y += color.Y * rgbWeight; sum.Z += color.Z * rgbWeight; sum.W += color.W * weight; //if (Numeric.IsNaN(sum.X) || Numeric.IsNaN(sum.Y) || Numeric.IsNaN(sum.Z) || Numeric.IsNaN(sum.W) // || sum.X < 0 || sum.Y < 0 || sum.Z < 0 || sum.W < 0 // || sum.X > 1 || sum.Y > 1 || sum.Z > 1 || sum.W > 1) // Debugger.Break(); } float f = 1 / totalRgbWeights; sum.X *= f; sum.Y *= f; sum.Z *= f; //if (Numeric.IsNaN(sum.X) || Numeric.IsNaN(sum.Y) || Numeric.IsNaN(sum.Z) || Numeric.IsNaN(sum.W) // || sum.X < 0 || sum.Y < 0 || sum.Z < 0 || sum.W < 0 // || sum.X > 1 || sum.Y > 1 || sum.Z > 1 || sum.W > 1) // Debugger.Break(); tmpImage4F.SetPixel(x, y, sum); } } ); } // Resize vertically: tmpImage --> dstImage { float scale = (float)dstImage4F.Height / tmpImage4F.Height; float inverseScale = 1.0f / scale; for (int x = 0; x < dstImage4F.Width; x++) #else Parallel.For(0, dstImage4F.Width, x => { // Apply polyphase kernel vertically. for (int y = 0; y < dstImage4F.Height; y++) { float center = (y + 0.5f) * inverseScale; int left = (int)Math.Floor(center - kernelY.Width); int right = (int)Math.Ceiling(center + kernelY.Width); Debug.Assert(right - left <= kernelY.WindowSize); float totalRgbWeights = 0.0f; Vector4 sum = new Vector4(); for (int i = 0; i < kernelY.WindowSize; i++) { Vector4 color = tmpImage4F.GetPixel(x, left + i, wrapMode); const float alphaEpsilon = 1.0f / 256.0f; float alpha = alphaTransparency ? color.W + alphaEpsilon : 1.0f; float weight = kernelY.Weights[y, i]; float rgbWeight = weight * alpha; totalRgbWeights += rgbWeight; sum.X += color.X * rgbWeight; sum.Y += color.Y * rgbWeight; sum.Z += color.Z * rgbWeight; sum.W += color.W * weight; } float f = 1 / totalRgbWeights; sum.X *= f; sum.Y *= f; sum.Z *= f; dstImage4F.SetPixel(x, y, sum); } } ); } } // ReSharper restore AccessToDisposedClosure }