Ego Eram Reputo Posted April 7 Share Posted April 7 I could use a little help on this one I'm trying to recreate an outlining effect I saw on Watabou's one-page dungeon generator. I want to surround an object with little gray boxes something like this image. I had a very rough stab at it. Basically my plan was to step through the image and test the transparency of the target pixel. If it was transparent, throw a little box around it. The code I wrote trying to hack this out was truly awful. Spoiler /* ========================================================================== */ /* */ /* BoxOutline.cs */ /* (c) 2024 Ego Eram Reputo */ /* */ /* Description: surrounds a selection with shaded boxes */ /* */ /* ========================================================================== */ // Name: BoxOutline // Author: Ego Eram Reputo // Submenu: Render // URL: http://www.getpaint.net/redirect/plugins.html #region UICode IntSliderControl Amount2 = 25; // [15,100] Box Size ReseedButtonControl Amount1 = 0; // Reseed IntSliderControl Amount3 = 5; // [1,25] Spacing #endregion void Render(Surface dst, Surface src, Rectangle rect) { Rectangle selection = EnvironmentParameters.SelectionBounds; int boxSize = Amount2/2; int Spacing = Amount3; int rndHeight, rndWidth, m,p; ColorBgra PrimaryColor = (ColorBgra)EnvironmentParameters.PrimaryColor; Graphics g = new RenderArgs(dst).Graphics; g.Clip = new Region(rect); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; Pen outlinePen = new Pen(PrimaryColor, 2); SolidBrush solidBrush = new SolidBrush(Color.Beige); dst.CopySurface(src,rect.Location,rect); for (int y = selection.Top; y < selection.Bottom; y += boxSize+Spacing) { for (int x = selection.Left; x < selection.Right; x += boxSize+Spacing) { if (src[x,y].A >0 ) { rndHeight = RandomNumber.Next( boxSize/2.0 )+RandomNumber.Next(boxSize/2); rndWidth = RandomNumber.Next(boxSize/2)+RandomNumber.Next(boxSize/2); m=x-rndWidth-boxSize/2+1; if (m<0) {m=0;} p=y-rndHeight-boxSize/2+4; if (p<0) {p=0;} g.FillRectangle(solidBrush, m-rndWidth, p-rndHeight, boxSize*2+rndWidth, boxSize*2+rndHeight); g.DrawRectangle(outlinePen, m-rndWidth, p-rndHeight, boxSize*2+rndWidth, boxSize*2+rndHeight); } } } } Results were ......promising!? (at least it was working right 😁) Does anyone want to help me write something a little more elegant? 2 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 Link to comment Share on other sites More sharing options...
Tactilis Posted April 7 Share Posted April 7 One observation regarding the algorithm... Comparing your results with those from the one-page dungeon generator (OPDG), it appears that you are creating each box with a random width and height. I think the OPDG uses a fixed set of box sizes and the algorithm is along the lines of: Walk around the border and: 1. Randomly choose a box size from the set 2. Randomly choose the amount of overlap for this box (within certain constraints) with the box previously placed. 1 Quote Link to comment Share on other sites More sharing options...
Tactilis Posted April 7 Share Posted April 7 29 minutes ago, Tactilis said: Walk around the border ☝️ is left as an exercise for the coder 😉 1 Quote Link to comment Share on other sites More sharing options...
Reptillian Posted April 7 Share Posted April 7 Looks like a fun thing to try out. I won't do it in C# though. And I even have more idea than simply orthogonal directions. Quote G'MIC Filter Developer I am away from this forum for undetermined amount of time: If you really need anything related to my PDN plugin or my G'MIC filter within G'MIC plugin, then you can contact me via Paint.NET discord, and mention me. Link to comment Share on other sites More sharing options...
BoltBait Posted April 7 Share Posted April 7 Here is a classic script to help you determine when you are on the edge of a selection... Spoiler // Name: // Submenu: // Author: // Title: // Version: // Desc: // Keywords: // URL: // Help: #region UICode ReseedButtonControl Amount1 = 0; // Randomize #endregion // Here is the main multi-threaded render function // The dst canvas is broken up into rectangles and // your job is to write to each pixel of that rectangle void Render(Surface dst, Surface src, Rectangle rect) { // uint seed = RandomNumber.InitializeSeed(RandomNumberRenderSeed, rect.Location); // Delete this line if you don't need the selection outline shape PdnRegion selectionRegion = EnvironmentParameters.GetSelectionAsPdnRegion(); // Step through each row of the current rectangle for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; // Step through each pixel on the current row of the rectangle for (int x = rect.Left; x < rect.Right; x++) { ColorBgra SrcPixel = src[x,y]; ColorBgra DstPixel = dst[x,y]; ColorBgra CurrentPixel = SrcPixel; if ( (!selectionRegion.IsVisible(x-1,y)) || (!selectionRegion.IsVisible(x,y-1)) || (!selectionRegion.IsVisible(x+1,y)) || (!selectionRegion.IsVisible(x,y+1)) ) { // This pixel is next to the marching ants // as an example, I'm changing that pixel to a random color CurrentPixel = ColorBgra.FromBgr((byte)RandomNumber.Next(ref RandomNumberRenderSeed,255),(byte)RandomNumber.Next(ref RandomNumberRenderSeed,255),(byte)RandomNumber.Next(ref RandomNumberRenderSeed,255)); } else { // This pixel is NOT next to the marching ants } dst[x,y] = CurrentPixel; } } } So, select your building outline and run the script. Hope this helps. 1 Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game Link to comment Share on other sites More sharing options...
Red ochre Posted April 7 Share Posted April 7 Spoiler using (Graphics graf = new RenderArgs(dest).Graphics) { graf.Clip = new Region(rect); graf.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; using (GraphicsPath botface = new GraphicsPath()) { botface.StartFigure(); botface.AddLines(basecorners); botface.CloseFigure(); using (SolidBrush solidtestbrush = new SolidBrush(bfcolt)) { graf.FillPath(solidtestbrush, botface); graf.DrawPath(new Pen(bfcol), botface); } }//end botface block // another five faces done here in the real code }//end graf block // This is one of the many rectangles drawn in my Cuboids code (done in Visual studio not codelab). 'botface' means bottom face of a cube (not an insult;-) // Note:'dest' is surface which gets copied to 'dst' you'll prbably write straight to dst after copying src to dst. // I place Graphics paths, Brushes and Pens in 'using' statement blocks so they get disposed when no longer needed. // Otherwise you could run out of memory if you forget to seperate Dispose statements for everything created. // basecorners is an array of 4 PointFs calculated before creating the graphics stuff. Hope that helps... I've found it better to use 'using' statements to ensure graphics objects get disposed of. 1 Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
Rick Brewster Posted April 7 Share Posted April 7 FWIW, the classic effect system is getting deprecated, and soon -- I strongly encourage you to switch to BitmapEffect or GpuEffect (or probably [PropertyBased]GpuDrawingEffect in this case). It's worth learning this now before you end up with effect code that you can no longer maintain (compile) when newer versions of Paint.NET are released. This deprecation is starting with v5.1, where the classic effect system will be marked with [Obsolete("...", false)] (compiler warning to not use it), and then in the next release (e.g. v5.2 or v6.0) it'll be [Obsolete("...", true)] (compiler error preventing you from using it). I'm happy to help with the conversion btw, just let me know what questions you've got! Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted April 8 Author Share Posted April 8 21 hours ago, Tactilis said: I think the OPDG uses a fixed set of box sizes Hadn't considered that. I'll have a look and see if I can make six or seven boxes work. 16 hours ago, BoltBait said: Here is a classic script to help you determine when you are on the edge of a selection... Nice! Thank you @BoltBait 14 hours ago, Red ochre said: Hope that helps... I've found it better to use 'using' statements to ensure graphics objects get disposed of. Thanks Red. 12 hours ago, Rick Brewster said: FWIW, the classic effect system is getting deprecated, and soon -- I strongly encourage you to switch to BitmapEffect or GpuEffect (or probably [PropertyBased]GpuDrawingEffect in this case). Quite right. I will change of course. The posted code was something from a year or so ago (I just had another play with it to remember where I got to). 1 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 Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted April 13 Author Share Posted April 13 Some progress....... // Name: Box Outlining GPU // Submenu: test // Author: // Title: // Version: // Desc: // Keywords: // URL: // Help: // For help writing a GPU Drawing plugin: https://boltbait.com/pdn/CodeLab/help/tutorial/drawing/ #region UICode IntSliderControl Amount2 = 35; // [15,100] Size IntSliderControl Amount3 = 9; // [2,25] Spacing #endregion protected override unsafe void OnDraw(IDeviceContext deviceContext) { deviceContext.DrawImage(Environment.SourceImage); // preserve background // find out where our selection is located RectInt32 selection = Environment.Selection.RenderBounds; // variables int boxSize = Amount2/2; int Spacing = Amount3+1; int doubleSpacing = Amount3*2; int thickness = 2; int rndHeight, rndWidth; int step = boxSize*7/10; // define your brush and stroke style ISolidColorBrush fillBrush = deviceContext.CreateSolidColorBrush(LinearColors.LightGray); ISolidColorBrush outlineBrush = deviceContext.CreateSolidColorBrush(LinearColors.Black); IStrokeStyle boxStrokeStyle = deviceContext.Factory.CreateStrokeStyle(StrokeStyleProperties.Default); // setup drawing mode deviceContext.AntialiasMode = AntialiasMode.Aliased; // or .PerPrimitive Random rnd = new Random(); for (int y = selection.Top; y < selection.Bottom; y += step) { for (int x = selection.Left; x < selection.Right; x += step) { //if source pixel is opaque ) rndWidth = rnd.Next(Spacing, doubleSpacing); rndHeight = rnd.Next(Spacing, doubleSpacing); deviceContext.FillRectangle(x-rndWidth, y-rndHeight, boxSize, boxSize, fillBrush); deviceContext.DrawRectangle(x-rndWidth, y-rndHeight, boxSize, boxSize, outlineBrush, thickness, boxStrokeStyle); } } } I haven't figured out how to poll the source pixel yet, so I'm just filling selections at the moment. Still, I think looks great! 1 2 1 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 Link to comment Share on other sites More sharing options...
Rick Brewster Posted April 13 Share Posted April 13 10 hours ago, Ego Eram Reputo said: I haven't figured out how to poll the source pixel yet You can use Environment.GetSourceBitmapBgra32(), which will give you an IEffectInputBitmap<ColorBgra32>. Then call Lock() to get the IBitmapLock<ColorBgra32>(). Then use AsRegionPtr() to get it as a RegionPtr<ColorBgra32> which you can just index into, e.g. region[x, y]. Then just compare region[x, y].A == 255 to determine if it's opaque. Note that this works fine for querying the alpha channel, as the alpha channel is invariant with respect to color space (sRGB vs. linear scRGB vs. whatever other color profile they might have on the image!). If you want to query the RGB values, they won't line up with the color space you're using in the GPU effect, and it'll take some extra work to determine the right thing to do. So that's a topic for another time. 1 Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted May 19 Author Share Posted May 19 Time to admit I'm lost. // use Environment.GetSourceBitmapBgra32(), which will give you an IEffectInputBitmap<ColorBgra32> IEffectInputBitmap sourceImage = Environment.GetSourceBitmapBgra32(); RectInt32 rect = new RectInt32(0,0,sourceImage.Size); //Then call Lock() to get the IBitmapLock<ColorBgra32>(). IBitmapLock<ColorBgra32> myLock = sourceImage.Lock(rect); // Then use AsRegionPtr() to get it as a RegionPtr<ColorBgra32> RegionPtr<ColorBgra32> outputRegion = BitmapLockExtensions.AsRegionPtr<ColorBgra32>(myLock); The error I'm getting in the line which calls Lock() Quote Error at line 50: Cannot implicitly convert type 'PaintDotNet.Imaging.IBitmapLock' to 'PaintDotNet.Imaging.IBitmapLock<PaintDotNet.Imaging.ColorBgra32>'. An explicit conversion exists (are you missing a cast?) (CS0266) 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 Link to comment Share on other sites More sharing options...
Rick Brewster Posted May 19 Share Posted May 19 // use Environment.GetSourceBitmapBgra32(), which will give you an IEffectInputBitmap<ColorBgra32> IEffectInputBitmap sourceImage = Environment.GetSourceBitmapBgra32(); You need to use IEffectInputBitmap<ColorBgra32>. Then you'll have the version of Lock which returns IBitmapLock<ColorBgra32> GetSourceBitmapBgra32() is an extension method that calls GetSourceBitmap<ColorBgra32>(), which itself is an extension method which will call GetSourceBitmap(PixelFormats.Bgra32) and then do the appropriate casting from IEffectInputBitmap to IEffectInputBitmap<ColorBgra32>. // Then use AsRegionPtr() to get it as a RegionPtr<ColorBgra32> RegionPtr<ColorBgra32> outputRegion = BitmapLockExtensions.AsRegionPtr<ColorBgra32>(myLock); You should call the extension method as if it's a regular method on the object, e.g. myLock.AsRegionPtr(). Once you fix the GetSourceBitmap stuff above, this will work correctly. 1 Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted May 19 Author Share Posted May 19 Thanks so much Rick!!!! 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 Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted August 30 Author Share Posted August 30 I should have posted what I have learned so far.... // Name: Box Outlining GPU // Submenu: test // Author: // Title: // Version: // Desc: // Keywords: // URL: // Help: // For help writing a GPU Drawing plugin: https://boltbait.com/pdn/CodeLab/help/tutorial/drawing/ #region UICode IntSliderControl Amount2 = 25; // [15,100] Size IntSliderControl Amount3 = 0; // [-100,100] X position IntSliderControl Amount4 = 0; // [-100,100] Y position #endregion protected override unsafe void OnDraw(IDeviceContext deviceContext) { // Preserve the background image by drawing (copying?) the source image to the output canvas (pointed to by "deviceContext") deviceContext.DrawImage(Environment.SourceImage); // Gets the current selection from the environment and puts it into the "selection" integer rectangle variable. // References the current selection, if there is one. If there isn't one, it returns the entire layer. RectInt32 selection = Environment.Selection.RenderBounds; // variables int boxSize = Amount2/2; int halfBoxSize = boxSize/2; int thickness = 2; int rndHeight, rndWidth; int minSpacing = (int)Math.Sqrt(boxSize); int maxSpacing = minSpacing*3; int wOffset, hOffset; // define any brushes and stroke styles ISolidColorBrush fillBrush = deviceContext.CreateSolidColorBrush(LinearColors.LightGray); ISolidColorBrush outlineBrush = deviceContext.CreateSolidColorBrush(LinearColors.Black); IStrokeStyle boxStrokeStyle = deviceContext.Factory.CreateStrokeStyle(StrokeStyleProperties.Default); // setup drawing mode deviceContext.AntialiasMode = AntialiasMode.Aliased; // or .PerPrimitive // Initalize and seed the random number generator Random rnd = new Random(); // The next four lines allow polling of the source image.A value // Rick said .....use Environment.GetSourceBitmapBgra32(), which will give you an IEffectInputBitmap<ColorBgra32> IEffectInputBitmap<ColorBgra32> sourceImage = Environment.GetSourceBitmapBgra32(); RectInt32 rect = new RectInt32(0,0,sourceImage.Size); // .......then call Lock() to get the IBitmapLock<ColorBgra32>(). IBitmapLock<ColorBgra32> myLock = sourceImage.Lock(rect); // .....then use AsRegionPtr() to get it as a RegionPtr<ColorBgra32> RegionPtr<ColorBgra32> myRegion = myLock.AsRegionPtr(); // It is myRegion[x,y] that can be examined for the alpha value. // Note this technique is not valid for checking the RGB values because of color management. Here's how Rick explained it.... // "Note that this works fine for querying the alpha channel, as the alpha channel is invariant with respect to color space (sRGB vs. linear scRGB vs. whatever other // color profile they might have on the image!). If you want to query the RGB values, they won't line up with the color space you're using in the GPU effect, and it'll // take some extra work to determine the right thing to do. So that's a topic for another time." // // A GPUDrawing effect has a main function called OnDraw() with color type LinearColors // Step across the selection (or layer if no selection is active), rendering boxes where the source pixel is opaque. for (int y = selection.Top; y < selection.Bottom; y+= boxSize+1) { for (int x = selection.Left; x < selection.Right; x+= boxSize+1) { //if source pixel is opaque if (myRegion[x, y].A == 255) { rndWidth = rnd.Next(minSpacing, maxSpacing); rndHeight = rnd.Next(minSpacing, maxSpacing); wOffset = rndWidth+halfBoxSize-Amount3+3; hOffset = rndHeight+halfBoxSize-Amount4+2; rndWidth *=3; rndHeight *=3; // draw a filled rectangle, followed by an outline to get a two-toned box deviceContext.FillRectangle(x-wOffset, y-hOffset, boxSize+rndWidth, boxSize+rndHeight, fillBrush); deviceContext.DrawRectangle(x-wOffset, y-hOffset, boxSize+rndWidth, boxSize+rndHeight, outlineBrush, thickness, boxStrokeStyle); } } } // CompositeEffect compositeEffect = new CompositeEffect(deviceContext); // compositeEffect.Properties.Sources.Add(this.Environment.SourceImage); // compositeEffect.Properties.Mode.SetValue(CompositeMode.SourceOver); } 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 Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted September 15 Author Share Posted September 15 More progress....... Spoiler // Name: Box Outlining GPU // Submenu: test // Author: // Title: // Version: // Desc: // Keywords: // URL: // Help: // NSC // For help writing a GPU Drawing plugin: https://boltbait.com/pdn/CodeLab/help/tutorial/drawing/ #region UICode IntSliderControl Amount2 = 25; // [15,100] Size IntSliderControl Amount5 = 2; // [1,12] Border Thickness RadioButtonControl Amount6 = 0; // Coloring|Black/Gray|Primary/Secondary|Secondary/Primary IntSliderControl Amount3 = 0; // [-100,100] X position IntSliderControl Amount4 = 0; // [-100,100] Y position CheckboxControl Amount7 = true; // Preserve Source Object (uncheck to overwrite) #endregion protected override unsafe void OnDraw(IDeviceContext deviceContext) { // Preserve the background image by drawing (copying?) the source image to the output canvas (pointed to by "deviceContext") deviceContext.DrawImage(Environment.SourceImage); // Gets the current selection from the environment and puts it into the "selection" integer rectangle variable. // References the current selection, if there is one. If there isn't one, it returns the entire layer. RectInt32 selection = Environment.Selection.RenderBounds; // variables int boxSize = Amount2*4/11; // *4/11 = /2.75 larger numbers makes the boxes bigger int halfBoxSize = boxSize/2; int thickness = Amount5; int rndHeight, rndWidth; int minSpacing = (int)Math.Sqrt(boxSize)+1;// increase for more variation in sizes int maxSpacing = minSpacing+halfBoxSize+3; int wOffset, hOffset; ISolidColorBrush fillBrush = deviceContext.CreateSolidColorBrush(LinearColors.LightGray); ISolidColorBrush outlineBrush = deviceContext.CreateSolidColorBrush(LinearColors.Black); // This is now of type ManagedColor instead of ColorBgra32 ManagedColor primaryColorM = Environment.PrimaryColor; ManagedColor secondaryColorM = Environment.SecondaryColor; // You can also use a ManagedColorProperty with IndirectUI to let the user select a color with a Color Wheel // e.g. ManagedColorProperty mcm = new ManagedColorProperty(PropertyNames.Color1, Environment.PrimaryColor, ManagedColorPropertyAlphaMode.SupportsAlpha); // Behind the scenes, the device context is tagged with the appropriate color space / color context // I've rigged it up so _you_ don't have to worry about color space or color context or linear or companded or whatever // Because what _you_ want is just the appropriate color value for rendering with the device context ColorRgba128Float primaryColor = primaryColorM.Get(deviceContext); ColorRgba128Float secondaryColor = secondaryColorM.Get(deviceContext); // define any brushes and stroke styles switch (Amount6) { case 0: fillBrush = deviceContext.CreateSolidColorBrush(LinearColors.LightGray); outlineBrush = deviceContext.CreateSolidColorBrush(LinearColors.Black); break; case 1: fillBrush = deviceContext.CreateSolidColorBrush(secondaryColor); outlineBrush = deviceContext.CreateSolidColorBrush(primaryColor); break; case 2: fillBrush = deviceContext.CreateSolidColorBrush(primaryColor); outlineBrush = deviceContext.CreateSolidColorBrush(secondaryColor); break; } // The following line is unncessary if the strokestyle is default. //IStrokeStyle boxStrokeStyle = deviceContext.Factory.CreateStrokeStyle(StrokeStyleProperties.Default); // setup drawing mode deviceContext.AntialiasMode = AntialiasMode.Aliased; // or .PerPrimitive // Initialize and seed the random number generator Random rnd = new Random(); // The next four lines allow polling of the source image.A value // Rick said .....use Environment.GetSourceBitmapBgra32(), which will give you an IEffectInputBitmap<ColorBgra32> IEffectInputBitmap<ColorBgra32> sourceImage = Environment.GetSourceBitmapBgra32(); RectInt32 rect = new RectInt32(0,0,sourceImage.Size); // .......then call Lock() to get the IBitmapLock<ColorBgra32>(). IBitmapLock<ColorBgra32> myLock = sourceImage.Lock(rect); // .....then use AsRegionPtr() to get it as a RegionPtr<ColorBgra32> RegionPtr<ColorBgra32> myRegion = myLock.AsRegionPtr(); // It is myRegion[x,y] that can be examined for the alpha value. // Note this technique is not valid for checking the RGB values because of color management. Here's how Rick explained it.... // "Note that this works fine for querying the alpha channel, as the alpha channel is invariant with respect to color space (sRGB vs. linear scRGB vs. whatever other // color profile they might have on the image!). If you want to query the RGB values, they won't line up with the color space you're using in the GPU effect, and it'll // take some extra work to determine the right thing to do. So that's a topic for another time." // // A GPUDrawing effect has a main function called OnDraw() with color type LinearColors // Step across the selection (or layer if no selection is active), rendering boxes where the source pixel is opaque. for (int y = selection.Top; y < selection.Bottom; y+= boxSize*3/2) { for (int x = selection.Left; x < selection.Right; x+= boxSize*3/2) { //if source pixel is opaque if (myRegion[x, y].A == 255) { rndWidth = rnd.Next(minSpacing, maxSpacing); rndHeight = rnd.Next(minSpacing, maxSpacing); wOffset = rndWidth+halfBoxSize-Amount3+3; hOffset = rndHeight+halfBoxSize-Amount4+6; rndWidth *=3; rndHeight *=3; // draw a filled rectangle, followed by an outline to get a two-toned box deviceContext.FillRectangle(x-wOffset, y-hOffset, boxSize+rndWidth, boxSize+rndHeight, fillBrush); deviceContext.DrawRectangle(x-wOffset, y-hOffset, boxSize+rndWidth, boxSize+rndHeight, outlineBrush, thickness); // in the line above I'm omitting boxStrokeStyle as it is the default setting [ lines 70 & 71 ] } } } if(Amount7){ // overlay the sourceimage deviceContext.DrawImage(Environment.SourceImage); } } 2 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 Link to comment Share on other sites More sharing options...
Tactilis Posted September 15 Share Posted September 15 5 hours ago, Ego Eram Reputo said: More progress It's great that you are documenting this progression 😃 Looks like you need to set a minimum box size... Quote Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted September 15 Author Share Posted September 15 13 hours ago, Tactilis said: Looks like you need to set a minimum box size... 👍 I'm using something like a jitter to vary the box size and another to vary the placement. When both align it occasionally conspires to miss the object edge. I'll continue to refine 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 Link to comment Share on other sites More sharing options...
BoltBait Posted September 15 Share Posted September 15 Here is a gift, @Ego Eram Reputo Spoiler // Name: Box Outlining GPU // Submenu: test // Author: // Title: // Version: // Desc: // Keywords: // URL: // Help: // NSC #region UICode IntSliderControl Amount2 = 25; // [15,100] Size IntSliderControl Amount5 = 2; // [1,12] Border Thickness RadioButtonControl Amount6 = 0; // Coloring|Black/Gray|Primary/Secondary|Secondary/Primary IntSliderControl Amount3 = 0; // [-100,100] X position IntSliderControl Amount4 = 0; // [-100,100] Y position CheckboxControl Amount7 = true; // Preserve Source Object (uncheck to overwrite) CheckboxControl OutlineRooms = true; // Outline Rooms? IntSliderControl OutlineRadius = 5; // [0,100] {OutlineRooms} Outline Radius CheckboxControl shadows = true; // Render shadows AngleControl shadowDir = -45; // [-180,180] {shadows} Shadow Direction IntSliderControl shadowDest = 50; // [0,100] {shadows} Shadow Distance CheckboxControl grid = true; // Draw Grid IntSliderControl gridSize = 50; // [10,100] {grid} Grid size IntSliderControl gridXOffset = 0; // [0,100] {grid} xOffset IntSliderControl gridYOffset = 0; // [0,100] {grid} yOffset #endregion protected override unsafe void OnDraw(IDeviceContext deviceContext) { // Preserve the background image by drawing (copying?) the source image to the output canvas (pointed to by "deviceContext") deviceContext.DrawImage(Environment.SourceImage); // Gets the current selection from the environment and puts it into the "selection" integer rectangle variable. // References the current selection, if there is one. If there isn't one, it returns the entire layer. RectInt32 selection = Environment.Selection.RenderBounds; // variables int boxSize = Amount2*4/11; // *4/11 = /2.75 larger numbers makes the boxes bigger int halfBoxSize = boxSize/2; int thickness = Amount5; int rndHeight, rndWidth; int minSpacing = (int)Math.Sqrt(boxSize)+1;// increase for more variation in sizes int maxSpacing = minSpacing+halfBoxSize+3; int wOffset, hOffset; ISolidColorBrush fillBrush = deviceContext.CreateSolidColorBrush(LinearColors.LightGray); ISolidColorBrush outlineBrush = deviceContext.CreateSolidColorBrush(LinearColors.Black); // This is now of type ManagedColor instead of ColorBgra32 ManagedColor primaryColorM = Environment.PrimaryColor; ManagedColor secondaryColorM = Environment.SecondaryColor; // You can also use a ManagedColorProperty with IndirectUI to let the user select a color with a Color Wheel // e.g. ManagedColorProperty mcm = new ManagedColorProperty(PropertyNames.Color1, Environment.PrimaryColor, ManagedColorPropertyAlphaMode.SupportsAlpha); // Behind the scenes, the device context is tagged with the appropriate color space / color context // I've rigged it up so _you_ don't have to worry about color space or color context or linear or companded or whatever // Because what _you_ want is just the appropriate color value for rendering with the device context ColorRgba128Float primaryColor = primaryColorM.Get(deviceContext); ColorRgba128Float secondaryColor = secondaryColorM.Get(deviceContext); // define any brushes and stroke styles switch (Amount6) { case 0: fillBrush = deviceContext.CreateSolidColorBrush(LinearColors.LightGray); outlineBrush = deviceContext.CreateSolidColorBrush(LinearColors.Black); break; case 1: fillBrush = deviceContext.CreateSolidColorBrush(secondaryColor); outlineBrush = deviceContext.CreateSolidColorBrush(primaryColor); break; case 2: fillBrush = deviceContext.CreateSolidColorBrush(primaryColor); outlineBrush = deviceContext.CreateSolidColorBrush(secondaryColor); break; } // The following line is unncessary if the strokestyle is default. //IStrokeStyle boxStrokeStyle = deviceContext.Factory.CreateStrokeStyle(StrokeStyleProperties.Default); // setup drawing mode deviceContext.AntialiasMode = AntialiasMode.Aliased; // or .PerPrimitive // Initialize and seed the random number generator Random rnd = new Random(); // The next four lines allow polling of the source image.A value // Rick said .....use Environment.GetSourceBitmapBgra32(), which will give you an IEffectInputBitmap<ColorBgra32> IEffectInputBitmap<ColorBgra32> sourceImage = Environment.GetSourceBitmapBgra32(); RectInt32 rect = new RectInt32(0,0,sourceImage.Size); // .......then call Lock() to get the IBitmapLock<ColorBgra32>(). IBitmapLock<ColorBgra32> myLock = sourceImage.Lock(rect); // .....then use AsRegionPtr() to get it as a RegionPtr<ColorBgra32> RegionPtr<ColorBgra32> myRegion = myLock.AsRegionPtr(); // It is myRegion[x,y] that can be examined for the alpha value. // Note this technique is not valid for checking the RGB values because of color management. Here's how Rick explained it.... // "Note that this works fine for querying the alpha channel, as the alpha channel is invariant with respect to color space (sRGB vs. linear scRGB vs. whatever other // color profile they might have on the image!). If you want to query the RGB values, they won't line up with the color space you're using in the GPU effect, and it'll // take some extra work to determine the right thing to do. So that's a topic for another time." // // A GPUDrawing effect has a main function called OnDraw() with color type LinearColors // Step across the selection (or layer if no selection is active), rendering boxes where the source pixel is opaque. for (int y = selection.Top; y < selection.Bottom; y+= boxSize*3/2) { for (int x = selection.Left; x < selection.Right; x+= boxSize*3/2) { //if source pixel is opaque if (myRegion[x, y].A == 255) { rndWidth = rnd.Next(minSpacing, maxSpacing); rndHeight = rnd.Next(minSpacing, maxSpacing); wOffset = rndWidth+halfBoxSize-Amount3+3; hOffset = rndHeight+halfBoxSize-Amount4+6; rndWidth *=3; rndHeight *=3; // draw a filled rectangle, followed by an outline to get a two-toned box deviceContext.FillRectangle(x-wOffset, y-hOffset, boxSize+rndWidth, boxSize+rndHeight, fillBrush); deviceContext.DrawRectangle(x-wOffset, y-hOffset, boxSize+rndWidth, boxSize+rndHeight, outlineBrush, thickness); // in the line above I'm omitting boxStrokeStyle as it is the default setting [ lines 70 & 71 ] } } } if(Amount7) { // overlay the sourceimage deviceContext.DrawImage(CreateOutlinedOutput(deviceContext, OutlineRooms)); if(grid) { // draw grid lines RectFloat sourceBounds = new RectFloat(Point2Float.Zero, Environment.Document.Size); IDirect2DFactory d2dFactory = this.Services.GetService<IDirect2DFactory>(); ISolidColorBrush grayBrush = deviceContext.CreateSolidColorBrush(LinearColors.Gray); for (float x = 0; x < sourceBounds.Width; x += gridSize) { deviceContext.DrawLine(new Point2Float(x+gridXOffset, 0+gridYOffset), new Point2Float(x+gridXOffset, sourceBounds.Bottom+gridYOffset), grayBrush, 1, d2dFactory.CreateStrokeStyle(new StrokeStyleProperties(CapStyle.Flat, CapStyle.Flat, CapStyle.Flat, LineJoin.Round, 10f, PaintDotNet.Direct2D1.DashStyle.Dash, 0f, StrokeTransformType.Normal))); } for (float y=0; y < sourceBounds.Height; y+=gridSize) { deviceContext.DrawLine(new Point2Float(0+gridXOffset, y+gridYOffset), new Point2Float(sourceBounds.Right+gridXOffset, y+gridYOffset), grayBrush, 1, d2dFactory.CreateStrokeStyle(new StrokeStyleProperties(CapStyle.Flat, CapStyle.Flat, CapStyle.Flat, LineJoin.Round, 10f, PaintDotNet.Direct2D1.DashStyle.Dash, 0f, StrokeTransformType.Normal))); } } } } IDeviceImage CreateOutlinedOutput(IDeviceContext deviceContext, bool outline = false) { // create white floor from object on source canvas GrayscaleEffect grayscaleEffect = new GrayscaleEffect(deviceContext); grayscaleEffect.Properties.Input.Set(Environment.SourceImage); PdnHueSaturationLightnessEffect saturationEffect = new PdnHueSaturationLightnessEffect(deviceContext); saturationEffect.Properties.Input.Set(grayscaleEffect); saturationEffect.Properties.LightnessDelta.SetValue(1); if (!outline) return saturationEffect; // draw outline around floor CroppedFloodEffect outlineSurface = new CroppedFloodEffect(deviceContext); outlineSurface.Properties.Color.SetValue(ColorBgra.Black); outlineSurface.Properties.Rect.SetValue(Environment.SourceImage.GetLocalBounds(deviceContext)); MixEffect mixMaskEffect = new MixEffect(deviceContext); mixMaskEffect.SetInput(0, outlineSurface); mixMaskEffect.SetInput(1, saturationEffect); mixMaskEffect.Properties.Mode.SetValue(MixMode.CompositeSourceOver); GaussianBlurEffect2 blurEffect2 = new GaussianBlurEffect2(deviceContext); blurEffect2.Properties.AlphaMode.SetValue(GaussianBlurAlphaMode2.Straight); blurEffect2.Properties.Optimization.SetValue(GaussianBlurOptimization2.HighQuality); blurEffect2.Properties.StandardDeviation.SetValue(StandardDeviation.FromRadius(OutlineRadius*2)); blurEffect2.SetInput(0, mixMaskEffect); UnPremultiplyEffect SourceImageStraightAlpha = new UnPremultiplyEffect(deviceContext); SourceImageStraightAlpha.Properties.Input.Set(Environment.SourceImage); UnPremultiplyEffect OutlineStraightAlpha = new UnPremultiplyEffect(deviceContext); OutlineStraightAlpha.Properties.Input.Set(blurEffect2); InputSwizzleEffect inputSwizzleEffect = new InputSwizzleEffect(deviceContext); inputSwizzleEffect.Properties.AlphaMode.SetValue(InputSwizzleAlphaMode.Straight); inputSwizzleEffect.InputCount = 2; inputSwizzleEffect.SetInput(0, SourceImageStraightAlpha); inputSwizzleEffect.SetInput(1, OutlineStraightAlpha); inputSwizzleEffect.Properties.RedInputIndex.SetValue(1); inputSwizzleEffect.Properties.GreenInputIndex.SetValue(1); inputSwizzleEffect.Properties.BlueInputIndex.SetValue(1); inputSwizzleEffect.Properties.AlphaInputIndex.SetValue(0); inputSwizzleEffect.Properties.AlphaInputChannel.SetValue(ChannelSelector.A); PremultiplyEffect premultiplyEffect = new PremultiplyEffect(deviceContext); premultiplyEffect.Properties.Input.Set(inputSwizzleEffect); PdnBrightnessContrastEffect contrastEffect = new PdnBrightnessContrastEffect(deviceContext); contrastEffect.Properties.Input.Set(premultiplyEffect); contrastEffect.Properties.Contrast.SetValue(1); contrastEffect.Properties.Brightness.SetValue(-1); contrastEffect.Properties.AlphaMode.SetValue(PdnBrightnessContrastAlphaMode.Premultiplied); if (!shadows) return contrastEffect; // draw shadow on floor PdnMotionBlurEffect directionalBlurEffect = new PdnMotionBlurEffect(deviceContext); directionalBlurEffect.SetInput(0, mixMaskEffect); directionalBlurEffect.Properties.Angle.SetValue((float)shadowDir); directionalBlurEffect.Properties.Distance.SetValue(shadowDest); directionalBlurEffect.Properties.Centered.SetValue(false); UnPremultiplyEffect BlurStraightAlpha = new UnPremultiplyEffect(deviceContext); BlurStraightAlpha.Properties.Input.Set(directionalBlurEffect); InputSwizzleEffect shadowSwizzleEffect = new InputSwizzleEffect(deviceContext); shadowSwizzleEffect.Properties.AlphaMode.SetValue(InputSwizzleAlphaMode.Straight); shadowSwizzleEffect.InputCount = 2; shadowSwizzleEffect.SetInput(0, SourceImageStraightAlpha); shadowSwizzleEffect.SetInput(1, BlurStraightAlpha); shadowSwizzleEffect.Properties.RedInputIndex.SetValue(1); shadowSwizzleEffect.Properties.GreenInputIndex.SetValue(1); shadowSwizzleEffect.Properties.BlueInputIndex.SetValue(1); shadowSwizzleEffect.Properties.AlphaInputIndex.SetValue(0); shadowSwizzleEffect.Properties.AlphaInputChannel.SetValue(ChannelSelector.A); PremultiplyEffect premultiplyEffect2 = new PremultiplyEffect(deviceContext); premultiplyEffect2.Properties.Input.Set(shadowSwizzleEffect); MixEffect mixEffectDarken = new MixEffect(deviceContext); mixEffectDarken.SetInput(0, contrastEffect); mixEffectDarken.SetInput(1, premultiplyEffect2); mixEffectDarken.Properties.Mode.SetValue(MixMode.BlendDarken); return mixEffectDarken; } Source image: Result of my code changes: NOTE: It is far from perfect... but, then... so is the rest of the plugin? EDIT: Added some more fun... 1 Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted September 16 Author Share Posted September 16 3 hours ago, BoltBait said: Here is a gift, @Ego Eram Reputo Brilliant! Thanks so much BoltBait. That will open a few new doors for me too! 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 Link to comment Share on other sites More sharing options...
BoltBait Posted September 16 Share Posted September 16 2 hours ago, Ego Eram Reputo said: Brilliant! Thanks so much BoltBait. That will open a few new doors for me too! Glad you like it! And, if it wasn't clear, when I said... 6 hours ago, BoltBait said: EDIT: Added some more fun... I added in the inner shadow shown in your original screenshot that you were trying to copy. 1 Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game Link to comment Share on other sites More sharing options...
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.