Jump to content

GPU Drawing Effect Questions


Go to solution Solved by Rick Brewster,

Recommended Posts

For rotation you can use deviceContext.UseRotationAt(), which will push and pop the appropriate changes to the Transform property. You should use it with a using block.

 

I’ve got more notes to add. I’d have replied with more sooner but I’m currently nursing an arm/wrist/hand injury so I’m limited to mousing and typing with one hand 

 

 

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

I'm sorry @_koh_, I assumed you were being sarcastic and reacted badly due to past experiences on this forum.

I could not get deviceContext.Transform to show up in codelab but I was trying to access it below where I had defined my ellipse. May try again tomorrow.

NodeviceContext.png

Apologies again.

 

Just seen your post @Rick Brewster - No problem - get yourself well again!

 

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

 

PdnForumSig2.jpg

Link to comment
Share on other sites

1 hour ago, _koh_ said:

My english isn't that capable, so I wasn't even aware of you were reacting badly

 

I am English and I didn't perceive any bad reaction by @Red ochre 😉

 

 

By the way @_koh_, when RO said:

 

3 hours ago, Red ochre said:

I'll get my coat.😕

 

he is referring to https://en.wiktionary.org/wiki/I'll_get_my_coat

 

There's much to get lost in translation!

 

 


 

  • Thanks 1
  • Upvote 1
Link to comment
Share on other sites

On the subject of color spaces and "companded" vs. "linear", I would like to suggest compiling against the 5.1 Beta. I don't think CodeLab supports ManagedColor yet. It firstly provides a color value that is tagged with its color space, and makes it very easy to retrieve the color value you need to use for the device context's color space. Second, it provides a ManagedColorProperty for use with IndirectUI that is a better way of implementing a Color Wheel than the goofy way I repurposed Int32Property.

 

The whole notion of taking the ColorBgra32, casting to SrgbColorA, then LinearColorA, then ColorRgba128Float ... etc. This was an alright solution for 5.0, but it has actually quickly become obsolete with the color management systems in 5.1 which are substantially more sophisticated and hopefully not more complicated. I would also really like to have another person trying out the new API (ManagedColor) to get some feedback on it.

 

The code would look kinda like this:

// This is now of type ManagedColor instead of ColorBgra32
ManagedColor primaryColorM = Environment.PrimaryColor; 

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

ISolidColorBrush primaryBrush = deviceContext.CreateSolidColorBrush(primaryColor);
deviceContext.FillRectangle(..., primaryBrush); // or draw whatever you want of course

 

(I'm still catching up on the rest of the thread...)

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

21 hours ago, _koh_ said:

I'm saying you can do this to clamp sample coordinate.

 

int mcxi = (int)Math.Clamp(mcx, selection.Left, selection.Right);
int mcyi = (int)Math.Clamp(mcy, selection.Top, selection.Bottom);

 

You'll actually need to clamp to Right-1 and Bottom-1 if you're going to pass these to something like RegionPtr<T>'s indexer (or anything that treats a bitmap effectively as an array)

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

On 8/31/2024 at 4:04 PM, Red ochre said:
ColorBgra PC = Environment.PrimaryColor;//thanks Rick!

 

Highly recommend not using ColorBgra anymore, and instead using ColorBgra32. They have implicit casts between each other, but ColorBgra is the "old" color class with a lot of baggage and other weird-isms from 2004 through 2010 or so. It hasn't really seen any improvements or upgrades since then.

 

But also it's just nice to be consistently using the new types and systems.

 

I do plan on eventually tagging ColorBgra with [Obsolete]. There's a lot of internal PDN code that needs to be migrated first (which I just haven't gotten to)

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

On 8/31/2024 at 4:04 PM, Red ochre said:
IStrokeStyle PriStrokeStyle = deviceContext.Factory.CreateStrokeStyle(StrokeStyleProperties.Default);

 

btw if you're going to use the default stroke style you can just use null instead. The default value for a stroke style parameter is always null so you can usually just omit it entirely.

 

I saw this in @Ego Eram Reputo's code too

  • 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

On 8/31/2024 at 4:04 PM, Red ochre said:
double dist = Math.Sqrt((Xdiff * Xdiff) + (Ydiff * Ydiff));

 

btw .NET now has a MathF class which can help to avoid a lot of casting back to float after using Math for calculations that result in doubles.

 

Personally I like to keep everything as double until the very end and only cast to float for the actual drawing calls.

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

On 8/31/2024 at 4:04 PM, Red ochre said:
C1 = (byte)((iDrat * PC.R) + (Drat * SC.R));//use companded colorspace to mix

 

On 9/1/2024 at 2:10 AM, _koh_ said:

How brush color interact image color is a human perception thing in my view, so using sRGB brush is my preferable choice so far.

 

I strongly disagree. Using companded values does work very well for certain calculations, but not for blending or sampling. The only reason to do blending or sampling in companded space is if you really want to mimic how the drawing tools and layer blend modes currently operate. They are going to be upgraded to support linear, hopefully in the 6.0 timeframe along with all the revamps and upgrades for the .PDN file format and FileType plugins.

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

This is an example of a simple GpuDrawingEffect btw. Not sure if I already linked to it: https://github.com/paintdotnet/PdnV5EffectSamples/blob/main/Gpu/RainbowGpuEffect.cs

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

13 hours ago, Rick Brewster said:

Using companded values does work very well for certain calculations, but not for blending or sampling.

 

While I understand linear blending gives me physically accurate results, when I use a brush, I'm trying to change the image color by perceivable amount, and usually have intended result in mind. So getting the physically accurate average for given colors / alphas isn't my priority, it only needs to function in a predictable / controllable manner.

 

sRGB scale / linear scale

