Roly Poly Goblinoli Posted August 21, 2014 Posted August 21, 2014 (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). 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 February 29, 2016 by AnthonyScoffler Quote
toe_head2001 Posted August 21, 2014 Posted August 21, 2014 (edited) Fancy! Although I'd recommend not having the default settings maxed out to '255'....makes images completely black. The Edge Detection built into paint.net is much more basic than this.... you could jump on the 'plus' bandwagon and call this 'Edge Detect+' Edited August 21, 2014 by toe_head2001 Quote My Gallery | My Plugin Pack Layman's Guide to CodeLab
TechnoRobbo Posted August 21, 2014 Posted August 21, 2014 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(); Quote Go out there and be amazing. Have Fun, TRSome Pretty Pictures Some Cool Plugins
Roly Poly Goblinoli Posted August 21, 2014 Author Posted August 21, 2014 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 Quote
david.atwell Posted August 21, 2014 Posted August 21, 2014 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. Quote 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.
TechnoRobbo Posted August 21, 2014 Posted August 21, 2014 I only addressed 1 and -1 error but the rounding issues are all over. Try type casting as in my example. Can examine more later on tonight. Quote Go out there and be amazing. Have Fun, TRSome Pretty Pictures Some Cool Plugins
BoltBait Posted August 21, 2014 Posted August 21, 2014 How much different is this than the built-in Effects > Artistic > Ink Sketch ? Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game
Roly Poly Goblinoli Posted August 21, 2014 Author Posted August 21, 2014 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: Quote
BoltBait Posted August 21, 2014 Posted August 21, 2014 Nope, it won't do that, but even though Ink Sketch is built-in, it is open source and is guaranteed to stay that way. You can see the source code here: http://forums.getpaint.net/index.php?/topic/3474- Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game
Roly Poly Goblinoli Posted August 22, 2014 Author Posted August 22, 2014 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... Quote
BoltBait Posted August 22, 2014 Posted August 22, 2014 Check through the plugin development forum. I recall posting about it in there. I'll see if I can find it. As I recall it was very rough. EDIT: Found it. http://forums.getpaint.net/index.php?/topic/18105- Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game
TechnoRobbo Posted August 22, 2014 Posted August 22, 2014 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; } } } 1 Quote Go out there and be amazing. Have Fun, TRSome Pretty Pictures Some Cool Plugins
Ego Eram Reputo Posted August 22, 2014 Posted August 22, 2014 Is this good practice or unnecessary? //Cancels by user request. if (IsCancelRequested) { return; } Quote ebook: Mastering Paint.NET | resources: Plugin Index | Stereogram Tut | proud supporter of Codelab plugins: EER's Plugin Pack | Planetoid | StickMan | WhichSymbol+ | Dr Scott's Markup Renderer | CSV Filetype | dwarf horde plugins: Plugin Browser | ShapeMaker
Roly Poly Goblinoli Posted August 22, 2014 Author Posted August 22, 2014 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. Quote
Roly Poly Goblinoli Posted August 22, 2014 Author Posted August 22, 2014 (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 August 22, 2014 by AnthonyScoffler Quote
TechnoRobbo Posted August 22, 2014 Posted August 22, 2014 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 Quote Go out there and be amazing. Have Fun, TRSome Pretty Pictures Some Cool Plugins
BoltBait Posted August 22, 2014 Posted August 22, 2014 Boltbait seems to know what he's doing. Boy, have I got you fooled! 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. Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.