Sign in to follow this  
loupasc

Image sharpen

Recommended Posts

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);
        }
    }
}

 

SlightEdgeBoost.sample.png

result.png

 

Edited by loupasc
Delete old plugin archive
  • Like 1
  • Upvote 1

Share this post


Link to post
Share on other sites

Hello@ loupasc.

 

Paint.Net (4.2.11 beta 4211.7433.39692) would not open ('Not Responding') after placing your SlightEdgeBoost.dll into the Effects Folder.  all OK when I deleted the pesky .dll.

 

Thanks for your thoughtful contribution.

 

 

Share this post


Link to post
Share on other sites
6 minutes ago, ANONYMISS said:

Paint.Net (4.2.11 beta 4211.7433.39692) would not open

 

You're using an old beta. Please update to the newer release.

Share this post


Link to post
Share on other sites
7 minutes ago, toe_head2001 said:

 

You're using an old beta. Please update to the newer release.

 

Thank you @ toe_head2001. Not  a pesky.dll then - just a pesky beta. I'll mooch around with the horses until a stable happens by.

Share this post


Link to post
Share on other sites

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 by loupasc
Delete old plugin archive
  • Like 1

Share this post


Link to post
Share on other sites

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 by loupasc
Delete old plugin archive

Share this post


Link to post
Share on other sites

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 by loupasc
Delete old plugin archive

Share this post


Link to post
Share on other sites

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 by loupasc
  • Like 1
  • Upvote 1

Share this post


Link to post
Share on other sites

Join the conversation

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

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this