image.png.bed539863feda63b31104a9c9fed4fbc.pngimage.png.367202619b90ed55cbb537dc85ec6262.png


Our eyes are more sensitive to the darker tones, and gamma encoded color spaces are adjusted for that.
Using a brush is like adjusting sliders along these scales. So it's more easier to get the intended color with sRGB brush than linear one.

I may change my mind when I actually used linear brush, but this is how I see things for now.

 

edit:

Some tests. Umm, I can't decide.

 

B -> R gradation looks better in linear. sRGB(0.5, 0, 0.5) looks visibly darker than (1, 0, 0) or (0, 0, 1) as expected.

 

sRGB / linear

image.png.8d15cf684f97010a15ca7f91fe9ae476.pngimage.png.14422e890a951ea1e5c81d8445af9097.png

Brush size looks consistent in sRGB.

 

sRGB / sRGB inverse

image.png.830e29a3bd8be95bfbfd5c5f3483fe3a.pngimage.png.d24cbdd71d2b38eea2aa846d5501f26e.png

 

linear / linear inverse

image.png.1386e31b9d6f4fcf0fef94809a270029.pngimage.png.0595e29e1e580fb6c6f6e0cd1f04f59f.png

 

edit2:

Feels like
when R:G:B is consistent, sRGB gradation looks linear
when R+G+B is consistent, linear gradation looks linear
this... kinda making sense.

 

I wonder how Lab gradation will look. Covers both cases?
I know its purpose but never actually tested.
 

  • Upvote 1
Link to comment
Share on other sites

On 9/3/2024 at 2:14 AM, Rick Brewster said:

I saw this in @Ego Eram Reputo's code too

Same source! 🙂 https://boltbait.com/pdn/CodeLab/help/tutorial/drawing/

 

Hope your finding it easier to type now, no problem with quick replies - I have many less enjoyable real life chores to get on with.

Everything you have written has been very useful but it may be a few days before I can efficiently ask about the (still many) things I don't understand, but thanks for keeping an eye on this thread.

 

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

 

PdnForumSig2.jpg

Link to comment
Share on other sites

46 minutes ago, Red ochre said:

 

Yes, what @Rick Brewster says is true.  However, putting null in the example code would be much less discoverable than showing how to create the default.

 

I think someone following the example might see "StrokeStyleProperties.Default" and get curious to see what else could replace "Default" there.

 

  • Thanks 2
  • Upvote 2
Link to comment
Share on other sites

Didn't mean to criticize  @BoltBait and your tutorials are appreciated!... yet the options for dashes etc. seem less than obvious.

An example of how to set the StrokeStyle would be really useful!... syntax isn't always obvious.

