Rick Brewster Posted September 2 Share Posted September 2 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 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...
Red ochre Posted September 2 Author Share Posted September 2 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. Apologies again. Just seen your post @Rick Brewster - No problem - get yourself well again! Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
_koh_ Posted September 2 Share Posted September 2 36 minutes ago, Red ochre said: I'm sorry @_koh_, I assumed you were being sarcastic and reacted badly due to past experiences on this forum. No problem. My english isn't that capable, so I wasn't even aware of you were reacting badly😅 Quote Link to comment Share on other sites More sharing options...
Tactilis Posted September 2 Share Posted September 2 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! 1 1 Quote Link to comment Share on other sites More sharing options...
_koh_ Posted September 2 Share Posted September 2 30 minutes ago, Tactilis said: he is referring to https://en.wiktionary.org/wiki/I'll_get_my_coat There's much to get lost in translation! Ah. I just took it as AFK and thought, yeah sounds like he's busy. Quote Link to comment Share on other sites More sharing options...
Rick Brewster Posted September 3 Share Posted September 3 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...) 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...
Rick Brewster Posted September 3 Share Posted September 3 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) 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...
Rick Brewster Posted September 3 Share Posted September 3 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) 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...
Rick Brewster Posted September 3 Share Posted September 3 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 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...
Rick Brewster Posted September 3 Share Posted September 3 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. 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...
Rick Brewster Posted September 3 Share Posted September 3 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. 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...
Rick Brewster Posted September 3 Share Posted September 3 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 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...
_koh_ Posted September 3 Share Posted September 3 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 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 Brush size looks consistent in sRGB. sRGB / sRGB inverse linear / linear inverse 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. 1 Quote Link to comment Share on other sites More sharing options...
Red ochre Posted September 4 Author Share Posted September 4 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. Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
BoltBait Posted September 4 Share Posted September 4 46 minutes ago, Red ochre said: Same source! 🙂 https://boltbait.com/pdn/CodeLab/help/tutorial/drawing/ 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. 2 2 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 September 4 Author Share Posted September 4 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!). Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
Rick Brewster Posted September 4 Share Posted September 4 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. 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...
Red ochre Posted September 14 Author Share Posted September 14 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 Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
_koh_ Posted September 15 Share Posted September 15 I believe this is how to do it. using (deviceContext.UseRotateAtTransform(EiterAng, centre)) deviceContext.DrawEllipse(E, myBrush, lineW, null); edit: Using almost empty separate tab to check definitions may or may not help. 1 Quote Link to comment Share on other sites More sharing options...
Rick Brewster Posted September 15 Share Posted September 15 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. 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...
Red ochre Posted September 15 Author Share Posted September 15 @_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! 3 Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
Red ochre Posted September 18 Author Share Posted September 18 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 1 1 Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
_koh_ Posted September 18 Share Posted September 18 That's beautiful. I think you can just do this in this case. using ISolidColorBrush lineBrush = deviceContext.CreateSolidColorBrush(PC); 1 Quote Link to comment Share on other sites More sharing options...
Rick Brewster Posted September 18 Share Posted September 18 2 hours ago, Red ochre said: I do wonder if I should Dispose() of Brushes though? My advice has been, in the context of PDN plugins, don't worry about it. It will not affect performance enough unless you're creating A LOT of them; at least, not enough to offset the potential anxieties and risks involved in adding this tax to your coding worries. If using them in a loop (for example), you would be better served by just reusing them and re-setting the Color property. 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...
Rick Brewster Posted September 18 Share Posted September 18 Disposing things is more important with classic and Bitmap effects because your rendering method is called once per tile (or horizontal scanline, or whatever). It can happen hundreds of times. In GPU Image and GPU Drawing effects, your OnCreateOutput() or OnDraw() method is called once. So there's less pressure to worry or bother. And even with Classic or Bitmap effects, I wouldn't worry about it unless you're seeing weird performance issues. Like I said, the benefits aren't necessarily outweighed by the anxieties and risks. 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...
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.