Jump to content

Box Outlining


Recommended Posts

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.

 

box-outlining.png

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 😁)

 

boxoutline2.png

 

Does anyone want to help me write something a little more elegant?

  • You're a Smart Cookie! 2
Link to comment
Share on other sites

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.

  • Like 1
Link to comment
Share on other sites

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.

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

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.

  • You're a Smart Cookie! 1
Link to comment
Share on other sites

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.

  • You're a Smart Cookie! 1

 

Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings

 

PdnForumSig2.jpg

Link to comment
Share on other sites

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!

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

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

  • Upvote 1
Link to comment
Share on other sites

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! :mrgreen:

 

boxoutliningdemo-GPU.png

 

  • Thanks 1
  • Upvote 2
  • You're a Smart Cookie! 1
Link to comment
Share on other sites

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.

  • Upvote 1

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

  • 1 month later...

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)

 

Link to comment
Share on other sites

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

  • Upvote 1

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

  • 3 months later...

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);

}

 

Link to comment
Share on other sites

  • 3 weeks later...

More progress.......

 

boxoutliningdemo.png

 

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);
}
}

 

 

  • Upvote 2
Link to comment
Share on other sites

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 :)

Link to comment
Share on other sites

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] Outline Radius
CheckboxControl shadows = true; // Render shadows
AngleControl shadowDir = -45; // [-180,180] Shadow Direction
IntSliderControl shadowDest = 50; // [0,100] Shadow Distance
#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));
}
}


IDeviceImage CreateOutlinedOutput(IDeviceContext deviceContext, bool outline = false)
{
    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;
    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;
    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:

 

image.png

 

Result of my code changes:

 

image.png

 

NOTE:  It is far from perfect... but, then... so is the rest of the plugin? :D 

 

EDIT: Added some more fun... ;) 

 

 

  • Thanks 1
Link to comment
Share on other sites

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!

Link to comment
Share on other sites

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.

 

  • Thanks 1
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...