Jump to content
How to Install Plugins ×
Paint.NET 5.1 is now available! ×

Recommended Posts

Posted (edited)

Overview

I'm posting an edge detector which uses no advanced algorithms. It just goes from the top left to the bottom right and checks each pixel to see if it contrasts with another pixel by a certain degree in the chosen channels. The pixel is at an angle with a magnitude called the sampling range. This is a fairly basic edge detector that is easy to understand and hopefully inspires others to write better ones (I encourage derivations).

 

 

2OqRGRb.png

mBOd0Ol.png

whnnun3.png

 

Purpose and Applications

This is used to make outlines that reduce the data needed to analyze an image, deducing areas of interest, and enabling the use of many interesting edge-based effects.

 

Options

Sampling range: the magnitude for the vector (the other component is the angle) that determines how thick edges are.

 

Detect opposite edge: This detects the opposite edge in addition to the current one.

 

Edge angle: This is the angle from which edges are detected.

 

Intensity threshold: All thresholds are values that describe how 'different' pixels have to be to be detected as edges. This one deals with intensity.

 

Red, green, blue, and alpha thresholds: These deal with the rgba channels. See above.

 

Background color: This selects how the background appears. It may be black, transparent (replacing the image), or transparent (on top of the image). The foreground is white unless the background is replacing the image, in which case the foreground is black for visibility. You can always invert the colors later.

 

Remarks

Located in the Stylize folder. Using blurs before edge detection greatly improves accuracy. This one has a horizontal line problem, sometimes doesn't render anything with the sampling range at 1 or -1, and leaves a small bar of unchecked pixels on the sides (by nature of its implementation). I would've fixed these already if I easily could and any help is appreciated.

 

Source 