... Possibly obvious for experienced coders, (but they probably don't need a tutorial)... very difficult to set the understanding threshold but as someone who has learnt a lot from your tutorials, I think the more you put in the better. The reader can then cross-reference what you have said and gain a better understanding.

 

So yes, I am curious, but don't find the ways to set StrokeStyleProperties straightforward?- All code examples are really useful!

... and many thanks for all the previous tutorials (there wouldn't be a Redochre pack without them!).

 

 

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

 

PdnForumSig2.jpg

Link to comment
Share on other sites

1 hour ago, BoltBait said:

However, putting null in the example code would be much less discoverable than showing how to create the default.

 

Yup that's a perfectly good reason to do the sample code that way. And if someone never learns/realizes that they can just use null instead ... oh well. It's harmless. The benefit gained from stoking curiosity is better.

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

  • 2 weeks later...

 

On 9/2/2024 at 5:12 AM, Rick Brewster said:

For rotation you can use deviceContext.UseRotationAt(), which will push and pop the appropriate changes to the Transform property. You should use it with a using block.

Unfortunately I haven't had any success trying to rotate ellipses and really need a basic code example to make any progress.

Here is the latest .dll (compiled against Pdn 5.100.9004.30371 portable)... won't run on Pdn 5.0.13 due to new way of accessing Primary color.

SlinkyPlus3_07.zip

The annotated code is below. Any suggestions for better ways to do the pixel conversions etc. is very welcome.

Spoiler
// Name:SlinkyPlus
// Submenu:Iterative lines
// Author:John Robbins (Red ochre)
// Title:SlinkyPlus 3_07         Sept 2024       Red ochre
// Version:3.07
// Desc:Draws varying shapes along a path.
// Keywords:Circle, Ellipse, Triangle, Rectangle, Square, Polygon
// URL:
// Help:

// For help writing a GPU Drawing plugin: https://boltbait.com/pdn/CodeLab/help/tutorial/drawing/

#region UICode
IntSliderControl Amount1 = 30; // [2,1000] Number of shapes
PanSliderControl Amount2 = new Vector2Double(0.000, 0.000); // Start position
IntSliderControl Amount3 = 100; // [0,1000] Start size
DoubleSliderControl Amount4 = 0; // [-10,10] Wide....................Start shape......................Tall
DoubleSliderControl Amount13 = 0; // [-180,180] Start angle
PanSliderControl Amount5 = new Vector2Double(0.000, 0.000); // End position
IntSliderControl Amount6 = 100; // [0,1000] End size
DoubleSliderControl Amount7 = 0; // [-10,10] Wide....................End shape........................Tall
DoubleSliderControl Amount14 = 0; // [-180,180] End angle
ListBoxControl Amount8 = 0; // Curve type|semi-circular|semi-sinosoidal|sinosoidal
DoubleSliderControl Amount9 = 0; // [-200,200] Curvature %
IntSliderControl Amount10 = 10; // [1,100] Line width
ListBoxControl Amount11 = 0; // Colour options|YRMBCG Rainbow|CBMRYG Rainbow|MRYGCB Rainbow|Primary|Secondary|Primary to Secondary|NEW Primary to Secondary|Colours from source image
CheckboxControl Amount12 = true; // Clear background
#endregion

protected override unsafe void OnDraw(IDeviceContext deviceContext)
{
   if(!Amount12){deviceContext.DrawImage(Environment.SourceImage);}//Thanks Boltbait!
    RectInt32 selection = Environment.Selection.RenderBounds;
   
 //Safely get the src image to use for colour option 6 later
    using IEffectInputBitmap<ColorBgra32> sourceBitmap = Environment.GetSourceBitmapBgra32();
    using IBitmapLock<ColorBgra32> sourceLock = sourceBitmap.Lock(new RectInt32(0, 0, Environment.Document.Size));
    RegionPtr<ColorBgra32> sourceRegion = sourceLock.AsRegionPtr();

    
  //old  ColorBgra PC = Environment.PrimaryColor;//thanks Rick!... this is for Pdn v 5.0 series
  ManagedColor primaryColorM = Environment.PrimaryColor; 
  ColorRgba128Float PC = primaryColorM.Get(deviceContext);
  ManagedColor secondaryColorM = Environment.SecondaryColor; 
  ColorRgba128Float SC = secondaryColorM.Get(deviceContext);

  
    int lineW = Amount10;
    int Stop = selection.Top;
    int Sbott = selection.Bottom;
    int Sleft = selection.Left;
    int Srite = selection.Right;
    int H = Sbott - Stop;
    int W = Srite - Sleft;
    double dx = W / 2;
    double dy = H / 2;
    double Pi = Math.PI;
    double Pi2 = Pi * 2;
    int LineNum = Amount1;
    float EstarAng = (float)Amount13;//D2D transform takes float angle in degrees
    float EendAng = (float)Amount14;// NOT used yet because can't get RotateAt to work!
    float EdiffAng = EstarAng - EendAng;
    if(EdiffAng < 0){EdiffAng += 360f;}
    if(EdiffAng > 360){EdiffAng -= 360f;}
    float EiterAng = EdiffAng/(LineNum - 1f);
 //start
    double A1x = Amount2.X; double A1y = Amount2.Y;//CP1 
    float staS = Amount3;//start size
    double starat = Amount4 / 10;//stErat = start Ellipse ratio
    float Swidth = staS; if (starat > 0) { Swidth = (float)(staS * (1 - starat)); }
    float Sheight = staS; if (starat < 0) { Sheight = (float)(staS * (1 + starat)); }
 //end
    double A2x = Amount5.X; double A2y = Amount5.Y;//CP2 - end
    float endS = Amount6;// end size
    double endrat = Amount7 / 10;// = end Ellipse ratio
    float Ewidth = endS; if (endrat > 0) { Ewidth = (float)(endS * (1 - endrat)); }
    float Eheight = endS; if (endrat < 0) { Eheight = (float)(endS * (1 + endrat)); }
 // NEW need half values of start and end widths and heights to offset positions
    float halfstaW = Swidth/2f;float halfstaH = Sheight/2f;
    float halfendW = Ewidth/2f;float halfendH = Eheight/2f;
 // differences
    float Wdiff = Ewidth - Swidth;
    float Hdiff = Eheight - Sheight;
 // curvature
    double curvrat = Amount9 / 100;
 /* Pan slider defaults and range cannot be edited in codelab 6.12.
    So here, if both pan sliders are at zero I set the start and end coords.
    When compiled in VS this can go and these defaults and a range of 2 to -2 set in OnCreatePropertyCollection()
 */
    if (A1x == 0 && A1y == 0 && A2x == 0 && A2y == 0){ A1x = -0.5; A1y = -0.5; A2x = 0.5; A2y = 0.5; }

 // The ellipse are drawn from the centre coord BUT the major and minor radii must be factored in for position
    float Cp1x = (float)(Sleft + halfstaW + ((A1x + 1) * dx));
    float Cp1y = (float)(Stop + halfstaH + ((A1y + 1) * dy));
    float Cp2x = (float)(Sleft + halfendW + ((A2x + 1) * dx));
    float Cp2y = (float)(Stop + halfendH + ((A2y + 1) * dy));
 // define your brush and stroke style... StrokeStyle not needed here so use 'null' when drawing the ellipses
    ISolidColorBrush myBrush = deviceContext.CreateSolidColorBrush(PC);//previously 'priBrush' 
 //  IStrokeStyle myStrokeStyle = deviceContext.Factory.CreateStrokeStyle(StrokeStyleProperties.Default);//not need for this

 // setup drawing mode
    deviceContext.AntialiasMode = AntialiasMode.PerPrimitive;  // or .Aliased
    

 // Create points for curve, the path the ellipses take.
    Point2Float CP1 = new Point2Float(Cp1x, Cp1y);//start ellipse centre

    float Xdiff = Cp2x - Cp1x;
    float Ydiff = Cp2y - Cp1y;
    float LN = LineNum - 1;
    float xdiv = Xdiff / LN;
    float ydiv = Ydiff / LN;
    float Wdiv = Wdiff / LN;
    float Hdiv = Hdiff / LN;

    double dist = Math.Sqrt((Xdiff * Xdiff) + (Ydiff * Ydiff));
    float Ddiv = (float)(dist / LN);// minimum number of ellipses now 2 

    double stangle = Math.Atan2(Ydiff, Xdiff);
    double tangangle = stangle + (Pi / 2);

// The loop
for (int N = 0; N < LineNum; N++)
            {
               
                //curvature
                double curve = 0;
                double Drat2 = (double)(N) / (double)(LineNum);
                double r = dist / 2;

                switch (Amount8)
                {
                    case 2://semi-circular
                        curve = r * Math.Sqrt(Math.Sin(Drat2 * Pi)) * curvrat;
                        break;
                    case 0://semi-sinusoidal
                        curve = r * Math.Sin(Drat2 * Pi) * curvrat;
                        break;
                    case 1://sinusoidal
                        curve = r * Math.Sin(Drat2 * Pi * 2) * curvrat;
                        break;
                }

                float Xcurve = (float)(curve * Math.Cos(tangangle));
                float Ycurve = (float)(curve * Math.Sin(tangangle));


                float Wrad = (float)(Swidth + (N * Wdiv));
                float Hrad = (float)(Sheight + (N * Hdiv));
                float mcx = (float)(Cp1x + (N * xdiv)) - (Wrad / 2) + Xcurve;
                float mcy = (float)(Cp1y + (N * ydiv)) - (Hrad / 2) + Ycurve;
                Point2Float centre = new Point2Float(mcx, mcy);//ellipse centres
                //ellipse centres can be off canvas BUT cannot sample src pixels that don't exist or out of bounds exception!
                int mcxi = (int)Math.Clamp(mcx, selection.Left, selection.Right - 1);//Thanks _koh_ & Rick!
                int mcyi = (int)Math.Clamp(mcy, selection.Top, selection.Bottom - 1);
                //Colour options
                double Drat = (double)(N ) / (double)(LineNum - 1);//Drat correctly goes from 0 to 1.0
                double iDrat = 1 - Drat;

                //for rainbows
                double C1d = (Math.Sin((Drat * Pi2) + (Pi2 * 0.000000)) * 128) + 128; if (C1d > 255) { C1d = 255; }//adding Pi2 * 0 to 
                double C2d = (Math.Sin((Drat * Pi2) + (Pi2 * 0.666667)) * 128) + 128; if (C2d > 255) { C2d = 255; }//make comparison
                double C3d = (Math.Sin((Drat * Pi2) + (Pi2 * 0.333333)) * 128) + 128; if (C3d > 255) { C3d = 255; }//easier
                byte C1 = (byte)(Math.Clamp(C1d,0,255));//Thanks _koh_
                byte C2 = (byte)(Math.Clamp(C2d,0,255));
                byte C3 = (byte)(Math.Clamp(C3d,0,255));
                // for opacity
                float C4 = 1f;
/*
#if DEBUG
//Haven't used Debug before - very useful!
Debug.Write("N = ");Debug.Write(N);Debug.Write(", Drat = ");Debug.Write(Drat);
Debug.Write(", C1 = ");Debug.Write(C1);Debug.Write(", C2 = ");Debug.Write(C2);Debug.Write(", C3 = ");Debug.WriteLine(C3);
#endif                
*/
                switch (Amount11)
                {
                  /*Note: These rainbows are not 100% perfect but are close enough for me.
                   I could add further offset angles and choices but not really worth it.*/
                    case 0://rainbow YRMBCG
                        ColorRgb96Float RBG = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C2, C3, C1);
                        myBrush.Color = RBG;// The casts above convert from sRGB (companded) to linear colour space that D2D expects.
                        break;
                    case 1://rainbow CBMRYG
                        ColorRgb96Float BRG = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C1, C3, C2);//C1,C2,C3 need to be recalculated to get correct start/end colours
                        myBrush.Color = BRG;
                        break;
                    case 2://rainbow MRYGCB
                        ColorRgb96Float RGB = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C3, C2, C1);
                        myBrush.Color = RGB;
                        break;
                    case 3://Primary
                        myBrush.Color = PC;
                        break;
                    case 4://Secondary
                        myBrush.Color = SC;
                        break;
                    case 5://Primary to Secondary... Companded?
                      // SIMPLIFY THIS! ... can I go straight to ColorRgba128? ... see case 7
                        C1 = (byte)(((iDrat * PC.R) + (Drat * SC.R)) * 255);//I guess PC.R is 0 to 1f now?
                        C2 = (byte)(((iDrat * PC.G) + (Drat * SC.G)) * 255);
                        C3 = (byte)(((iDrat * PC.B) + (Drat * SC.B)) * 255); 
                        C4 = (float)((iDrat * PC.A) + (Drat * SC.A));
                
                        ColorRgb96Float P2C = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C3, C2, C1);
                        ColorRgba128Float P2Ca = (ColorRgba128Float)(P2C);
                        P2Ca.A = C4;
                        myBrush.Color = P2Ca;
                        // Is there an easier way to do this... construct a ColorRgba128Float from floats including opacity?
                        break;
                   case 6:// NEW Primary to Secondary... Linear?
                        float  Cf1 = (float)((iDrat * PC.R) + (Drat * SC.R));
                        float Cf2 = (float)((iDrat * PC.G) + (Drat * SC.G));
                        float Cf3 = (float)((iDrat * PC.B) + (Drat * SC.B)); 
                        float Cf4 = (float)((iDrat * PC.A) + (Drat * SC.A));
                        
                        myBrush.Color = new ColorRgba128Float(Cf1, Cf2, Cf3, Cf4);//Different graduation to case 5
                        break;
                    case 7://use source colours
                        ColorBgra32 srcCol = sourceRegion[mcxi, mcyi];//Clamped to canvas, protected from out of bounds exeptions.
                       // ColorBgra32 values are 4 * 8 bit channels (0 to 255)
                        ColorRgba128Float sColA= (LinearColor)(SrgbColor)ColorBgr24.FromBgr(srcCol.B, srcCol.G, srcCol.R);
                        sColA.A = (float)(srcCol.A)/255f;//ColorRgba128Float values are 4 * 32 bit float values (0 to 1f)
                        myBrush.Color = sColA;
/*#if DEBUG
Debug.Write("src Alpha = ") ;Debug.WriteLine(srcCol.A);
#endif*/ 
                        break;
                   
                }// end colour switch block

              //create the ellipses
              Ellipse E = new Ellipse(mcx,mcy,Wrad,Hrad);//not clamped coords
              /*
              PROBLEM:
              Trying to use 'deviceContext' here but intellisense not accepting it.
              This is weird because it is used in the DrawEllipse line below.
              If I override intellisense it says deviceContext is a variable not a type?
              If I add 'using' or using() it is still impossible?

              Ideally I would add a small rotation for each iteration of the ellipse (or other shapes later).
              I have tried every variation of syntax I can think of without success.
              I wondered if this was not present in Pdn 5.0.13, so am now using codelab v6.12 in portable 5.100.9004.30371,
              ...hence the new way of accessing Primary and Secondary colors.
             
              I really do not understand how to access or use translations and rotations, any example code would be very useful.
              Preferably in the loop here, where I'm having the problem.

              Various attempts commented out below.
              */
			  // using (deviceContext.Transform.RotateAt(20,mcx,mcy));//'void': type used in a using statement must be implicitly convertible to 'System.IDisposable'.
			 //deviceContext.Transform.RotateAt(20,mcx,mcy);//no errors but does nothing
             // deviceContext.//No intellisense options at all ??? possibly translation first but with no guide, impossible.
             // using deviceContext.UseRotationAt(EiterAng,centre);//bad
             // using (deviceContext.UseRotationAt(EiterAng,centre));//bad
              /*'IDeviceContext' does not contain a definition for 'UseRotationAt' and no accessible extension method 
              'UseRotationAt' accepting a first argument of type 'IDeviceContext' could be found 
              (are you missing a using directive or an assembly reference?)*/

              //draw the ellipses
              deviceContext.DrawEllipse(E,myBrush,lineW,null);//Ellipse,Brush,Line width, stroke style            
              
            }// end loop
}// end OnDraw

 

 

 

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

 

