Slight Edge Boost

Recommended Posts

Slight edge boost

Last version 1.6.7489.24864 [ 06 July 2020 ]

Location: Effects -> Photo

Corresponding source is available in the last post.

Plugin description

This plugin enhances image edges using unsharp mask method.

It provides the possibility to apply sharpen only to the image luminosity in order to avoid chromatic noise amplification.

Illustration on a stenope (blurry) photography

Spoiler

Edited by loupasc
New version 1.6.7489.24864
• 1
• 2
Share on other sites

Version 1.5.7467.22643

Source

Spoiler
```
// Name: Slight edge boost
// Author: Pascal Ollive
// Title: Slight edge boost
// Version: 1.5
// Keywords: Sharpening|Gaussian|filter
// URL: https://forums.getpaint.net/profile/160055-loupasc/
// Help:
#region UICode
RadioButtonControl colorSpace = 0; // Select sharpen target|RGB (all)|YUV (Y only)
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 = createKernelMatrix();

private static double gaussian(double x, double y) {
double sqr = x * x + y * y;
return 2 * Math.Exp(-6 * sqr) + Math.Exp(-9 * sqr / 8);
}

private static int[,] createKernelMatrix() {
int[,] matrix = new int[7, 7];
for (int j = 0; j < 7; j++) {
for (int i = 0; i < 7; i++) {
// support 2 => 1.5
matrix[i, j] = (int) Math.Round(-22311.3 * gaussian(2 * (i - 3)/ 3.0, 2 * (j - 3)/ 3.0));
}
}
//
// sum(matrix) = 0
matrix[3, 3] = 131068;
return matrix;
}

private byte strength = 21; // [2,233]

// ---------------------------
// -- Color space functions --
// ---------------------------

// -- RGB to YUV --

// out [0 .. 255]
private static double bgrToY(ColorBgra c) {
return 0.299 * c.R + 0.587 * c.G + 0.114 * c.B;
}

// out [-111.18 .. 111.18]
private static double bgrToU(ColorBgra c) {
return -0.14714 * c.R - 0.28886 * c.G + 0.436 * c.B;
}

// out [-156.825 .. 156.825]
private static double bgrToV(ColorBgra c) {
return 0.615 * c.R - 0.51499 * c.G - 0.10001 * c.B;
}

// -- YUV to RGB --

private static byte to8bit(double x) {
if (x < 0) {
return 0;
}
if (x > 255) {
return 255;
}
return (byte) Math.Round(x);
}

private static ColorBgra yuvToBgr(double y, double u, double v) {
byte b = to8bit(y + 2.032078 * u); // 0.299/0.14714
byte g = to8bit(y - 0.39465 * u - 0.5806 * v);
byte r = to8bit(y + 1.139827 * v); // 0.587/0.51499

return ColorBgra.FromBgr(b, g, r);
}

//
// ---------------------------
//

private static int surfaceExtend(int x, int maxValue) {
if (x < 0) {
return 0;
}
if (x < maxValue) {
return x;
}
return maxValue - 1;
}

private static ColorBgra getSafeSurfacePixel(Surface s, int x, int y) {
if ((x >= 0) && (y >= 0) && (x < s.Width) && (y < s.Height)) {
return s[x, y];
}
return s[surfaceExtend(x, s.Width), surfaceExtend(y, s.Height)];
}

// Utility function to provide result in 8-bit range
// strength 2^5
// factor 2^16
// 21-bit scale
private static byte to8bit(long n) {
if (n > 535822335) {
return 255;
}
if (n < 0) {
return 0;
}
return (byte) ((n + 1048576) >> 21);
}

private ColorBgra convolveRGB(Surface src, int x, int y, bool isCentralArea) {
// strength 2^5 and factor 2^16 => 21-bit scale
long r = src[x, y].R << 21;
long g = src[x, y].G << 21;
long b = src[x, y].B << 21;

for (int j = 0; j < 7; j++) {
int posY = y + j - 3;
for (int i = 0; i < 7; i++) {
int posX = x + i - 3;
long coef = KERNEL7x7[i, j] * strength;
ColorBgra srcPixel = (isCentralArea ? src[posX, posY]
: getSafeSurfacePixel(src, posX, posY));
r = r + coef * srcPixel.R;
g = g + coef * srcPixel.G;
b = b + coef * srcPixel.B;
}
}

return ColorBgra.FromBgr(to8bit(b), to8bit(g), to8bit(r));
}

private ColorBgra convolveYUV(Surface src, int x, int y, bool isCentralArea) {
// strength 2^5 and factor 2^16 => 2^21 (2097152)
double totalLuma = bgrToY(src[x, y]) * 2097152.0;
double u = bgrToU(src[x, y]);
double v = bgrToV(src[x, y]);

for (int j = 0; j < 7; j++) {
int posY = y + j - 3;
for (int i = 0; i < 7; i++) {
int posX = x + i - 3;
double luma = (isCentralArea ? bgrToY(src[posX, posY])
: bgrToY(getSafeSurfacePixel(src, posX, posY)));
totalLuma = totalLuma + luma * KERNEL7x7[i, j] * strength;
}
}

return yuvToBgr(totalLuma / 2097152.0, u, v);
}

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) {
bool isCentralArea = ((rect.Left >= 3) && (rect.Top >= 3)
&& (rect.Right <= src.Width - 3) && (rect.Bottom <= src.Height - 3));

for (int y = rect.Top; y < rect.Bottom; y++) {
if (IsCancelRequested) return;
for (int x = rect.Left; x < rect.Right; x++) {
dst[x, y] = (colorSpace == 0 ? convolveRGB(src, x, y, isCentralArea)
: convolveYUV(src, x, y, isCentralArea));
}
}
}```

