Jump to content
How to Install Plugins ×

Reconstruction filter tool box


Recommended Posts

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

interface.png.996bb72b3be764844392bf8cba

 

Plugin usage

- Inverse dithering

- Anti-aliasing

 

Spoiler

waveToolBox_demo.gif

 

And the corresponding spectrum (originally for validation purpose but I found it is nice to show)

Spoiler

waveToolBox_spectrum.gif

 

 

Best regards,

 

interface.png

 

WaveToolBox.zip

Edited by loupasc
Version Update 1.5.7466.17310
  • Like 3
  • Upvote 1
Link to comment
Share on other sites

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 by loupasc
Link to comment
Share on other sites

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 by loupasc
  • Like 1
Link to comment
Share on other sites

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.

  • Like 1

Xkds4Lh.png

Link to comment
Share on other sites

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 by loupasc
Link to comment
Share on other sites

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 by loupasc
Link to comment
Share on other sites

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?

  • Like 1
Link to comment
Share on other sites

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 by loupasc
Link to comment
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.

×
×
  • Create New...