PdnForumSig2.jpg

Link to comment
Share on other sites

1 hour ago, Red ochre said:
If I override intellisense it says deviceContext is a variable not a type?

deviceContext is the parameter to your OnDraw() method. It is of type IDeviceContext.

 

I think the lack of, or broken, Intellisense is what's tripping you up the most here. The method isn't actually UseRotationAt(), it's (as @_koh_ has in his snippet) UseRotateAtTransform(). If Intellisense were working properly it would be easy to see this when you type UseRotat as it would show you a list of the correct methods to use.

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

@_koh_ Thank you so much - this has been driving me mad!
I need to tinker with this but initial tests are good and I now have some syntax to work with - brilliant!

 

@Rick Brewster Yes, I do sometimes find codelab's intellisense confusing but I remember before it existed at all, so very grateful for @BoltBait and @toe_head2001's efforts. The new(ish) Debug ability is very useful!

 

I can find general C# examples via W3Schools or Microsoft but for using C# within a Pdn plug-in your advice and explanations are extremely useful. Thank you!

 

slinkyplusRotation.png

  • Upvote 3

 

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

 

PdnForumSig2.jpg

Link to comment
Share on other sites

slinkyplus3_20test.png

I'm happy this is working well, so will post the .dll and codelab code below.

 

NB. It will only work on Pdn 5.1+, so will not officially publish till that becomes the current release.
I've put some notes in the code but have no problems. I do wonder if I should Dispose() of Brushes though?
If your running Pdn 5.1+, feel free to experiment with it and post any feedback.
@Rick Brewster, @_koh_, @BoltBait and all - many thanks for your help!

