loupasc Posted May 29, 2020 Share Posted May 29, 2020 (edited) WaveToolBox Last version 1.5.7466.17310 [ 10 June 2020 ] Download link > > > Download Change log summary: Fix crash with small image using mirror border handling Improve matrix coefficient accuracy Location: Effects -> Advanced. Corresponding source is available in the last post. Plugin description The goal of the proposed plugin is to provide advanced settings for processing image using Sinc filters. It offers the following settings: - Kernel shape (square, disc and diamond) (diamond is not a shape really common) - Basic cutoff settings (Will be improved in future version) - Kernel radius, literally the size of the box filter in pixels - Border handling (uniform color, extend, mirror) - Global contrast The processing may be heavy in case of large radius; optimization is in progress... Graphic user interface Spoiler Plugin usage - Inverse dithering - Anti-aliasing Spoiler And the corresponding spectrum (originally for validation purpose but I found it is nice to show) Spoiler Best regards, WaveToolBox.zip Edited June 10, 2020 by loupasc Version Update 1.5.7466.17310 3 1 Quote Link to comment Share on other sites More sharing options...
Reptillian Posted May 29, 2020 Share Posted May 29, 2020 From what I see, this is basically utilizing fourier transform. I have not read the code that much to determine that, but the example picture shows it, right? Quote G'MIC Filter Developer Link to comment Share on other sites More sharing options...
loupasc Posted May 29, 2020 Author Share Posted May 29, 2020 (edited) Hi Reptillian, I used ImageMagick to make the spectrum images which are created using FFT. My plugin uses convolution method (Sinc filters). FFT with spectrum mask is a powerful tool and versatile but I think it's hard to get into that. My goal is make a toolkit allowing image processing like FFT but lighter in term of computation resources and easier to use. (Goal is today far from filled ) Edited May 29, 2020 by loupasc Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 30, 2020 Author Share Posted May 30, 2020 (edited) After checking FFT results I noticed diamond shaped filter was leaking. There were a bug in the coefficient creation. Here the fixed version v1.2. The results show a better retention of unwanted frequencies. Minor upgrade : The filter cutoff parameter has four states corresponding to the number of lobes of the Sinc functions (4-8-12-16). Spoiler // Name: WaveToolBox // Submenu: Advanced // Author: Pascal Ollive // Title: Wave tool box // Version: 1.2 // Desc: Reconstruction filter tool box // Keywords: convolution|kernel|filter // URL: // Help: #region UICode RadioButtonControl kernelShape = 0; // Shape|Square|Disc|Diamond IntSliderControl cutoffFactor = 2; // [1,4] Cutoff sharpness IntSliderControl radius = 8; // [8,100] Radius RadioButtonControl borderHandling = 2; // Border handling|Uniform|Extend|Mirror IntSliderControl contrastSliderIndex = 5; // [0,10] Contrast #endregion private int kernelRadius = 8; private double[] kernelMatrix = null; private double kernelNorm = 1.0; private static byte to8bit(double x) { if (x < 0) { return 0; } if (x > 255) { return 255; } return (byte) Math.Round(x); } private static double lanczos(double x, int lobeCount) { if (x == 0.0) { return 1.0; } double xMultiplyPI = Math.PI * x; return lobeCount * Math.Sin(xMultiplyPI) * Math.Sin(xMultiplyPI / lobeCount) / (xMultiplyPI * xMultiplyPI); } private static double computeLanczosCoef(int i, int j, byte shape, int lobeCount, int radius) { // center shift int x = i - radius + 1; int y = j - radius + 1; if (shape == 0) { // Square // normalize into lanczos window return lanczos((double) lobeCount * x / radius, lobeCount) * lanczos((double) lobeCount * y / radius, lobeCount); } if (shape == 1) { // Disc double d = Math.Sqrt(x * x + y * y); if (d < radius) { // normalize into lanczos window return lanczos(lobeCount * d / radius, lobeCount); } } if (shape == 2) { // Diamond (45 degrees rotated square and SQRT_2-rescaled) if (x < 0) { x = -x; } if (y < 0) { y = -y; } double p = (x + y); if (p < radius) { // normalize into lanczos window return lanczos(lobeCount * p / radius, lobeCount) * lanczos(lobeCount * (p - 2 * y) / radius, lobeCount); } } return 0.0; } private static double[] createLanczosKernel(byte shape, int lobeCount, int radius) { int dimension = 2 * radius - 1; double[] matrix = new double[dimension * dimension]; for (int j = 0; j < dimension; j++) { for (int i = 0; i < dimension; i++) { matrix[dimension * j + i] = computeLanczosCoef(i, j, shape, lobeCount, radius); } } return matrix; } private static double getCoefficientSum(double[] matrix) { double result = matrix[0]; for (int i = 1; i < matrix.Length; i++) { result = result + matrix[i]; } return result; } private static int surfaceExtend(int x, int maxValue) { if (x < 0) { return 0; } if (x > maxValue) { return maxValue; } return x; } private static int surfaceMirror(int x, int maxValue) { if (x < 0) { return - x; } if (x > maxValue) { return 2 * maxValue - x; } return x; } private ColorBgra getSafeSurfacePixel(Surface s, int x, int y) { if ((x >= 0) && (y >= 0) && (x < s.Width) && (y < s.Height)) { return s[x, y]; } if (borderHandling > 0) { if (borderHandling == 1) { // Extend return s[surfaceExtend(x, s.Width - 1), surfaceExtend(y, s.Height - 1)]; } // Mirror return s[surfaceMirror(x, s.Width - 1), surfaceMirror(y, s.Height - 1)]; } // Background color return ColorBgra.Black; } private double[] convolve(Surface s, int x, int y, double[] m, int radius) { double[] bgr = new double[3]; int dimension = 2 * radius - 1; int offset = 0; for (int j = 1; j <= dimension; j++) { for (int i = 1; i <= dimension; i++) { ColorBgra c = getSafeSurfacePixel(s, x + i - radius, y + j - radius); bgr[0] = bgr[0] + c.B * m[offset]; bgr[1] = bgr[1] + c.G * m[offset]; bgr[2] = bgr[2] + c.R * m[offset]; offset = offset + 1; } } return bgr; } private double applyContrast(double x) { return (256 + 10 * (contrastSliderIndex - 5)) * x / 256 - (5 * (contrastSliderIndex - 5)); } void PreRender(Surface dst, Surface src) { // Radius displayed by the slider is actually corresponding lanczos8 kernel radius kernelRadius = (radius * cutoffFactor) / 2; kernelMatrix = createLanczosKernel(kernelShape, 4 * cutoffFactor, kernelRadius); kernelNorm = getCoefficientSum(kernelMatrix); } void Render(Surface dst, Surface src, Rectangle rect) { for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { double[] bgr = convolve(src, x, y, kernelMatrix, kernelRadius); dst[x,y] = ColorBgra.FromBgr(to8bit(applyContrast(bgr[0] / kernelNorm)), to8bit(applyContrast(bgr[1] / kernelNorm)), to8bit(applyContrast(bgr[2] / kernelNorm))); } } } WaveToolBox.zip Edited June 5, 2020 by loupasc 1 Quote Link to comment Share on other sites More sharing options...
Djisves Posted May 30, 2020 Share Posted May 30, 2020 It's best to replace the link on the first post. Always try to keep the first post updated with the latest version of your plugin. When PDN users look for plugins by searching the forum, they should have all the necessary updated information and download links on the first post of the topic. Most don't even read beyond the first post. Have a look at how the more experienced publishers of plugins do it in this section and you'll get the hang of it. 1 Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 31, 2020 Author Share Posted May 31, 2020 (edited) Version 1.3.7456.16099 Computation have been optimized. The kernel matrix has symmetries allowing to divide its storage by 4. Convolution has been optimized too the symmetry allows to factorize multiplications which divide the number of multiplication by four. Spoiler // Name: WaveToolBox // Submenu: Advanced // Author: Pascal Ollive // Title: Wave tool box // Version: 1.3 // Desc: Reconstruction filter tool box // Keywords: convolution|kernel|filter // URL: https://forums.getpaint.net/profile/160055-loupasc/ // Help: #region UICode RadioButtonControl kernelShape = 0; // Shape|Square|Disc|Diamond IntSliderControl cutoffFactor = 2; // [1,4] Cutoff sharpness IntSliderControl radiusSliderIndex = 8; // [8,100] Radius RadioButtonControl borderHandling = 2; // Border handling|Uniform|Extend|Mirror IntSliderControl contrastSliderIndex = 5; // [0,10] Contrast #endregion private double[,] kernelMatrix = null; private double kernelNorm = 1.0; private static byte to8bit(double x) { if (x < 0) { return 0; } if (x > 255) { return 255; } return (byte) Math.Round(x); } private static double lanczos(double x, int lobeCount) { if (x == 0.0) { return 1.0; } double xMultiplyPI = Math.PI * x; return lobeCount * Math.Sin(xMultiplyPI) * Math.Sin(xMultiplyPI / lobeCount) / (xMultiplyPI * xMultiplyPI); } // Returns the matrix coefficient at (i,j) in function of kernel properties // - shape - number of lobes - radius - private static double computeLanczosCoef(int i, int j, byte shape, int lobeCount, byte radius) { if (shape == 0) { // Square // normalize into lanczos window return lanczos((double) lobeCount * i / radius, lobeCount) * lanczos((double) lobeCount * j / radius, lobeCount); } if (shape == 1) { // Disc double d = Math.Sqrt(i * i + j * j); if (d < radius) { // normalize into lanczos window return lanczos(lobeCount * d / radius, lobeCount); } } if (shape == 2) { // Diamond (45 degrees rotated square and SQRT_2-rescaled) double p = (i + j); if (p < radius) { // normalize into lanczos window return lanczos(lobeCount * p / radius, lobeCount) * lanczos(lobeCount * (p - 2 * j) / radius, lobeCount); } } return 0.0; } // Matrix content has symmetries on horizontal axis and vertical axis // => Only a quarter of the matrix is generated // => Possibility to factorize computation allowing to divide number of multiplications by 4 private static double[,] createLanczosKernel(byte shape, int lobeCount, byte radius) { double[,] matrix = new double[radius, radius]; for (int j = 0; j < radius; j++) { for (int i = 0; i < radius; i++) { matrix[i, j] = computeLanczosCoef(i, j, shape, lobeCount, radius); } } return matrix; } private static double getCoefficientSum(double[,] m) { // Assuming dealing with square matrix (GetLength(0) == GetLength(1)) byte radius = (byte) m.GetLength(0); // Let's start with the center coefficient double result = m[0, 0]; // Next loop in the central axis for (int i = 1; i < radius; i++) { result = result + 4 * m[i, 0]; } // Finally the remaining area for (int j = 1; j < radius; j++) { for (int i = 1; i < radius; i++) { result = result + 4 * m[i, j]; } } return result; } private static int surfaceExtend(int x, int maxValue) { if (x < 0) { return 0; } if (x > maxValue) { return maxValue; } return x; } private static int surfaceMirror(int x, int maxValue) { if (x < 0) { return - x; } if (x > maxValue) { return 2 * maxValue - x; } return x; } private ColorBgra getSafeSurfacePixel(Surface s, int x, int y) { if ((x >= 0) && (y >= 0) && (x < s.Width) && (y < s.Height)) { return s[x, y]; } if (borderHandling > 0) { if (borderHandling == 1) { // Extend return s[surfaceExtend(x, s.Width - 1), surfaceExtend(y, s.Height - 1)]; } // Mirror return s[surfaceMirror(x, s.Width - 1), surfaceMirror(y, s.Height - 1)]; } // Background color return ColorBgra.Black; } private double[] convolve(Surface s, int x, int y, double[,] m) { // Assuming dealing with square matrix (GetLength(0) == GetLength(1)) byte radius = (byte) m.GetLength(0); // Let's start with the center coefficient double[] bgr = { s[x, y].B * m[0, 0], s[x, y].G * m[0, 0], s[x, y].R * m[0, 0]}; // Next loop in the central axis for (int i = 1; i < radius; i++) { double coef = m[i, 0]; ColorBgra c1 = getSafeSurfacePixel(s, x - i, y); ColorBgra c2 = getSafeSurfacePixel(s, x + i, y); ColorBgra c3 = getSafeSurfacePixel(s, x, y - i); ColorBgra c4 = getSafeSurfacePixel(s, x, y + i); bgr[0] = bgr[0] + coef * (c1.B + c2.B + c3.B + c4.B); bgr[1] = bgr[1] + coef * (c1.G + c2.G + c3.G + c4.G); bgr[2] = bgr[2] + coef * (c1.R + c2.R + c3.R + c4.R); } // Finally the remaining area for (int j = 1; j < radius; j++) { for (int i = 1; i < radius; i++) { double coef = m[i, j]; ColorBgra c1 = getSafeSurfacePixel(s, x - i, y - j); ColorBgra c2 = getSafeSurfacePixel(s, x + i, y - j); ColorBgra c3 = getSafeSurfacePixel(s, x - i, y + j); ColorBgra c4 = getSafeSurfacePixel(s, x + i, y + j); bgr[0] = bgr[0] + coef * (c1.B + c2.B + c3.B + c4.B); bgr[1] = bgr[1] + coef * (c1.G + c2.G + c3.G + c4.G); bgr[2] = bgr[2] + coef * (c1.R + c2.R + c3.R + c4.R); } } return bgr; } private double applyContrast(double x) { return (256 + 10 * (contrastSliderIndex - 5)) * x / 256 - (5 * (contrastSliderIndex - 5)); } void PreRender(Surface dst, Surface src) { // Radius displayed by the slider is actually corresponding lanczos8 kernel radius byte kernelRadius = (byte) ((radiusSliderIndex * cutoffFactor) / 2); kernelMatrix = createLanczosKernel(kernelShape, 4 * cutoffFactor, kernelRadius); kernelNorm = getCoefficientSum(kernelMatrix); } void Render(Surface dst, Surface src, Rectangle rect) { for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { double[] bgr = convolve(src, x, y, kernelMatrix); dst[x,y] = ColorBgra.FromBgr(to8bit(applyContrast(bgr[0] / kernelNorm)), to8bit(applyContrast(bgr[1] / kernelNorm)), to8bit(applyContrast(bgr[2] / kernelNorm))); } } } Edited June 5, 2020 by loupasc Quote Link to comment Share on other sites More sharing options...
loupasc Posted June 5, 2020 Author Share Posted June 5, 2020 (edited) Version 1.4.7461.19568 Optimization in the rendering method. Test if the rectangle boundary is far enough from the border allowing useless limit tests when getting surface color pixels. Spoiler // Name: WaveToolBox // Submenu: Advanced // Author: Pascal Ollive // Title: Wave tool box // Version: 1.4 // Desc: Reconstruction filter tool box // Keywords: convolution|kernel|filter // URL: https://forums.getpaint.net/profile/160055-loupasc/ // Help: #region UICode RadioButtonControl kernelShape = 0; // Shape|Square|Disc|Diamond IntSliderControl cutoffFactor = 2; // [1,4] Cutoff sharpness IntSliderControl radiusSliderIndex = 8; // [8,100] Radius RadioButtonControl borderHandling = 2; // Border handling|Uniform|Extend|Mirror IntSliderControl contrastSliderIndex = 5; // [0,10] Contrast #endregion private double[,] kernelMatrix = null; private double kernelNorm = 1.0; private static byte to8bit(double x) { if (x < 0) { return 0; } if (x > 255) { return 255; } return (byte) Math.Round(x); } private static double lanczos(double x, int lobeCount) { if (x == 0.0) { return 1.0; } double xMultiplyPI = Math.PI * x; return lobeCount * Math.Sin(xMultiplyPI) * Math.Sin(xMultiplyPI / lobeCount) / (xMultiplyPI * xMultiplyPI); } // Returns the matrix coefficient at (i,j) in function of kernel properties // - shape - number of lobes - radius - private static double computeLanczosCoef(int i, int j, byte shape, int lobeCount, byte radius) { if (shape == 0) { // Square // normalize into lanczos window return lanczos((double) lobeCount * i / radius, lobeCount) * lanczos((double) lobeCount * j / radius, lobeCount); } if (shape == 1) { // Disc double d = Math.Sqrt(i * i + j * j); if (d < radius) { // normalize into lanczos window return lanczos(lobeCount * d / radius, lobeCount); } } if (shape == 2) { // Diamond (45 degrees rotated square and SQRT_2-rescaled) double p = (i + j); if (p < radius) { // normalize into lanczos window return lanczos(lobeCount * p / radius, lobeCount) * lanczos(lobeCount * (p - 2 * j) / radius, lobeCount); } } return 0.0; } // Matrix content has symmetries on horizontal axis and vertical axis // => Only a quarter of the matrix is generated // => Possibility to factorize computation allowing to divide number of multiplications by 4 private static double[,] createLanczosKernel(byte shape, int lobeCount, byte radius) { double[,] matrix = new double[radius, radius]; for (int j = 0; j < radius; j++) { for (int i = 0; i < radius; i++) { matrix[i, j] = computeLanczosCoef(i, j, shape, lobeCount, radius); } } return matrix; } private static double getCoefficientSum(double[,] m) { // Assuming dealing with square matrix (GetLength(0) == GetLength(1)) byte radius = (byte) m.GetLength(0); // Let's start with the center coefficient double result = m[0, 0]; // Next loop in the central axis for (int i = 1; i < radius; i++) { result = result + 4 * m[i, 0]; } // Finally the remaining area for (int j = 1; j < radius; j++) { for (int i = 1; i < radius; i++) { result = result + 4 * m[i, j]; } } return result; } private static int surfaceExtend(int x, int maxValue) { if (x < 0) { return 0; } if (x < maxValue) { return x; } return maxValue - 1; } private static int surfaceMirror(int x, int maxValue) { if (x < 0) { return - x; } if (x < maxValue) { return x; } return 2 * maxValue - x - 1; } private ColorBgra getSafeSurfacePixel(Surface s, int x, int y) { if ((x >= 0) && (y >= 0) && (x < s.Width) && (y < s.Height)) { return s[x, y]; } if (borderHandling > 0) { if (borderHandling == 1) { // Extend return s[surfaceExtend(x, s.Width), surfaceExtend(y, s.Height)]; } // Mirror return s[surfaceMirror(x, s.Width), surfaceMirror(y, s.Height)]; } // Background color return ColorBgra.Black; } private double[] convolve(Surface s, int x, int y, bool isCentralArea, double[,] m, byte radius) { // Let's start with the center coefficient double[] bgr = { s[x, y].B * m[0, 0], s[x, y].G * m[0, 0], s[x, y].R * m[0, 0]}; // Next loop in the central axis for (int i = 1; i < radius; i++) { double coef = m[i, 0]; ColorBgra c1; ColorBgra c2; ColorBgra c3; ColorBgra c4; if (isCentralArea) { c1 = s[x - i, y]; c2 = s[x + i, y]; c3 = s[x, y - i]; c4 = s[x, y + i]; } else { c1 = getSafeSurfacePixel(s, x - i, y); c2 = getSafeSurfacePixel(s, x + i, y); c3 = getSafeSurfacePixel(s, x, y - i); c4 = getSafeSurfacePixel(s, x, y + i); } bgr[0] = bgr[0] + coef * (c1.B + c2.B + c3.B + c4.B); bgr[1] = bgr[1] + coef * (c1.G + c2.G + c3.G + c4.G); bgr[2] = bgr[2] + coef * (c1.R + c2.R + c3.R + c4.R); } // Finally the remaining area for (int j = 1; j < radius; j++) { for (int i = 1; i < radius; i++) { double coef = m[i, j]; ColorBgra c1; ColorBgra c2; ColorBgra c3; ColorBgra c4; if (isCentralArea) { c1 = s[x - i, y - j]; c2 = s[x + i, y - j]; c3 = s[x - i, y + j]; c4 = s[x + i, y + j]; } else { c1 = getSafeSurfacePixel(s, x - i, y - j); c2 = getSafeSurfacePixel(s, x + i, y - j); c3 = getSafeSurfacePixel(s, x - i, y + j); c4 = getSafeSurfacePixel(s, x + i, y + j); } bgr[0] = bgr[0] + coef * (c1.B + c2.B + c3.B + c4.B); bgr[1] = bgr[1] + coef * (c1.G + c2.G + c3.G + c4.G); bgr[2] = bgr[2] + coef * (c1.R + c2.R + c3.R + c4.R); } } return bgr; } private double applyContrast(double x) { return (256 + 10 * (contrastSliderIndex - 5)) * x / 256 - (5 * (contrastSliderIndex - 5)); } void PreRender(Surface dst, Surface src) { // Radius displayed by the slider is actually corresponding lanczos8 kernel radius byte kernelRadius = (byte) ((radiusSliderIndex * cutoffFactor) / 2); kernelMatrix = createLanczosKernel(kernelShape, 4 * cutoffFactor, kernelRadius); kernelNorm = getCoefficientSum(kernelMatrix); } void Render(Surface dst, Surface src, Rectangle rect) { // Assuming dealing with square matrix (GetLength(0) == GetLength(1)) byte radius = (byte) kernelMatrix.GetLength(0); bool isCentralArea = ((rect.Left >= radius) && (rect.Top >= radius) && (rect.Right < src.Width - radius) && (rect.Bottom < src.Height - radius)); for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { double[] bgr = convolve(src, x, y, isCentralArea, kernelMatrix, radius); dst[x,y] = ColorBgra.FromBgr(to8bit(applyContrast(bgr[0] / kernelNorm)), to8bit(applyContrast(bgr[1] / kernelNorm)), to8bit(applyContrast(bgr[2] / kernelNorm))); } } } Edited June 10, 2020 by loupasc Quote Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted June 5, 2020 Share Posted June 5, 2020 When releasing an update, edit the first post in your plugin thread an replace the DLL in it. I'd encourage you to remove all other DLLs from these posts. You've given out the source so there is no need to be offering redundant versions of the DLL. Users expect the first post to have the most recent version of your plugin. It's not a great idea to make people dig through all subsequent posts to find the latest version. Cool? 1 Quote ebook: Mastering Paint.NET | resources: Plugin Index | Stereogram Tut | proud supporter of Codelab plugins: EER's Plugin Pack | Planetoid | StickMan | WhichSymbol+ | Dr Scott's Markup Renderer | CSV Filetype | dwarf horde plugins: Plugin Browser | ShapeMaker Link to comment Share on other sites More sharing options...
loupasc Posted June 10, 2020 Author Share Posted June 10, 2020 (edited) Version 1.5.7466.17310 Fix crash with small image using mirror border handling. Improve accuracy of the matrix coefficients ( Math.Sin( k * Pi ) = 0). Source Spoiler // Name: WaveToolBox // Submenu: Advanced // Author: Pascal Ollive // Title: Wave tool box // Version: 1.5 // Desc: Reconstruction filter tool box // Keywords: convolution|kernel|filter // URL: https://forums.getpaint.net/profile/160055-loupasc/ // Help: #region UICode RadioButtonControl kernelShape = 0; // Shape|Square|Disc|Diamond IntSliderControl cutoffFactor = 2; // [1,4] Cutoff sharpness IntSliderControl radiusSliderIndex = 8; // [8,100] Radius RadioButtonControl borderHandling = 2; // Border handling|Uniform|Extend|Mirror IntSliderControl contrastSliderIndex = 5; // [0,10] Contrast #endregion private double[,] kernelMatrix = null; private double kernelNorm = 1.0; private static byte to8bit(double x) { if (x < 0) { return 0; } if (x > 255) { return 255; } return (byte) Math.Round(x); } private static double lanczos(double x, int lobeCount) { if (x == 0.0) { return 1.0; } double xMultiplyPI = Math.PI * x; return lobeCount * Math.Sin(xMultiplyPI) * Math.Sin(xMultiplyPI / lobeCount) / (xMultiplyPI * xMultiplyPI); } private static double lanczos(int n, int lobeCount, byte radius) { if (n == 0) { return 1.0; } if ((lobeCount * n) % radius == 0) { return 0.0; } // normalize into lanczos window return lanczos((double) lobeCount * n / radius, lobeCount); } private static double lanczos(int i, int j, int lobeCount, byte radius) { if (i == 0) { // lanczos(i, lobeCount, radius) = 1 return lanczos(j, lobeCount, radius); } if (j == 0) { // lanczos(j, lobeCount, radius) = 1 return lanczos(i, lobeCount, radius); } // i and j are not equal to zero if (((lobeCount * i) % radius == 0) || ((lobeCount * j) % radius == 0)) { return 0.0; } return lanczos(i, lobeCount, radius) * lanczos(j, lobeCount, radius); } // Returns the matrix coefficient at (i,j) in function of kernel properties // - shape - number of lobes - radius - private static double computeLanczosCoef(int i, int j, byte shape, int lobeCount, byte radius) { if (shape == 0) { // Square return lanczos(i, j, lobeCount, radius); } if (shape == 1) { // Disc if ((i == 0) || (j == 0)) { return lanczos(i, j, lobeCount, radius); } double d = Math.Sqrt(i * i + j * j); if (d < radius) { // normalize into lanczos window return lanczos(lobeCount * d / radius, lobeCount); } } if (shape == 2) { // Diamond (45 degrees rotated square and SQRT_2-rescaled) int p = (i + j); if (p < radius) { return lanczos(p, i - j, lobeCount, radius); } } return 0.0; } // Matrix content has symmetries on horizontal axis and vertical axis // => Only a quarter of the matrix is generated // => Possibility to factorize computation allowing to divide number of multiplications by 4 private static double[,] createLanczosKernel(byte shape, int lobeCount, byte radius) { double[,] matrix = new double[radius, radius]; for (int j = 0; j < radius; j++) { for (int i = 0; i < radius; i++) { matrix[i, j] = computeLanczosCoef(i, j, shape, lobeCount, radius); } } return matrix; } private static double getCoefficientSum(double[,] m) { // Assuming dealing with square matrix (GetLength(0) == GetLength(1)) byte radius = (byte) m.GetLength(0); // Let's start with the center coefficient double result = m[0, 0]; // Next loop in the central axis for (int i = 1; i < radius; i++) { result = result + 4 * m[i, 0]; } // Finally the remaining area for (int j = 1; j < radius; j++) { for (int i = 1; i < radius; i++) { result = result + 4 * m[i, j]; } } return result; } private static int surfaceExtend(int x, int maxValue) { if (x < 0) { return 0; } if (x < maxValue) { return x; } return maxValue - 1; } private static int surfaceMirror(int x, int maxValue) { int symX; if (x < 0) { symX = - x; if (symX < maxValue) { return symX; } return maxValue - 1; } if (x < maxValue) { return x; } symX = 2 * maxValue - x - 1; if (symX < 0) { return 0; } return symX; } private ColorBgra getSafeSurfacePixel(Surface s, int x, int y) { if ((x >= 0) && (y >= 0) && (x < s.Width) && (y < s.Height)) { return s[x, y]; } if (borderHandling > 0) { if (borderHandling == 1) { // Extend return s[surfaceExtend(x, s.Width), surfaceExtend(y, s.Height)]; } // Mirror return s[surfaceMirror(x, s.Width), surfaceMirror(y, s.Height)]; } // Background color return ColorBgra.Black; } private double[] convolve(Surface s, int x, int y, bool isCentralArea, double[,] m, byte radius) { // Let's start with the center coefficient double[] bgr = { s[x, y].B * m[0, 0], s[x, y].G * m[0, 0], s[x, y].R * m[0, 0]}; // Next loop in the central axis for (int i = 1; i < radius; i++) { double coef = m[i, 0]; ColorBgra c1; ColorBgra c2; ColorBgra c3; ColorBgra c4; if (isCentralArea) { c1 = s[x - i, y]; c2 = s[x + i, y]; c3 = s[x, y - i]; c4 = s[x, y + i]; } else { c1 = getSafeSurfacePixel(s, x - i, y); c2 = getSafeSurfacePixel(s, x + i, y); c3 = getSafeSurfacePixel(s, x, y - i); c4 = getSafeSurfacePixel(s, x, y + i); } bgr[0] = bgr[0] + coef * (c1.B + c2.B + c3.B + c4.B); bgr[1] = bgr[1] + coef * (c1.G + c2.G + c3.G + c4.G); bgr[2] = bgr[2] + coef * (c1.R + c2.R + c3.R + c4.R); } // Finally the remaining area for (int j = 1; j < radius; j++) { for (int i = 1; i < radius; i++) { double coef = m[i, j]; ColorBgra c1; ColorBgra c2; ColorBgra c3; ColorBgra c4; if (isCentralArea) { c1 = s[x - i, y - j]; c2 = s[x + i, y - j]; c3 = s[x - i, y + j]; c4 = s[x + i, y + j]; } else { c1 = getSafeSurfacePixel(s, x - i, y - j); c2 = getSafeSurfacePixel(s, x + i, y - j); c3 = getSafeSurfacePixel(s, x - i, y + j); c4 = getSafeSurfacePixel(s, x + i, y + j); } bgr[0] = bgr[0] + coef * (c1.B + c2.B + c3.B + c4.B); bgr[1] = bgr[1] + coef * (c1.G + c2.G + c3.G + c4.G); bgr[2] = bgr[2] + coef * (c1.R + c2.R + c3.R + c4.R); } } return bgr; } private double applyContrast(double x) { return (256 + 10 * (contrastSliderIndex - 5)) * x / 256 - (5 * (contrastSliderIndex - 5)); } void PreRender(Surface dst, Surface src) { // Radius displayed by the slider is actually corresponding lanczos8 kernel radius byte kernelRadius = (byte) ((radiusSliderIndex * cutoffFactor) / 2); kernelMatrix = createLanczosKernel(kernelShape, 4 * cutoffFactor, kernelRadius); kernelNorm = getCoefficientSum(kernelMatrix); } void Render(Surface dst, Surface src, Rectangle rect) { // Assuming dealing with square matrix (GetLength(0) == GetLength(1)) byte radius = (byte) kernelMatrix.GetLength(0); bool isCentralArea = ((rect.Left >= radius) && (rect.Top >= radius) && (rect.Right <= src.Width - radius) && (rect.Bottom <= src.Height - radius)); for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { double[] bgr = convolve(src, x, y, isCentralArea, kernelMatrix, radius); dst[x,y] = ColorBgra.FromBgr(to8bit(applyContrast(bgr[0] / kernelNorm)), to8bit(applyContrast(bgr[1] / kernelNorm)), to8bit(applyContrast(bgr[2] / kernelNorm))); } } } Edited June 10, 2020 by loupasc Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.