loupasc Posted May 18, 2020 Share Posted May 18, 2020 (edited) Hi ! I wrote an image sharpen plugin. The processing is based on unsharp mask; the particularity of this filter is to use a different function : f(x,y) = 2 * e^(-6(x^2+y^2)) + e^(-9(x^2+y^2)/8) (r=1.5) The processing result seems to look good but I'm not the best judge The image attached come from a pinhole photography and the processing has been applied three times with the maximum amount. // Name: Slight edge boost // Submenu: Photo // Author: Pascal Ollive // Title: Slight edge boost // Version: 1.0 // Desc: Gaussian unsharp mask // Keywords: Sharpening|Gaussian|filter // URL: // Help: #region UICode IntSliderControl sliderValue = 5; // [0,10] Strength #endregion // 2 * Math.exp(-6*x^2+y^2) + Math.exp(-9*x^2+y^2/8) // sum(matrix) = 16384 (2^14) private static short[] KERNEL7x7 = { 0, -3, -14, -23, -14, -3, 0, -3, -38, -172, -284, -172, -38, -3, -14, -172, -793, -1566, -793, -172, -14, -23, -284, -1566, 28712, -1566, -284, -23, -14, -172, -793, -1566, -793, -172, -14, -3, -38, -172, -284, -172, -38, -3, 0, -3, -14, -23, -14, -3, 0 }; // Truncated matrix => border management private static short[] KERNEL5x5 = { -38, -170, -281, -170, -38, -170, -782, -1545, -782, -170, -281, -1545, 28328, -1545, -281, -170, -782, -1545, -782, -170, -38, -170, -281, -170, -38 }; private static short[] KERNEL3x3 = { -1037, -2048, -1037, -2048, 28724, -2048, -1037, -2048, -1037 }; private static short[][] KERNEL_ARRAY = { null, // identity KERNEL3x3, KERNEL5x5, KERNEL7x7 }; private byte strength = 17; // [2,32] // Function applied separately on each BGR component // The result of the sharpness depends of the user input private byte getSharpenedSubPixel(byte srcPixel, int sharpestValue) { int result = ((32 - strength) * (srcPixel << 14) + strength * sharpestValue + 262144) >> 19; if (result < 0) { return 0; } if (result > 255) { return 255; } return (byte) result; } // [ p0 ][center][ p2 ] <= Extend border // [ p0 ][center][ p2 ] (center = p1) // [ p3 ][ p4 ][ p5 ] private ColorBgra convolveBorder(ColorBgra[] arrayOfPoints) { int r = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].R + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].R + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].R + KERNEL3x3[6] * arrayOfPoints[3].R + KERNEL3x3[7] * arrayOfPoints[4].R + KERNEL3x3[8] * arrayOfPoints[5].R; int g = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].G + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].G + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].G + KERNEL3x3[6] * arrayOfPoints[3].G + KERNEL3x3[7] * arrayOfPoints[4].G + KERNEL3x3[8] * arrayOfPoints[5].G; int b = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].B + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].B + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].B + KERNEL3x3[6] * arrayOfPoints[3].B + KERNEL3x3[7] * arrayOfPoints[4].B + KERNEL3x3[8] * arrayOfPoints[5].B; return ColorBgra.FromBgr(getSharpenedSubPixel(arrayOfPoints[1].B, b), getSharpenedSubPixel(arrayOfPoints[1].G, g), getSharpenedSubPixel(arrayOfPoints[1].R, r)); } private void copyBorderLeft(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x, y - 1]; dest[1] = src[x, y]; dest[2] = src[x, y + 1]; dest[3] = src[x + 1, y - 1]; dest[4] = src[x + 1, y]; dest[5] = src[x + 1, y + 1]; } private void copyBorderRight(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x, y - 1]; dest[1] = src[x, y]; dest[2] = src[x, y + 1]; dest[3] = src[x - 1, y - 1]; dest[4] = src[x - 1, y]; dest[5] = src[x - 1, y + 1]; } private void copyBorderTop(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x - 1, y]; dest[1] = src[x, y]; dest[2] = src[x + 1, y]; dest[3] = src[x - 1, y + 1]; dest[4] = src[x, y + 1]; dest[5] = src[x + 1, y + 1]; } private void copyBorderBottom(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x - 1, y]; dest[1] = src[x, y]; dest[2] = src[x + 1, y]; dest[3] = src[x - 1, y - 1]; dest[4] = src[x, y - 1]; dest[5] = src[x + 1, y - 1]; } private ColorBgra convolve(Surface src, int x, int y, byte radius) { short[] kernel = KERNEL_ARRAY[radius]; int r = 0; int g = 0; int b = 0; int offset = 0; for (int j = y - radius; j <= y + radius; j++) { for (int i = x - radius; i <= x + radius; i++) { ColorBgra srcPixel = src[i, j]; r = r + kernel[offset] * srcPixel.R; g = g + kernel[offset] * srcPixel.G; b = b + kernel[offset] * srcPixel.B; offset = offset + 1; } } return ColorBgra.FromBgr(getSharpenedSubPixel(src[x, y].B, b), getSharpenedSubPixel(src[x, y].G, g), getSharpenedSubPixel(src[x, y].R, r)); } private ColorBgra processPixel(Surface src, int x, int y, Rectangle bounds, ColorBgra[] arrayOfPoints) { if (((x == bounds.Left) || (x == bounds.Right - 1)) && ((y == bounds.Top) || (y == bounds.Bottom - 1))) { // Extreme corner => identity return src[x, y]; } if (x == bounds.Left) { copyBorderLeft(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (x == bounds.Right - 1) { copyBorderRight(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (y == bounds.Top) { copyBorderTop(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (y == bounds.Bottom - 1) { copyBorderBottom(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if ((x == bounds.Left + 1) || (x == bounds.Right - 2) || (y == bounds.Top + 1) || (y == bounds.Bottom - 2)) { return convolve(src, x, y, 1); } if ((x == bounds.Left + 2) || (x == bounds.Right - 3) || (y == bounds.Top + 2) || (y == bounds.Bottom - 3)) { return convolve(src, x, y, 2); } return convolve(src, x, y, 3); } void PreRender(Surface dst, Surface src) { // 10 => 32 for fast division strength = (byte) (3 * sliderValue + 2); } void Render(Surface dst, Surface src, Rectangle rect) { ColorBgra[] borderPixels = new ColorBgra[6]; for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { dst[x, y] = processPixel(src, x, y, rect, borderPixels); } } } Edited May 21, 2020 by loupasc Delete old plugin archive 1 1 Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 20, 2020 Author Share Posted May 20, 2020 (edited) Hello, I updated this plugin and I added an extra sharpen strength. // Name: Slight edge boost // Submenu: Photo // Author: Pascal Ollive // Title: Slight edge boost // Version: 1.1 // Desc: Gaussian unsharp mask // Keywords: Sharpening|Gaussian|filter // URL: // Help: #region UICode IntSliderControl sliderValue = 5; // [0,11] Strength #endregion // sliderValue in [0,10] // 2 * Math.exp(-6*x^2+y^2) + Math.exp(-9*x^2+y^2/8) r=1.5 // sum(matrix) = 16384 (2^14) private static short[] SHARPER_KERNEL = { 0, -3, -14, -23, -14, -3, 0, -3, -38, -172, -284, -172, -38, -3, -14, -172, -793, -1566, -793, -172, -14, -23, -284, -1566, 28712, -1566, -284, -23, -14, -172, -793, -1566, -793, -172, -14, -3, -38, -172, -284, -172, -38, -3, 0, -3, -14, -23, -14, -3, 0 }; // sliderValue = 11 // 2 * Math.exp(-6*x^2+y^2) + Math.exp(-9*x^2+y^2/8) r=2 // sum(matrix) = 16384 private static short[] SHARPEST_KERNEL = { -8, -35, -80, -107, -80, -35, -8, -35, -141, -330, -442, -330, -141, -35, -80, -330, -897, -1610, -897, -330, -80, -107, -442, -1610, 32764, -1610, -442, -107, -80, -330, -897, -1610, -897, -330, -80, -35, -141, -330, -442, -330, -141, -35, -8, -35, -80, -107, -80, -35, -8 }; // Truncated matrix => border management private static short[] KERNEL5x5 = { -38, -170, -281, -170, -38, -170, -782, -1545, -782, -170, -281, -1545, 28328, -1545, -281, -170, -782, -1545, -782, -170, -38, -170, -281, -170, -38 }; private static short[] KERNEL3x3 = { -1037, -2048, -1037, -2048, 28724, -2048, -1037, -2048, -1037 }; private short[][] sharpenKernel = { null, // identity KERNEL3x3, KERNEL5x5, null // SHARPER_KERNEL or SHARPEST_KERNEL }; private byte strength = 17; // [2,32] // Function applied separately on each BGR component // The result of the sharpness depends of the user input private byte getSharpenedSubPixel(byte srcPixel, int sharpestValue) { int result = ((32 - strength) * (srcPixel << 14) + strength * sharpestValue + 262144) >> 19; if (result < 0) { return 0; } if (result > 255) { return 255; } return (byte) result; } // [ p0 ][center][ p2 ] <= Extend border // [ p0 ][center][ p2 ] (center = p1) // [ p3 ][ p4 ][ p5 ] private ColorBgra convolveBorder(ColorBgra[] arrayOfPoints) { int r = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].R + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].R + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].R + KERNEL3x3[6] * arrayOfPoints[3].R + KERNEL3x3[7] * arrayOfPoints[4].R + KERNEL3x3[8] * arrayOfPoints[5].R; int g = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].G + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].G + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].G + KERNEL3x3[6] * arrayOfPoints[3].G + KERNEL3x3[7] * arrayOfPoints[4].G + KERNEL3x3[8] * arrayOfPoints[5].G; int b = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].B + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].B + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].B + KERNEL3x3[6] * arrayOfPoints[3].B + KERNEL3x3[7] * arrayOfPoints[4].B + KERNEL3x3[8] * arrayOfPoints[5].B; return ColorBgra.FromBgr(getSharpenedSubPixel(arrayOfPoints[1].B, b), getSharpenedSubPixel(arrayOfPoints[1].G, g), getSharpenedSubPixel(arrayOfPoints[1].R, r)); } private void copyBorderLeft(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x, y - 1]; dest[1] = src[x, y]; dest[2] = src[x, y + 1]; dest[3] = src[x + 1, y - 1]; dest[4] = src[x + 1, y]; dest[5] = src[x + 1, y + 1]; } private void copyBorderRight(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x, y - 1]; dest[1] = src[x, y]; dest[2] = src[x, y + 1]; dest[3] = src[x - 1, y - 1]; dest[4] = src[x - 1, y]; dest[5] = src[x - 1, y + 1]; } private void copyBorderTop(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x - 1, y]; dest[1] = src[x, y]; dest[2] = src[x + 1, y]; dest[3] = src[x - 1, y + 1]; dest[4] = src[x, y + 1]; dest[5] = src[x + 1, y + 1]; } private void copyBorderBottom(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x - 1, y]; dest[1] = src[x, y]; dest[2] = src[x + 1, y]; dest[3] = src[x - 1, y - 1]; dest[4] = src[x, y - 1]; dest[5] = src[x + 1, y - 1]; } private ColorBgra convolve(Surface src, int x, int y, byte radius) { short[] kernel = sharpenKernel[radius]; int r = 0; int g = 0; int b = 0; int offset = 0; for (int j = y - radius; j <= y + radius; j++) { for (int i = x - radius; i <= x + radius; i++) { ColorBgra srcPixel = src[i, j]; r = r + kernel[offset] * srcPixel.R; g = g + kernel[offset] * srcPixel.G; b = b + kernel[offset] * srcPixel.B; offset = offset + 1; } } return ColorBgra.FromBgr(getSharpenedSubPixel(src[x, y].B, b), getSharpenedSubPixel(src[x, y].G, g), getSharpenedSubPixel(src[x, y].R, r)); } private ColorBgra processPixel(Surface src, int x, int y, Rectangle bounds, ColorBgra[] arrayOfPoints) { if (((x == bounds.Left) || (x == bounds.Right - 1)) && ((y == bounds.Top) || (y == bounds.Bottom - 1))) { // Extreme corner => identity return src[x, y]; } if (x == bounds.Left) { copyBorderLeft(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (x == bounds.Right - 1) { copyBorderRight(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (y == bounds.Top) { copyBorderTop(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (y == bounds.Bottom - 1) { copyBorderBottom(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if ((x == bounds.Left + 1) || (x == bounds.Right - 2) || (y == bounds.Top + 1) || (y == bounds.Bottom - 2)) { return convolve(src, x, y, 1); } if ((x == bounds.Left + 2) || (x == bounds.Right - 3) || (y == bounds.Top + 2) || (y == bounds.Bottom - 3)) { return convolve(src, x, y, 2); } return convolve(src, x, y, 3); } void PreRender(Surface dst, Surface src) { if (sliderValue > 10) { strength = 32; sharpenKernel[3] = SHARPEST_KERNEL; } else { // 10 => 32 for fast division strength = (byte) (3 * sliderValue + 2); sharpenKernel[3] = SHARPER_KERNEL; } } void Render(Surface dst, Surface src, Rectangle rect) { ColorBgra[] borderPixels = new ColorBgra[6]; for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { dst[x, y] = processPixel(src, x, y, rect, borderPixels); } } } Edited May 21, 2020 by loupasc Delete old plugin archive 1 Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 21, 2020 Author Share Posted May 21, 2020 (edited) The unsharp amount has been increased causing coefficients being outside short integer range => values are now stored into 32-bit integer. The amount has been tuned; the goal is get the plugin sample photo sharp using the plugin once. The extra amount has been removed (this was a bad idea ^^) in order to keep the code simple. Smaller kernel matrix have been computed from the 7x7 kernel, 5x5 matrix loose the extreme border coefficients which are no longer compensated by the center coefficient => border coefficients keep the same value and the center value decreases, and idem for 3x3 matrix. TODO : improve border management in order avoid filter artifact on non-rectangular selection. // Name: Slight edge boost // Submenu: Photo // Author: Pascal Ollive // Title: Slight edge boost // Version: 1.2 // Desc: Gaussian unsharp mask // Keywords: Sharpening|Gaussian|filter // URL: // Help: #region UICode IntSliderControl sliderValue = 5; // [0,10] Strength #endregion // 2 * Math.exp(-6*x^2+y^2) + Math.exp(-9*x^2+y^2/8) r=1.5 // sum(matrix) = 16384 // factor = 16384 private static int[] KERNEL7x7 = { -1, -8, -38, -62, -38, -8, -1, -8, -102, -458, -755, -458, -102, -8, -38, -458, -2105, -4158, -2105, -458, -38, -62, -755, -4158, 49148, -4158, -755, -62, -38, -458, -2105, -4158, -2105, -458, -38, -8, -102, -458, -755, -458, -102, -8, -1, -8, -38, -62, -38, -8, -1 }; // Truncated matrix => border management private static int[] KERNEL5x5 = { -102, -458, -755, -458, -102, -458, -2105, -4158, -2105, -458, -755, -4158, 48528, -4158, -755, -458, -2105, -4158, -2105, -458, -102, -458, -755, -458, -102 }; private static int[] KERNEL3x3 = { -2105, -4158, -2105, -4158, 41436, -4158, -2105, -4158, -2105 }; private static int[][] KERNEL_ARRAY = { null, // identity KERNEL3x3, KERNEL5x5, KERNEL7x7 }; private byte strength = 17; // [2,32] // Function applied separately on each BGR component // The result of the sharpness depends of the user input private byte getSharpenedSubPixel(byte srcPixel, int sharpestValue) { int result = ((32 - strength) * (srcPixel << 14) + strength * sharpestValue + 262144) >> 19; if (result < 0) { return 0; } if (result > 255) { return 255; } return (byte) result; } // [ p0 ][center][ p2 ] <= Extend border // [ p0 ][center][ p2 ] (center = p1) // [ p3 ][ p4 ][ p5 ] private ColorBgra convolveBorder(ColorBgra[] arrayOfPoints) { int r = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].R + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].R + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].R + KERNEL3x3[6] * arrayOfPoints[3].R + KERNEL3x3[7] * arrayOfPoints[4].R + KERNEL3x3[8] * arrayOfPoints[5].R; int g = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].G + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].G + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].G + KERNEL3x3[6] * arrayOfPoints[3].G + KERNEL3x3[7] * arrayOfPoints[4].G + KERNEL3x3[8] * arrayOfPoints[5].G; int b = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].B + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].B + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].B + KERNEL3x3[6] * arrayOfPoints[3].B + KERNEL3x3[7] * arrayOfPoints[4].B + KERNEL3x3[8] * arrayOfPoints[5].B; return ColorBgra.FromBgr(getSharpenedSubPixel(arrayOfPoints[1].B, b), getSharpenedSubPixel(arrayOfPoints[1].G, g), getSharpenedSubPixel(arrayOfPoints[1].R, r)); } private void copyBorderLeft(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x, y - 1]; dest[1] = src[x, y]; dest[2] = src[x, y + 1]; dest[3] = src[x + 1, y - 1]; dest[4] = src[x + 1, y]; dest[5] = src[x + 1, y + 1]; } private void copyBorderRight(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x, y - 1]; dest[1] = src[x, y]; dest[2] = src[x, y + 1]; dest[3] = src[x - 1, y - 1]; dest[4] = src[x - 1, y]; dest[5] = src[x - 1, y + 1]; } private void copyBorderTop(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x - 1, y]; dest[1] = src[x, y]; dest[2] = src[x + 1, y]; dest[3] = src[x - 1, y + 1]; dest[4] = src[x, y + 1]; dest[5] = src[x + 1, y + 1]; } private void copyBorderBottom(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x - 1, y]; dest[1] = src[x, y]; dest[2] = src[x + 1, y]; dest[3] = src[x - 1, y - 1]; dest[4] = src[x, y - 1]; dest[5] = src[x + 1, y - 1]; } private ColorBgra convolve(Surface src, int x, int y, byte radius) { int[] kernel = KERNEL_ARRAY[radius]; int r = 0; int g = 0; int b = 0; int offset = 0; for (int j = y - radius; j <= y + radius; j++) { for (int i = x - radius; i <= x + radius; i++) { ColorBgra srcPixel = src[i, j]; r = r + kernel[offset] * srcPixel.R; g = g + kernel[offset] * srcPixel.G; b = b + kernel[offset] * srcPixel.B; offset = offset + 1; } } return ColorBgra.FromBgr(getSharpenedSubPixel(src[x, y].B, b), getSharpenedSubPixel(src[x, y].G, g), getSharpenedSubPixel(src[x, y].R, r)); } private ColorBgra processPixel(Surface src, int x, int y, Rectangle bounds, ColorBgra[] arrayOfPoints) { if (((x == bounds.Left) || (x == bounds.Right - 1)) && ((y == bounds.Top) || (y == bounds.Bottom - 1))) { // Extreme corner => identity return src[x, y]; } if (x == bounds.Left) { copyBorderLeft(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (x == bounds.Right - 1) { copyBorderRight(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (y == bounds.Top) { copyBorderTop(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (y == bounds.Bottom - 1) { copyBorderBottom(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if ((x == bounds.Left + 1) || (x == bounds.Right - 2) || (y == bounds.Top + 1) || (y == bounds.Bottom - 2)) { return convolve(src, x, y, 1); } if ((x == bounds.Left + 2) || (x == bounds.Right - 3) || (y == bounds.Top + 2) || (y == bounds.Bottom - 3)) { return convolve(src, x, y, 2); } return convolve(src, x, y, 3); } void PreRender(Surface dst, Surface src) { // 10 => 32 for fast division strength = (byte) (3 * sliderValue + 2); } void Render(Surface dst, Surface src, Rectangle rect) { ColorBgra[] borderPixels = new ColorBgra[6]; for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { dst[x, y] = processPixel(src, x, y, rect, borderPixels); } } } Edited May 21, 2020 by loupasc Delete old plugin archive Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 21, 2020 Author Share Posted May 21, 2020 (edited) I just realized that I can get surface dimension with Width and Height properties. Sorry for the last release... The good new is that the artifact on non-rectangular selection is fixed ! Changes are minor but I think it's good to post. // Name: Slight edge boost // Submenu: Photo // Author: Pascal Ollive // Title: Slight edge boost // Version: 1.3 // Desc: Gaussian unsharp mask // Keywords: Sharpening|Gaussian|filter // URL: // Help: #region UICode IntSliderControl sliderValue = 5; // [0,10] Strength #endregion // 2 * Math.exp(-6*x^2+y^2) + Math.exp(-9*x^2+y^2/8) r=1.5 // sum(matrix) = 16384 // factor = 16384 private static int[] KERNEL7x7 = { -1, -8, -38, -62, -38, -8, -1, -8, -102, -458, -755, -458, -102, -8, -38, -458, -2105, -4158, -2105, -458, -38, -62, -755, -4158, 49148, -4158, -755, -62, -38, -458, -2105, -4158, -2105, -458, -38, -8, -102, -458, -755, -458, -102, -8, -1, -8, -38, -62, -38, -8, -1 }; // Truncated matrix => border management private static int[] KERNEL5x5 = { -102, -458, -755, -458, -102, -458, -2105, -4158, -2105, -458, -755, -4158, 48528, -4158, -755, -458, -2105, -4158, -2105, -458, -102, -458, -755, -458, -102 }; private static int[] KERNEL3x3 = { -2105, -4158, -2105, -4158, 41436, -4158, -2105, -4158, -2105 }; private static int[][] KERNEL_ARRAY = { null, // identity KERNEL3x3, KERNEL5x5, KERNEL7x7 }; private byte strength = 17; // [2,32] // Function applied separately on each BGR component // The result of the sharpness depends of the user input private byte getSharpenedSubPixel(byte srcPixel, int sharpestValue) { int result = ((32 - strength) * (srcPixel << 14) + strength * sharpestValue + 262144) >> 19; if (result < 0) { return 0; } if (result > 255) { return 255; } return (byte) result; } // [ p0 ][center][ p2 ] <= Extend border // [ p0 ][center][ p2 ] (center = p1) // [ p3 ][ p4 ][ p5 ] private ColorBgra convolveBorder(ColorBgra[] arrayOfPoints) { int r = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].R + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].R + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].R + KERNEL3x3[6] * arrayOfPoints[3].R + KERNEL3x3[7] * arrayOfPoints[4].R + KERNEL3x3[8] * arrayOfPoints[5].R; int g = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].G + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].G + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].G + KERNEL3x3[6] * arrayOfPoints[3].G + KERNEL3x3[7] * arrayOfPoints[4].G + KERNEL3x3[8] * arrayOfPoints[5].G; int b = (KERNEL3x3[0] + KERNEL3x3[3]) * arrayOfPoints[0].B + (KERNEL3x3[1] + KERNEL3x3[4]) * arrayOfPoints[1].B + (KERNEL3x3[2] + KERNEL3x3[5]) * arrayOfPoints[2].B + KERNEL3x3[6] * arrayOfPoints[3].B + KERNEL3x3[7] * arrayOfPoints[4].B + KERNEL3x3[8] * arrayOfPoints[5].B; return ColorBgra.FromBgr(getSharpenedSubPixel(arrayOfPoints[1].B, b), getSharpenedSubPixel(arrayOfPoints[1].G, g), getSharpenedSubPixel(arrayOfPoints[1].R, r)); } private void copyBorderLeft(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x, y - 1]; dest[1] = src[x, y]; dest[2] = src[x, y + 1]; dest[3] = src[x + 1, y - 1]; dest[4] = src[x + 1, y]; dest[5] = src[x + 1, y + 1]; } private void copyBorderRight(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x, y - 1]; dest[1] = src[x, y]; dest[2] = src[x, y + 1]; dest[3] = src[x - 1, y - 1]; dest[4] = src[x - 1, y]; dest[5] = src[x - 1, y + 1]; } private void copyBorderTop(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x - 1, y]; dest[1] = src[x, y]; dest[2] = src[x + 1, y]; dest[3] = src[x - 1, y + 1]; dest[4] = src[x, y + 1]; dest[5] = src[x + 1, y + 1]; } private void copyBorderBottom(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x - 1, y]; dest[1] = src[x, y]; dest[2] = src[x + 1, y]; dest[3] = src[x - 1, y - 1]; dest[4] = src[x, y - 1]; dest[5] = src[x + 1, y - 1]; } private ColorBgra convolve(Surface src, int x, int y, byte radius) { int[] kernel = KERNEL_ARRAY[radius]; int r = 0; int g = 0; int b = 0; int offset = 0; for (int j = y - radius; j <= y + radius; j++) { for (int i = x - radius; i <= x + radius; i++) { ColorBgra srcPixel = src[i, j]; r = r + kernel[offset] * srcPixel.R; g = g + kernel[offset] * srcPixel.G; b = b + kernel[offset] * srcPixel.B; offset = offset + 1; } } return ColorBgra.FromBgr(getSharpenedSubPixel(src[x, y].B, b), getSharpenedSubPixel(src[x, y].G, g), getSharpenedSubPixel(src[x, y].R, r)); } private ColorBgra processPixel(Surface src, int x, int y, ColorBgra[] arrayOfPoints) { if (((x == 0) || (x == src.Width - 1)) && ((y == 0) || (y == src.Height - 1))) { // Extreme corner => identity return src[x, y]; } if (x == 0) { copyBorderLeft(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (x == src.Width - 1) { copyBorderRight(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (y == 0) { copyBorderTop(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (y == src.Height - 1) { copyBorderBottom(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if ((x == 1) || (x == src.Width - 2) || (y == 1) || (y == src.Height - 2)) { return convolve(src, x, y, 1); } if ((x == 2) || (x == src.Width - 3) || (y == 2) || (y == src.Height - 3)) { return convolve(src, x, y, 2); } return convolve(src, x, y, 3); } void PreRender(Surface dst, Surface src) { // 10 => 32 for fast division strength = (byte) (3 * sliderValue + 2); } void Render(Surface dst, Surface src, Rectangle rect) { ColorBgra[] borderPixels = new ColorBgra[6]; for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { dst[x, y] = processPixel(src, x, y, borderPixels); } } } Edited May 27, 2020 by loupasc Delete old plugin archive Quote Link to comment Share on other sites More sharing options...
loupasc Posted May 27, 2020 Author Share Posted May 27, 2020 (edited) Hi ! I modified the engine of my sharpening plugin, the sharpen amount is now following the standard. I changed how the sharpen is applied. The slider has a nonlinear effect on result allowing to have more range. (Math enthusiast will spot easily the sequence of values used). And the source code. // Name: Slight edge boost // Submenu: Photo // Author: Pascal Ollive // Title: Slight edge boost // Version: 1.4 // Desc: Gaussian unsharp mask // Keywords: Sharpening|Gaussian|filter // URL: // Help: #region UICode IntSliderControl sliderValue = 5; // [0,10] Strength #endregion // 10 amounts depending of sliderValue private static byte[] AMOUNT = {2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233}; // 2 * Math.exp(-6*x^2+y^2) + Math.exp(-9*x^2+y^2/8) r=1.5 // sum(matrix) = 0 // factor = 65536 private static int[] KERNEL7x7 = { -3, -34, -150, -248, -150, -34, -3, -34, -409, -1831, -3021, -1831, -409, -34, -150, -1831, -8423, -16633, -8423, -1831, -150, -248, -3021, -16633, 131068, -16633, -3021, -248, -150, -1831, -8423, -16633, -8423, -1831, -150, -34, -409, -1831, -3021, -1831, -409, -34, -3, -34, - 150, -248, -150, -34, -3 }; // Truncated matrix => border management private static int[] KERNEL5x5 = { -409, -1831, -3021, -1831, -409, -1831, -8423, -16633, -8423, -1831, -3021, -16633, 128592, -16633, -3021, -1831, -8423, -16633, -8423, -1831, -409, -1831, -3021, -1831, -409 }; private static int[] KERNEL3x3 = { -8423, -16633, -8423, -16633, 100224, -16633, -8423, -16633, -8423 }; private static int[][] KERNEL_ARRAY = { null, // identity KERNEL3x3, KERNEL5x5, KERNEL7x7 }; private long strength = 21; // [2,233] // Utility function to provide result in 8-bit range // strength 2^5 // factor 2^16 // 21-bit scale private static byte to8bit(long n) { long result = (n + 1048576) >> 21; if (result > 255) { return 255; } if (result < 0) { return 0; } return (byte) result; } // [ p0 ][center][ p2 ] <= Extend border // [ p0 ][center][ p2 ] (center = p1) // [ p3 ][ p4 ][ p5 ] private ColorBgra convolveBorder(ColorBgra[] arrayOfPoints) { long r = (KERNEL3x3[0] + KERNEL3x3[3]) * strength * arrayOfPoints[0].R + ((KERNEL3x3[1] + KERNEL3x3[4]) * strength + 2097152) * arrayOfPoints[1].R + (KERNEL3x3[2] + KERNEL3x3[5]) *strength * arrayOfPoints[2].R + KERNEL3x3[6] * strength * arrayOfPoints[3].R + KERNEL3x3[7] * strength * arrayOfPoints[4].R + KERNEL3x3[8] * strength * arrayOfPoints[5].R; long g = (KERNEL3x3[0] + KERNEL3x3[3]) * strength * arrayOfPoints[0].G + ((KERNEL3x3[1] + KERNEL3x3[4]) * strength + 2097152) * arrayOfPoints[1].G + (KERNEL3x3[2] + KERNEL3x3[5]) * strength * arrayOfPoints[2].G + KERNEL3x3[6] * strength * arrayOfPoints[3].G + KERNEL3x3[7] * strength * arrayOfPoints[4].G + KERNEL3x3[8] * strength * arrayOfPoints[5].G; long b = (KERNEL3x3[0] + KERNEL3x3[3]) * strength * arrayOfPoints[0].B + ((KERNEL3x3[1] + KERNEL3x3[4]) * strength + 2097152) * arrayOfPoints[1].B + (KERNEL3x3[2] + KERNEL3x3[5]) * strength * arrayOfPoints[2].B + KERNEL3x3[6] * strength * arrayOfPoints[3].B + KERNEL3x3[7] * strength * arrayOfPoints[4].B + KERNEL3x3[8] * strength * arrayOfPoints[5].B; return ColorBgra.FromBgr(to8bit(b), to8bit(g), to8bit(r)); } private void copyBorderLeft(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x, y - 1]; dest[1] = src[x, y]; dest[2] = src[x, y + 1]; dest[3] = src[x + 1, y - 1]; dest[4] = src[x + 1, y]; dest[5] = src[x + 1, y + 1]; } private void copyBorderRight(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x, y - 1]; dest[1] = src[x, y]; dest[2] = src[x, y + 1]; dest[3] = src[x - 1, y - 1]; dest[4] = src[x - 1, y]; dest[5] = src[x - 1, y + 1]; } private void copyBorderTop(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x - 1, y]; dest[1] = src[x, y]; dest[2] = src[x + 1, y]; dest[3] = src[x - 1, y + 1]; dest[4] = src[x, y + 1]; dest[5] = src[x + 1, y + 1]; } private void copyBorderBottom(Surface src, int x, int y, ColorBgra[] dest) { dest[0] = src[x - 1, y]; dest[1] = src[x, y]; dest[2] = src[x + 1, y]; dest[3] = src[x - 1, y - 1]; dest[4] = src[x, y - 1]; dest[5] = src[x + 1, y - 1]; } private ColorBgra convolve(Surface src, int x, int y, byte radius) { int[] kernel = KERNEL_ARRAY[radius]; long r = src[x, y].R << 21; long g = src[x, y].G << 21; long b = src[x, y].B << 21; int offset = 0; for (int j = y - radius; j <= y + radius; j++) { for (int i = x - radius; i <= x + radius; i++) { ColorBgra srcPixel = src[i, j]; r = r + kernel[offset] * strength * srcPixel.R; g = g + kernel[offset] * strength * srcPixel.G; b = b + kernel[offset] * strength * srcPixel.B; offset = offset + 1; } } return ColorBgra.FromBgr(to8bit(b), to8bit(g), to8bit(r)); } private ColorBgra processPixel(Surface src, int x, int y, ColorBgra[] arrayOfPoints) { if (((x == 0) || (x == src.Width - 1)) && ((y == 0) || (y == src.Height - 1))) { // Extreme corner => identity return src[x, y]; } if (x == 0) { copyBorderLeft(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (x == src.Width - 1) { copyBorderRight(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (y == 0) { copyBorderTop(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if (y == src.Height - 1) { copyBorderBottom(src, x, y, arrayOfPoints); return convolveBorder(arrayOfPoints); } if ((x == 1) || (x == src.Width - 2) || (y == 1) || (y == src.Height - 2)) { return convolve(src, x, y, 1); } if ((x == 2) || (x == src.Width - 3) || (y == 2) || (y == src.Height - 3)) { return convolve(src, x, y, 2); } return convolve(src, x, y, 3); } void PreRender(Surface dst, Surface src) { // strength 5-bit precision [2/32 .. 233/32] strength = AMOUNT[sliderValue]; } void Render(Surface dst, Surface src, Rectangle rect) { ColorBgra[] borderPixels = new ColorBgra[6]; for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { dst[x, y] = processPixel(src, x, y, borderPixels); } } } SlightEdgeBoost.zip Edited May 27, 2020 by loupasc 1 1 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.