Edited by loupasc
Syntax highlighting
Share on other sites

Thank you @loupasc, with sharp edges I  don't have to change my eye glasses.

Share on other sites

• 2 weeks later...

Cool plugin! 😮

Edited by Foxxey
Share on other sites

❤️ @loupasc!

Live as if you were to die tomorrow. Learn as if you were to live forever.

Gandhi

Share on other sites

❤️

Welcome to Family!

Thank you for your great result.

Live as if you were to die tomorrow. Learn as if you were to live forever.

Gandhi

Share on other sites

@Foxxey Danke

@Seerose It's my pleasure to contribute here !

Share on other sites

Version 1.6.7489.24864

Minor improvements (kernel matrix accuracy and to8bit clamp function a bit more efficient).

Spoiler

```
// Name: Slight edge boost
// Author: Pascal Ollive
// Title: Slight edge boost
// Version: 1.6
// Keywords: Sharpening|Gaussian|filter
// URL: https://forums.getpaint.net/profile/160055-loupasc/
// Help:
#region UICode
RadioButtonControl colorSpace = 0; // Select sharpen target|RGB (all)|YUV (Y only)
IntSliderControl sliderValue = 5; // [0,10] Strength
#endregion

// Scale double -> integer
private const int KERNEL_COEF_SCALE = -22306;

// 10 amounts depending of sliderValue
private static byte[] AMOUNT = {2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233};

// Significant value in 9x9 border
private static int EXTREME_BORDER_COEF = (int) Math.Round(KERNEL_COEF_SCALE * gaussian(0, -8 / 3.0));

// 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 = createKernelMatrix();

private static double gaussian(double x, double y) {
double sqr = x * x + y * y;
return 2 * Math.Exp(-6 * sqr) + Math.Exp(-9 * sqr / 8);
}

private static int[,] createKernelMatrix() {
int[,] matrix = new int[7, 7];
int sum = 4 * EXTREME_BORDER_COEF;
for (int j = 0; j < 7; j++) {
for (int i = 0; i < 7; i++) {
// support 2 => 1.5
matrix[i, j] = (int) Math.Round(KERNEL_COEF_SCALE * gaussian(2 * (i - 3)/ 3.0, 2 * (j - 3)/ 3.0));
sum = sum + matrix[i, j];
}
}
//
// sum(matrix) = 0
matrix[3, 3] = matrix[3, 3] - sum;
return matrix;
}

private byte strength = 21; // [2,233]

// ---------------------------
// -- Color space functions --
// ---------------------------

// -- RGB to YUV --

// out [0 .. 255]
private static double bgrToY(ColorBgra c) {
return 0.299 * c.R + 0.587 * c.G + 0.114 * c.B;
}

// out [-111.18 .. 111.18]
private static double bgrToU(ColorBgra c) {
return -0.14714 * c.R - 0.28886 * c.G + 0.436 * c.B;
}

// out [-156.825 .. 156.825]
private static double bgrToV(ColorBgra c) {
return 0.615 * c.R - 0.51499 * c.G - 0.10001 * c.B;
}

// -- YUV to RGB --

private static byte to8bit(double x) {
if (x < 0) {
return 0;
}
if (x > 255) {
return 255;
}
return (byte) Math.Round(x);
}

private static ColorBgra yuvToBgr(double y, double u, double v) {
byte b = to8bit(y + 2.032078 * u); // 0.299/0.14714
byte g = to8bit(y - 0.39465 * u - 0.5806 * v);
byte r = to8bit(y + 1.139827 * v); // 0.587/0.51499

return ColorBgra.FromBgr(b, g, r);
}

//
// ---------------------------
//

private static int surfaceExtend(int x, int maxValue) {
if (x < 0) {
return 0;
}
if (x < maxValue) {
return x;
}
return maxValue - 1;
}

private static ColorBgra getSafeSurfacePixel(Surface s, int x, int y) {
if ((x >= 0) && (y >= 0) && (x < s.Width) && (y < s.Height)) {
return s[x, y];
}
return s[surfaceExtend(x, s.Width), surfaceExtend(y, s.Height)];
}

// Utility function to provide result in 8-bit range
// strength 2^5
// factor 2^16
// 21-bit scale
private static byte to8bit(long n) {
if (n > 533725183) {
return 255;
}
if (n < 0) {
return 0;
}
return (byte) ((n + 1048576) >> 21);
}

private static int[] convolveRgbBorder(Surface src, int x, int y) {
int[] rgb = { 0, 0, 0 };
ColorBgra c1 = getSafeSurfacePixel(src, x, y - 4);
ColorBgra c2 = getSafeSurfacePixel(src, x - 4, y);
ColorBgra c3 = getSafeSurfacePixel(src, x + 4, y);
ColorBgra c4 = getSafeSurfacePixel(src, x, y + 4);

rgb[0] = EXTREME_BORDER_COEF * (c1.R + c2.R + c3.R + c4.R);
rgb[1] = EXTREME_BORDER_COEF * (c1.G + c2.G + c3.G + c4.G);
rgb[2] = EXTREME_BORDER_COEF * (c1.B + c2.B + c3.B + c4.B);

return rgb;
}

private ColorBgra convolveRGB(Surface src, int x, int y, bool isCentralArea) {
int[] rgb = convolveRgbBorder(src, x, y);
// strength 2^5 and factor 2^16 => 21-bit scale
long r = (src[x, y].R << 21) + rgb[0] * strength;
long g = (src[x, y].G << 21) + rgb[1] * strength;
long b = (src[x, y].B << 21) + rgb[2] * strength;

for (int j = 0; j < 7; j++) {
int posY = y + j - 3;
for (int i = 0; i < 7; i++) {
int posX = x + i - 3;
long coef = KERNEL7x7[i, j] * strength;
ColorBgra srcPixel = (isCentralArea ? src[posX, posY]
: getSafeSurfacePixel(src, posX, posY));
r = r + coef * srcPixel.R;
g = g + coef * srcPixel.G;
b = b + coef * srcPixel.B;
}
}

return ColorBgra.FromBgr(to8bit(b), to8bit(g), to8bit(r));
}

private static double convolveYuvBorder(Surface src, int x, int y) {
return EXTREME_BORDER_COEF * (bgrToY(getSafeSurfacePixel(src, x, y - 4))
+ bgrToY(getSafeSurfacePixel(src, x - 4, y))
+ bgrToY(getSafeSurfacePixel(src, x + 4, y))
+ bgrToY(getSafeSurfacePixel(src, x, y + 4)));
}

private ColorBgra convolveYUV(Surface src, int x, int y, bool isCentralArea) {
// strength 2^5 and factor 2^16 => 2^21 (2097152)
double totalLuma = bgrToY(src[x, y]) * 2097152.0;
double u = bgrToU(src[x, y]);
double v = bgrToV(src[x, y]);

totalLuma = totalLuma + convolveYuvBorder(src, x, y) * strength;

for (int j = 0; j < 7; j++) {
int posY = y + j - 3;
for (int i = 0; i < 7; i++) {
int posX = x + i - 3;
double luma = (isCentralArea ? bgrToY(src[posX, posY])
: bgrToY(getSafeSurfacePixel(src, posX, posY)));
totalLuma = totalLuma + luma * KERNEL7x7[i, j] * strength;
}
}

return yuvToBgr(totalLuma / 2097152.0, u, v);
}

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) {
bool isCentralArea = ((rect.Left >= 3) && (rect.Top >= 3)
&& (rect.Right <= src.Width - 3) && (rect.Bottom <= src.Height - 3));

for (int y = rect.Top; y < rect.Bottom; y++) {
if (IsCancelRequested) return;
for (int x = rect.Left; x < rect.Right; x++) {
dst[x, y] = (colorSpace == 0 ? convolveRGB(src, x, y, isCentralArea)
: convolveYUV(src, x, y, isCentralArea));
}
}
}```

Share on other sites

❤️

Thank you so much for the new version.

Live as if you were to die tomorrow. Learn as if you were to live forever.

Gandhi

You're welcome !

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

×   Pasted as rich text.   Paste as plain text instead

Only 75 emoji are allowed.