SlinkyPlus3_20.zip

 

Spoiler
// Name:SlinkyPlus
// Submenu:Iterative lines
// Author:John Robbins (Red ochre)
// Title:SlinkyPlus 3_20    Sept 2024    Red ochre
// Version:3.20
// Desc:Draws varying shapes along a path.
// Keywords:Circle, Ellipse, Triangle, Rectangle, Square, Pentagon, Hexagon, Heptagon, Octagon, Nonagon, Decagon, Polygon
// URL:
// Help:


#region UICode
ListBoxControl Amount16 = 4; // Shape|Ellipse|Triangle|Rectangle|Pentagon|5 star|Hexagon|Heptagon|7 star|Octagon|Nonagon|9 star|Decagon|Hendecagon|11 star
IntSliderControl Amount1 = 50; // [2,1000] Number of shapes
PanSliderControl Amount2 = new Vector2Double(0.000, 0.000); // Start position
IntSliderControl Amount3 = 100; // [0,1000] Start size
DoubleSliderControl Amount4 = 0; // [-10,10] Wide....................Start shape......................Tall
DoubleSliderControl Amount13 = 0; // [-180,180] Start angle
PanSliderControl Amount5 = new Vector2Double(0.000, 0.000); // End position
IntSliderControl Amount6 = 100; // [0,1000] End size
DoubleSliderControl Amount7 = 0; // [-10,10] Wide....................End shape........................Tall
DoubleSliderControl Amount14 = 360; // [-900,900] End angle
ListBoxControl Amount8 = 0; // Curve type|semi-circular|semi-sinosoidal|sinosoidal
DoubleSliderControl Amount9 = 0; // [-200,200] Curvature %
IntSliderControl Amount10 = 3; // [1,100] Line width
ListBoxControl Amount11 = 3; // Line colour|YRMBCG Rainbow|CBMRYG Rainbow|MRYGCB Rainbow|Primary|Secondary|Primary to Secondary|Secondary to Primary|Colours from source image
CheckboxControl Amount12 = true; // Clear background
ListBoxControl Amount15 = 3; // Fill colour|No fill|As line colour|YRMBCG Rainbow|CBMRYG Rainbow|MRYGCB Rainbow|Primary|Secondary|Primary to Secondary|Secondary to Primary|Colours from source image
#endregion