#region UICode
double Amount1 = 1; // [-10,10] Sampling range
bool Amount2 = false; // [0,1] Detect opposite edge
double Amount3 = 0; // [-180,180] Edge angle
double Amount4 = 0.05; // [0,1] Intensity threshold
double Amount5 = 255; // [0,255] Red threshold
double Amount6 = 255; // [0,255] Green threshold
double Amount7 = 255; // [0,255] Blue threshold
double Amount8 = 255; // [0,255] Alpha threshold
byte Amount9 = 0; // Background Color|Black|Transparent, no image|Transparent, with image
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{  
    //Creates variables to store the colors.
    ColorBgra CurrentPixel, Col1, Col2;

    //Calculates the x and y components of the vector created by the angle chooser.
    double rads = Amount3 * Math.PI /180;
    double magX = Math.Cos(rads) * Amount1;
    double magY = Math.Sin(rads) * Amount1;
    
    //The for loops iterate through all pixels.
    for (float y = rect.Top; y < rect.Bottom; y++)
    {
        //Cancels by user request.
        if (IsCancelRequested)
        {
            return;
        }
        for (float x = rect.Left; x < rect.Right; x++)
        {
            //Sets the value of the current pixel to be transparent or black.
            //The pixel color will later become white if an edge is detected.
            if (Amount9 == 0)
            {
                CurrentPixel = ColorBgra.Black;
            }
            else if (Amount9 == 1)
            {
                CurrentPixel = ColorBgra.FromBgra(0, 0, 0, 0);
            }
            else
            {
                CurrentPixel = src[(int)x,(int)y];
            }
            
            //Gets the intensities of the pixels.
            Col1 = src.GetBilinearSampleClamped(x, y);
            Col2 = src.GetBilinearSampleClamped(x + (float)magX, y + (float)magY);
            
            if (Amount4 < 1)
            {                
                //The intensity threshold has been reached.
                if ((Col1.GetIntensity()- Col2.GetIntensity()) > Amount4 ||
	(Col2.GetIntensity()- Col1.GetIntensity()) < -Amount4)
                {
                    CurrentPixel = ColorBgra.White;
                }
            }
            //If red is being calculated.
            if (Amount5 < 255)
            {
                //The redness threshold has been reached.
                if ((Col1.R - Col2.R > Amount5) ||
                    (Col2.R - Col1.R < -Amount5))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If green is being calculated.
            if (Amount6 < 255)
            {
                //The green-ness threshold has been reached.
                if ((Col1.G - Col2.G > Amount6) ||
                    (Col2.G - Col1.G < -Amount6))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If blue is being calculated.
            if (Amount7 < 255)
            {
                //The blueness threshold has been reached.
                if ((Col1.B - Col2.B > Amount7) ||
                    (Col2.B - Col1.B < -Amount7))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If alpha is being calculated.
            if (Amount8 < 255)
            {
                //The alpha threshold has been reached.
                if ((Col1.A - Col2.A > Amount8) ||
                    (Col2.A - Col1.A < -Amount8))
                {
                    CurrentPixel = Color.White;
                }
            }
            
            //Changes the foreground color to black for visibility if the background is transparent.
            if (Amount9 == 1 && CurrentPixel.Equals(Color.White))
            {
                CurrentPixel = Color.Black;
            }

            //Applies everything again (in the next iteration), but from the exact opposite angle.
            if (Amount2)
            {
                magX = Math.Cos(rads + Math.PI) * Amount1;
                magY = Math.Sin(rads + Math.PI) * Amount1;
            }
            
            //Applies all changes to the destination surface.
            dst[(int)x, (int)y] = CurrentPixel;
        }
    }
}

Basic Edge Detector.zip

Edited by AnthonyScoffler
Posted

 

 

This one has a horizontal line problem, sometimes doesn't render anything with the sampling range at 1 or -1, and leaves a small bar of unchecked pixels on the sides (by nature of its implementation). I would've fixed these already if I easily could and any help is appreciated.

your rounding back to zero

Set up a temp variable before the loops

ColorBgra  tmp2;

Change this

                    double pix2Int = src[(int)(x + magX), (int)(y + magY)].GetIntensity();
                    

to this

                    //Gets the intensities of the pixels.
                    tmp2 =  src.GetBilinearSample((float)x + (float)magX, (float)y + (float)magY);
                    double pix2Int = tmp2.GetIntensity();

Go out there and be amazing. Have Fun, TR
TRsSig.png?raw=1
Some Pretty Pictures Some Cool Plugins

Posted

It's been updated for your changes, TechnoRobbo. The three errors described still persist, though. I added pictures a link to pictures because this forum hates pictures and is a picture Nazi, even though the pictures are hosted elsewhere. But I digress :)

Posted

I use PNGs all the time. It might be because they were uploaded to Google Drive; I don't think Drive supports hotlinking. Try a different image host.

 

The Doctor: There was a goblin, or a trickster, or a warrior... A nameless, terrible thing, soaked in the blood of a billion galaxies. The most feared being in all the cosmos. And nothing could stop it, or hold it, or reason with it. One day it would just drop out of the sky and tear down your world.
Amy: But how did it end up in there?
The Doctor: You know fairy tales. A good wizard tricked it.
River Song: I hate good wizards in fairy tales; they always turn out to be him.

Posted

 

How much different is this than the built-in Effects > Artistic > Ink Sketch?

 

Very. This is guaranteed to be open-source. Ink sketch is a combined effect and is not meant to be an edge detector anyway. This can operate on individual channels instead of the image in its entirety. It can use an angle to detect edges from. I plan on fixing the lines, but I don't think Ink Sketch does this:

N84sDCu.png

Posted

I couldn't remember if it was open-source or not, but now that I think about it, you talked about it in the CodeLab tutorials. It's okay though, since both are different effects. If Paint.NET really needs a dedicated edge detector, I can try to find an (actual) algorithm online and make one. My 'algorithm' is child's play compared to real ones... :)

Posted

Here you go - it's your code, I just took out all that confusing rounding, works fine now.

#region UICode
double Amount1 = 2; // [-10,10] Sampling range
bool Amount2 = false; // [0,1] Detect opposite edge
double Amount3 = 45; // [-180,180] Edge angle
double Amount4 = 1; // [0,1] Intensity threshold
double Amount5 = 255; // [0,255] Red threshold
double Amount6 = 255; // [0,255] Green threshold
double Amount7 = 255; // [0,255] Blue threshold
double Amount8 = 255; // [0,255] Alpha threshold
byte Amount9 = 0; // Background Color|Black|Transparent, no image|Transparent, with image
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{
    //debug section
    //Amount4 = .1;
    //end debug
    
    //Creates a variable to store the current pixel.
    ColorBgra CurrentPixel,Col1,Col2;

    //Calculates the x and y components of the vector created by the angle chooser.
    //The Pi/180 part converts the degrees to radians so the functions work.
    double rads = Amount3 * Math.PI /180;
    double magX = Math.Cos(rads) * Amount1;
    double magY = Math.Sin(rads) * Amount1;
    
    //The for loops iterate through all pixels.
    for (float y = rect.Top; y < rect.Bottom; y++)
    {
        //Cancels by user request.
        if (IsCancelRequested)
        {
            return;
        }
        for (float x = rect.Left; x < rect.Right; x++)
        {
            //Sets the value of the current pixel to be transparent or black.
            //The pixel color will later become white if an edge is detected.
            if (Amount9 == 0)
            {
                CurrentPixel = ColorBgra.Black;
            }
            else if (Amount9 == 1)
            {
                CurrentPixel = ColorBgra.FromBgra(0, 0, 0, 0);
            }
            else
            {
                CurrentPixel = src[(int)x,(int)y];
            }
            
            
            //Checks if the pixels to be looked at in the sampling range actually exist.
            //If intensity is being calculated.

                //Gets the intensities of the pixels.
                Col1 = src.GetBilinearSampleClamped(x, y);
                Col2 = src.GetBilinearSampleClamped(x + (float)magX, y + (float)magY);

            if (Amount4 < 1)
            {

                
                //The intensity threshold has been reached.
                if ((Col1.GetIntensity()- Col2.GetIntensity()) > Amount4 ||
					(Col2.GetIntensity()- Col1.GetIntensity()) < -Amount4)
                {
                    CurrentPixel = ColorBgra.White;
                }
            }
            //If red is being calculated.
            if (Amount5 < 255)
            {
                //The redness threshold has been reached.
                if ((Col1.R - Col2.R > Amount5) ||
                    (Col2.R - Col1.R < -Amount5))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If green is being calculated.
            if (Amount6 < 255)
            {
                //The green-ness threshold has been reached.
                if ((Col1.G - Col2.G > Amount6) ||
                    (Col2.G - Col1.G < -Amount6))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If blue is being calculated.
            if (Amount7 < 255)
            {
                //The blueness threshold has been reached.
                if ((Col1.B - Col2.B > Amount7) ||
                    (Col2.B - Col1.B < -Amount7))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If alpha is being calculated.
            if (Amount8 < 255)
            {
                //The alpha threshold has been reached.
                if ((Col1.A - Col2.A > Amount8) ||
                    (Col2.A - Col1.A < -Amount8))
                {
                    CurrentPixel = Color.White;
                }
            }
            
            //Changes the foreground color to black for visibility if the background is transparent.
            if (Amount9 == 1 && CurrentPixel.Equals(Color.White))
            {
                CurrentPixel = Color.Black;
            }

            //
            //Applies everything again (in the next iteration), but from the exact opposite angle.
            if (Amount2)
            {
                magX = Math.Cos(rads + Math.PI) * Amount1;
                magY = Math.Sin(rads + Math.PI) * Amount1;
            }
            
            //Applies all changes to the destination surface.
            dst[(int)x, (int)y] = CurrentPixel;
        }
    }
}
  • Upvote 1

Go out there and be amazing. Have Fun, TR
TRsSig.png?raw=1
Some Pretty Pictures Some Cool Plugins

Posted

Is this good practice or unnecessary?
 

 //Cancels by user request.
        if (IsCancelRequested)
        {
            return;
        }
Posted

I'm assuming it's good practice because it allows the effect to exit the render function so that the effect stops. I think it operates for all threads in a multi-threaded effect, but I'm not sure because I don't understand multiple threads too well. It's included in default CodeLab scripts now, so I left it. Boltbait seems to know what he's doing.

Posted (edited)

 

Here you go - it's your code, I just took out all that confusing rounding, works fine now.

Thanks a ton for fixing my disaster-piece. I'll give you +1 when my rep cap resets (why does it even exist?)

Also, I updated my code to reflect the changes. You're written in as an author.

Edited by AnthonyScoffler
Posted

 

 

Ego  - Is this good practice or unnecessary?

Necessity for cancelling long complex loops. Integral part of CodLab 2.3 generated code.

 

 

AS - Thanks a ton for fixing my disaster-piece. I'll give you +1 when my rep cap resets (why does it even exist?)

Also, I updated my code to reflect the changes. You're written in as an author.

No need for that, I just proof read it, Wordpad copy pasting.

And, I assume the reps cap exists so as not to de-value it's importance

Go out there and be amazing. Have Fun, TR
TRsSig.png?raw=1
Some Pretty Pictures Some Cool Plugins

Posted

Boltbait seems to know what he's doing.

Boy, have I got you fooled! :D

Anyway, it allows an effect that is running to end faster if the user changes a UI control or cancels an effect. It is not necessary, but sometimes it can make your UI more responsive.

I put it in the Y loop so it only checks once per row. If your effect is especially slow, you might want to move it to the X loop. I did that ONCE in all my plugins.

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...