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