protected override unsafe void OnDraw(IDeviceContext deviceContext)
{
  /*
  Compiled with codelab v6.12 against Pdn 5.100.9004.30371 portable, (18/09/2024)

  Questions:
  Should Brushes be disposed() after the loop?

  To Do:
  1. Will not publish yet as requires Pdn v 5.1+ (due to new way of accessing Primary/Secondary colors).
  2  Will need to compile in VS to set pan-slider defaults etc.
  3. VS has better intellisense so I would rename the U.I. variables then too.
  4. Would then consider using a tabbed U.I. and possibly adding more choices and 2 color-wheels.
  5. Linear gradient brushes could look good for the fill but would over-complicate the U.I. (and code!).
  6. Add help and plug-in browser image... better icon too.
  7. Add more curve types. Possibly 'bulge/squeeze' size along path.
  8. Add More shapes? hearts, flowers etc. Could a Pdn shape geometry be used?... far too complex for me!
  */


   if(!Amount12){deviceContext.DrawImage(Environment.SourceImage);}//Thanks Boltbait!
    RectInt32 selection = Environment.Selection.RenderBounds;
   
 //Safely get the src image to use for colour option 6 later
    using IEffectInputBitmap<ColorBgra32> sourceBitmap = Environment.GetSourceBitmapBgra32();
    using IBitmapLock<ColorBgra32> sourceLock = sourceBitmap.Lock(new RectInt32(0, 0, Environment.Document.Size));
    RegionPtr<ColorBgra32> sourceRegion = sourceLock.AsRegionPtr();

    
  //old  ColorBgra PC = Environment.PrimaryColor;//thanks Rick!... this is for Pdn v 5.0 series
  ManagedColor primaryColorM = Environment.PrimaryColor; 
  ColorRgba128Float PC = primaryColorM.Get(deviceContext);
  ManagedColor secondaryColorM = Environment.SecondaryColor; 
  ColorRgba128Float SC = secondaryColorM.Get(deviceContext);

    int Vertices = 3;
    int lineW = Amount10;
    int Stop = selection.Top;
    int Sbott = selection.Bottom;
    int Sleft = selection.Left;
    int Srite = selection.Right;
    int H = Sbott - Stop;
    int W = Srite - Sleft;
    double dx = W / 2;
    double dy = H / 2;
    double Pi = Math.PI;
    double Pi2 = Pi * 2;
    int LineNum = Amount1;
    float EstarAng = (float)Amount13;//D2D transform takes float angle in degrees
    float EendAng = (float)Amount14;// NOT used yet because can't get RotateAt to work!
    float EdiffAng = EendAng - EstarAng;
    float EiterAng = EdiffAng/(LineNum - 1f);
 //start
    double A1x = Amount2.X; double A1y = Amount2.Y;//CP1 
    float staS = Amount3;//start size
    double starat = Amount4 / 10;//stErat = start Ellipse ratio
    float Swidth = staS; if (starat > 0) { Swidth = (float)(staS * (1 - starat)); }
    float Sheight = staS; if (starat < 0) { Sheight = (float)(staS * (1 + starat)); }
 //end
    double A2x = Amount5.X; double A2y = Amount5.Y;//CP2 - end
    float endS = Amount6;// end size
    double endrat = Amount7 / 10;// = end Ellipse ratio
    float Ewidth = endS; if (endrat > 0) { Ewidth = (float)(endS * (1 - endrat)); }
    float Eheight = endS; if (endrat < 0) { Eheight = (float)(endS * (1 + endrat)); }
 // NEW need half values of start and end widths and heights to offset positions
    float halfstaW = Swidth/2f;float halfstaH = Sheight/2f;
    float halfendW = Ewidth/2f;float halfendH = Eheight/2f;
 // differences
    float Wdiff = Ewidth - Swidth;
    float Hdiff = Eheight - Sheight;
 // curvature
    double curvrat = Amount9 / 100;
 /* Pan slider defaults and range cannot be edited in codelab 6.12.
    So here, if both pan sliders are at zero I set the start and end coords.
    When compiled in VS this can go and these defaults and a range of 2 to -2 set in OnCreatePropertyCollection()*/
    if (A1x == 0 && A1y == 0 && A2x == 0 && A2y == 0){ A1x = -0.5; A1y = -0.5; A2x = 0.5; A2y = 0.5; }

 // The shapes points are calculated from the centre coord BUT the major and minor radii must be factored in for position
    float Cp1x = (float)(Sleft + halfstaW + ((A1x + 1) * dx));
    float Cp1y = (float)(Stop + halfstaH + ((A1y + 1) * dy));
    float Cp2x = (float)(Sleft + halfendW + ((A2x + 1) * dx));
    float Cp2y = (float)(Stop + halfendH + ((A2y + 1) * dy));

    

 // Create points for curve, the path the ellipses take.
    Point2Float CP1 = new Point2Float(Cp1x, Cp1y);//start ellipse centre

    float Xdiff = Cp2x - Cp1x;
    float Ydiff = Cp2y - Cp1y;
    float LN = LineNum - 1;
    float xdiv = Xdiff / LN;
    float ydiv = Ydiff / LN;
    float Wdiv = Wdiff / LN;
    float Hdiv = Hdiff / LN;

    double dist = Math.Sqrt((Xdiff * Xdiff) + (Ydiff * Ydiff));
    float Ddiv = (float)(dist / LN);// minimum number of ellipses now 2 

    double stangle = Math.Atan2(Ydiff, Xdiff);
    double tangangle = stangle + (Pi / 2);

 // define your brush and stroke style... StrokeStyle not needed here so use 'null' when drawing the ellipses
    ISolidColorBrush lineBrush = deviceContext.CreateSolidColorBrush(PC);//previously 'priBrush' - rename it to 'lineBrush' in VS
    ISolidColorBrush fillBrush = deviceContext.CreateSolidColorBrush(PC);
    deviceContext.AntialiasMode = AntialiasMode.PerPrimitive;  // or .Aliased

 
    //variables for polygons declared outside loop 
    float angle = MathF.PI * (2f/Vertices);
    Point2Float vert;

// The loop
for (int N = 0; N < LineNum; N++)
            {
               
                //curvature
                double curve = 0;
                double Drat2 = (double)(N) / (double)(LineNum);
                double r = dist / 2;

                switch (Amount8)
                {
                    case 2://semi-circular
                        curve = r * Math.Sqrt(Math.Sin(Drat2 * Pi)) * curvrat;
                        break;
                    case 0://semi-sinusoidal
                        curve = r * Math.Sin(Drat2 * Pi) * curvrat;
                        break;
                    case 1://sinusoidal
                        curve = r * Math.Sin(Drat2 * Pi * 2) * curvrat;
                        break;
                }

                float Xcurve = (float)(curve * Math.Cos(tangangle));
                float Ycurve = (float)(curve * Math.Sin(tangangle));


                float Wrad = (float)(Swidth + (N * Wdiv));
                float Hrad = (float)(Sheight + (N * Hdiv));
                float mcx = (float)(Cp1x + (N * xdiv)) - (Wrad / 2) + Xcurve;
                float mcy = (float)(Cp1y + (N * ydiv)) - (Hrad / 2) + Ycurve;
                Point2Float centre = new Point2Float(mcx, mcy);//ellipse centres
                //ellipse centres can be off canvas BUT cannot sample src pixels that don't exist or out of bounds exception!
                int mcxi = (int)Math.Clamp(mcx, selection.Left, selection.Right - 1);//Thanks _koh_ & Rick!
                int mcyi = (int)Math.Clamp(mcy, selection.Top, selection.Bottom - 1);
                //Colour options
                double Drat = (double)(N ) / (double)(LineNum - 1);//Drat correctly goes from 0 to 1.0
                double iDrat = 1 - Drat;

                //for rainbows
                double C1d = (Math.Sin((Drat * Pi2) + (Pi2 * 0.000000)) * 128) + 128; if (C1d > 255) { C1d = 255; }//adding Pi2 * 0 to 
                double C2d = (Math.Sin((Drat * Pi2) + (Pi2 * 0.666667)) * 128) + 128; if (C2d > 255) { C2d = 255; }//make comparison
                double C3d = (Math.Sin((Drat * Pi2) + (Pi2 * 0.333333)) * 128) + 128; if (C3d > 255) { C3d = 255; }//easier
                byte C1 = (byte)(Math.Clamp(C1d,0,255));//Thanks _koh_
                byte C2 = (byte)(Math.Clamp(C2d,0,255));
                byte C3 = (byte)(Math.Clamp(C3d,0,255));
              

              
                switch (Amount11)//line colour
                {
                  /*Note: These rainbows are not 100% perfect but are close enough for me.
                   I could add further offset angles and choices but not really worth it.*/
                    case 0://rainbow YRMBCG
                        ColorRgb96Float RBG = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C2, C3, C1);
                        lineBrush.Color = RBG;// The casts above convert from sRGB (companded) to linear colour space that D2D expects.
                        break;
                    case 1://rainbow CBMRYG
                        ColorRgb96Float BRG = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C1, C3, C2);//C1,C2,C3 need to be recalculated to get correct start/end colours
                        lineBrush.Color = BRG;
                        break;
                    case 2://rainbow MRYGCB
                        ColorRgb96Float RGB = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C3, C2, C1);
                        lineBrush.Color = RGB;
                        break;
                    case 3://Primary
                        lineBrush.Color = PC;
                        break;
                    case 4://Secondary
                        lineBrush.Color = SC;
                        break;
                    case 5:// NEW Primary to Secondary... Linear?
                        float Cf1 = (float)((iDrat * PC.R) + (Drat * SC.R));
                        float Cf2 = (float)((iDrat * PC.G) + (Drat * SC.G));
                        float Cf3 = (float)((iDrat * PC.B) + (Drat * SC.B)); 
                        float Cf4 = (float)((iDrat * PC.A) + (Drat * SC.A));
                        lineBrush.Color = new ColorRgba128Float(Cf1, Cf2, Cf3, Cf4);
                        break;
                     case 6:// Secondary to Primary
                        float Cf1s = (float)((iDrat * SC.R) + (Drat * PC.R));
                        float Cf2s = (float)((iDrat * SC.G) + (Drat * PC.G));
                        float Cf3s = (float)((iDrat * SC.B) + (Drat * PC.B)); 
                        float Cf4s = (float)((iDrat * SC.A) + (Drat * PC.A));
                        lineBrush.Color = new ColorRgba128Float(Cf1s, Cf2s, Cf3s, Cf4s);
                        break;
                    case 7://use source colours
                        ColorBgra32 srcCol = sourceRegion[mcxi, mcyi];//Clamped to canvas, protected from out of bounds exeptions.
                       // ColorBgra32 values are 4 * 8 bit channels (0 to 255)
                        ColorRgba128Float sColA= (LinearColor)(SrgbColor)ColorBgr24.FromBgr(srcCol.B, srcCol.G, srcCol.R);
                        sColA.A = (float)(srcCol.A)/255f;//ColorRgba128Float values are 4 * 32 bit float values (0 to 1f)
                        lineBrush.Color = sColA;
                        break;
                   }// end line colour switch block ----------------------------------------------------------------

                  switch (Amount15)//fill colour
                        {
                    case 0://no fill
                        fillBrush.Color = new ColorRgba128Float(0,0,0,0);
                        break;
                    case 1:// as line colour
                        fillBrush.Color = lineBrush.Color;
                        break;
                    case 2://rainbow YRMBCG
                        ColorRgb96Float RBG = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C2, C3, C1);
                        fillBrush.Color = RBG;
                        break;
                    case 3://rainbow CBMRYG
                        ColorRgb96Float BRG = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C1, C3, C2);
                        fillBrush.Color = BRG;
                        break;
                    case 4://rainbow MRYGCB
                        ColorRgb96Float RGB = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C3, C2, C1);
                        fillBrush.Color = RGB;
                        break;
                    case 5://Primary
                        fillBrush.Color = PC;
                        break;
                    case 6://Secondary
                        fillBrush.Color = SC;
                        break;
                    case 7:// Primary to Secondary
                        float Cf1 = (float)((iDrat * PC.R) + (Drat * SC.R));
                        float Cf2 = (float)((iDrat * PC.G) + (Drat * SC.G));
                        float Cf3 = (float)((iDrat * PC.B) + (Drat * SC.B)); 
                        float Cf4 = (float)((iDrat * PC.A) + (Drat * SC.A));
                        fillBrush.Color = new ColorRgba128Float(Cf1, Cf2, Cf3, Cf4);
                        break;
                     case 8:// Secondary to Primary
                        float Cf1s = (float)((iDrat * SC.R) + (Drat * PC.R));
                        float Cf2s = (float)((iDrat * SC.G) + (Drat * PC.G));
                        float Cf3s = (float)((iDrat * SC.B) + (Drat * PC.B)); 
                        float Cf4s = (float)((iDrat * SC.A) + (Drat * PC.A));
                        fillBrush.Color = new ColorRgba128Float(Cf1s, Cf2s, Cf3s, Cf4s);
                        break;
                    case 9://use source colours
                        ColorBgra32 srcCol = sourceRegion[mcxi, mcyi];
                        ColorRgba128Float sColA= (LinearColor)(SrgbColor)ColorBgr24.FromBgr(srcCol.B, srcCol.G, srcCol.R);
                        sColA.A = (float)(srcCol.A)/255f;
                        fillBrush.Color = sColA;
                        break;
                   }// end line colour switch block -------------------------------------
           
     float iterAng = EstarAng + (N * EiterAng);// used for rotation transform

            switch(Amount16){
                    case 0://ellipses
                          Ellipse E = new Ellipse(mcx,mcy,Wrad,Hrad);//not clamped coords
                          
                          using (deviceContext.UseRotateAtTransform(iterAng, centre)){
                          deviceContext.DrawEllipse(E, lineBrush, lineW, null); 
                          deviceContext.FillEllipse(E, fillBrush);}//end using block
                    break;
                    case 1://triangles
                          Vertices = 3; angle = MathF.PI * (2f/Vertices);
                          break;
                    case 2://rectangles
                          Vertices = 4; angle = MathF.PI * (2f/Vertices);
                          break;
                    case 3://pentagons
                          Vertices = 5; angle = MathF.PI * (2f/Vertices);
                    break;
                    case 4://5 stars
                          Vertices = 5; angle = MathF.PI * (4f/Vertices);
                    break;
                    case 5://hexagon
                          Vertices = 6; angle = MathF.PI * (2f/Vertices);
                    break;
                    case 6://heptagon
                          Vertices = 7; angle = MathF.PI * (2f/Vertices);
                    break;
                    case 7://7 star
                          Vertices = 7; angle = MathF.PI * (4f/Vertices);
                    break;
                    case 8://octagon
                          Vertices = 8; angle = MathF.PI * (2f/Vertices);
                    break;
                    case 9://nonagon
                          Vertices = 9; angle = MathF.PI * (2f/Vertices);
                    break;
                    case 10://9 star
                          Vertices = 9; angle = MathF.PI * (4f/Vertices);
                    break;
                    case 11://decagon
                          Vertices = 10; angle = MathF.PI * (2f/Vertices);
                    break;
                    case 12://hendecagon
                          Vertices = 11; angle = MathF.PI * (2f/Vertices);
                    break;
                    case 13://11 star
                          Vertices = 11; angle = MathF.PI * (4f/Vertices);
                    break;
                }//end shape switch block
 
            //rotate/draw & fill shapes of polygons and stars only
            if(Amount16 > 0){ Point2Float[] verts = new Point2Float[Vertices];//3 is min & arrays start at 0
                             for (int V = 0; V < Vertices; V++)
                             {vert = new Point2Float(centre.X + (MathF.Sin((float)(V +1f) * angle) * Wrad), centre.Y + (MathF.Cos((float)(V + 1f) * angle)* Hrad)); 
                              verts[V] = vert;}
                              using (deviceContext.UseRotateAtTransform(iterAng, centre)){
                              deviceContext.DrawPolygon(verts,lineBrush,lineW,null);
                              deviceContext.FillPolygon(verts,fillBrush);}//end of using block
                              }//end polygon generation loop
     
            
                       
              
            }// end loop
            //should I call myBrush.Dispose() & fillBrush.Dispose() here or not?
            // Intellisense doesn't show that option here but does in simpler, non-looping, OnDraw() effects?
}// end OnDraw

 

 

 

 

 

  • Like 1

 

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

 

PdnForumSig2.jpg

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