Red ochre Posted August 28 Share Posted August 28 Quote I'm happy to help with the conversion btw, just let me know what questions you've got! I'm not having much success trying to convert a simple G.D.I.+ effect to use the new system. I have followed @BoltBait 's tutorial (Many thanks) but still have many basic questions. I will keep these questions in this one thread which may be of use for others learning the new system. Hopefully they have relatively easy answers (well, the first three) and the new system will become clearer to me in time. If there is already a reference of simple code snippets for common uses, please direct me to it. Many thanks in advance. 1. How to get the Primary and Secondary colors? Previously: ColorBgra PC = (ColorBgra)EnvironmentParameters.PrimaryColor; ----------------------- 2. How to get pixel values from the src surface? Previously: ColorBgra srcColor = src.GetBilinearSampleWrapped(Xcoord, Ycoord);//or clamped etc. Do I need to somehow lock and import the src surface? Quote 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. ???? What does that look like as code please? (straight over my head 😀). ------------------------ 3. The correct new way to change the destination background colour or image? Previously I would use something like: dst.CopySurface(src, rect.Location, rect);// quickly copies Src to Dst if (TransparentBackground){ColorBgra klear = ColorBgra.Transparent; dst.Clear(rect, klear);} ----------------------- 4. Can GpuDrawingEffects be added to a BitmapEffect and does this need to done in a separate method? ... say you wanted to mess with the colour of the Src pixel by pixel then 'draw' something on top using Direct2D. If so how is this done. ----------------------- 5. Single-threaded effects; Previously 'OnSetRenderInfo' could be used for single threaded calculations to set 'global' variables, which could then be used in 'Render'. Is this approach still possible and if not how should this be done? ---------------------- 6. From @BoltBait's tutorial I can draw ellipses and fill them with a SolidColorBrush but cannot fathom the values needed in the RadialGradientBrush. Actually a more advanced tutorial on using paths and various types of brushes could be very useful! The MS tutorials seem to assume a much deeper knowledge of C# and C++ than I have and I'm not sure how relevant they are within a Pdn effect. Eg. https://learn.microsoft.com/en-us/windows/win32/direct2d/how-to-create-a-radial-gradient-brush The quotes are Rick's from E.E.R.'s Box Outlining thread. ( I don't even know how to use quotes from different threads in this forum! 🙄) 1 Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
BoltBait Posted August 28 Share Posted August 28 First, be use you are using CodeLab v6.12 and Paint.NET v5.0.13. 1 Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game Link to comment Share on other sites More sharing options...
Red ochre Posted August 28 Author Share Posted August 28 Yes @ReMake set me straight on that! 😄 Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
BoltBait Posted August 28 Share Posted August 28 On 8/28/2024 at 2:35 PM, Red ochre said: I have ... many basic questions. First, there are 3 different types of effects you can write now. Overview Tutorial The one most similar to the old Classic Effect is Bitmap Effect: Bitmap Effect Tutorial Be sure to read this page as well. It includes info about color spaces: Paint.NET Effect Rendering Overview On 8/28/2024 at 2:35 PM, Red ochre said: 2. How to get pixel values from the src surface? Once you follow the Bitmap Effect Tutorial you'll see how this is done. On 8/28/2024 at 2:35 PM, Red ochre said: 3. The correct new way to change the destination background colour or image? This is different depending on which of the 3 types of plugins you're writing. Bitmap // Get your source pixel ColorBgra32 sourcePixel = sourceRegion[x,y]; if (TransparentBackground) sourcePixel.A = 0; // maintain RGB, but make fully transparent GPU Drawing Normally, a GPU Drawing effect would start by drawing the background: protected override unsafe void OnDraw(IDeviceContext deviceContext) { if (!TransparentBackground) deviceContext.DrawImage(Environment.SourceImage); : : Just don't draw the background to start your effect and the resulting image will be transparent to start. GPU Image In order to maintain the background image, you'll need to mix what you've prepared with the background image. Normally, at the end of your effect you might do something like this: : : if (TransparentBackground) return blahEffect; // otherwise mix with the source image MixEffect mixEffect = new MixEffect(deviceContext); mixEffect.Properties.Destination.Set(Environment.SourceImage); mixEffect.Properties.Source.Set(blahEffect); // your previous effect stream mixEffect.Properties.Mode.SetValue(MixMode.CompositeSourceOver); return mixEffect; } On 8/28/2024 at 2:35 PM, Red ochre said: 4. Can GpuDrawingEffects be added to a BitmapEffect and does this need to done in a separate method? ... say you wanted to mess with the colour of the Src pixel by pixel then 'draw' something on top using Direct2D. If so how is this done. There is a way, but this is outside the scope of my current tutorials. Many of my effects do this. It's done using a pixel shader. On 8/28/2024 at 2:35 PM, Red ochre said: 5. Single-threaded effects; Previously 'OnSetRenderInfo' could be used for single threaded calculations to set 'global' variables, which could then be used in 'Render'. Is this approach still possible and if not how should this be done? Sure, this is still possible. On the Build DLL screen, you can use the following options: You can run CodeLab in these modes for development by using the Options screen: On 8/28/2024 at 2:35 PM, Red ochre said: 6. From @BoltBait's tutorial I can draw ellipses and fill them with a SolidColorBrush but cannot fathom the values needed in the RadialGradientBrush. Actually a more advanced tutorial on using paths and various types of brushes could be very useful! Gradient Brush is a little more complicated than a Solid Color Brush... This is for a GPU Drawing effect: // You'll need to create a geometry from whatever you wish to render, for example text would look something like this: IGeometry myGeometry = d2dFactory.CreateGeometryFromTextLayout(... // Then, let's define our graident (this is 2 colors, but you can put more in there if you want) GradientStop[] gradientStops = { new GradientStop(0.0f, gradientFillFromColor), new GradientStop(1.0f, gradientFillToColor) }; IGradientStopCollection spGradientStopCollection = deviceContext.CreateGradientStopCollection( gradientStops, GradientStopGamma.Linear, ExtendMode.Clamp); // Create your brush. You need to specify two points that define the start and end of the gradient: ILinearGradientBrush gradientBrush = deviceContext.CreateLinearGradientBrush(myGeometry.GetBounds().TopLeft, myGeometry.GetBounds().BottomLeft, spGradientStopCollection); // finally, you fill the geometry deviceContext.FillGeometry(myGeometry, gradientBrush); Hope this helps! 😎 1 2 Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game Link to comment Share on other sites More sharing options...
Rick Brewster Posted August 28 Share Posted August 28 The first question I'd ask is, what effect are you trying to convert? Either a link to the effect's forum post, and/or its source code. 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 August 28 Author Share Posted August 28 Thanks both for your replies - I am finding this frustrating but do appreciate your help! I will re-read your tutorials but example code would help. @Rick Brewster The effect I'm tinkering with is called 'Slinky' (in my pack). Originally I was thinking of just changing the range of some of the controls but thought I'd try converting it. https://forums.getpaint.net/topic/28267-slinky-eccentric-ellipses-new-21st-april-2014/ It will need to be compiled in VS to set the range and default for the Vector2Double control. (codelab cannot do this). If I can get it working with the new system I was thinking of adding filled ellipses and possibly experimenting with other shapes. Here is the 10 year old VS code Slinky.cs Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
Solution Rick Brewster Posted August 28 Solution Share Posted August 28 This looks very convertible to a GpuDrawingEffect. Some notes on the conversion: EnvironmentParameters is now just Environment. To get the primary/secondary color, use Environment.PrimaryColor/SecondaryColor. The way this works will be slightly different in 5.1 but it will be the same property (your existing plugin DLL won't break, but you'll need to make changes if you recompile). The OnDraw() method is called once. You won't have to worry about the "roi" or any of that. Your drawing commands will be recorded into something called a command list, and then PDN will do the work to render it into each tile that gets rendered. Worth noting is that a command list is a type of image in Direct2D. Other types of images include bitmaps and effects. So think of an image as an abstraction for something that has a bounding rectangle and which produces pixels within that rectangle. So any time you see a parameter or property of type IDeviceImage, think of terms of bitmap, effect, and command list. You can create your own command lists. I'm not seeing a need for it in this particular effect though. Pens do not exist in Direct2D, but brushes do. A pen is basically just a brush, a stroke width, and a stroke style all in one object. Most of the drawing commands, e.g. DrawRectangle(), will take a brush, stroke width, and stroke style. A stroke style is created with the IDirect2DFactory::CreateStrokeStyle() method. The only thing I see that I don't have in the new system is GetBilinearSampleWrapped(). For now, just access the pixels with the RegionPtr indexer and use % to wrap around. I can provide this method's code and you can copy+paste it, or if it's a popular enough request I can find a way to provide something built-in. You can still access the source layer as a regular CPU-side bitmap. First, use Environment.GetSourceBitmapBgra32() which returns an IEffectInputBitmap<ColorBgra32> Next, call the Lock method to get an IBitmapLock<ColorBgra32> From the lock object, use AsRegionPtr() to get a RegionPtr<ColorBgra32>. This gives faster, simpler access to the bitmap's contents. Like this: using IEffectInputBitmap<ColorBgra32> sourceBitmap = Environment.GetSourceBitmapBgra32(); using IBitmapLock<ColorBgra32> sourceLock = sourceBitmap.Lock(new RectInt32(0, 0, Environment.Document.Size)); RegionPtr<ColorBgra32> sourceRegion = sourceLock.AsRegionPtr(); ... ColorBgra32 color = sourceRegion[x, y]; However, note that if you're using ColorBgra32, the colors will not be linearized by default. So, probably better to use ColorRgba128Float instead of ColorBgra32 in the code above. There are still some growing pains here -- the underlying systems are stable, but our "culture" and our code samples are still migrating. 2 hours ago, Red ochre said: 3. The correct new way to change the destination background colour or image? You can use deviceContext.Clear(). It can be called without a color, which will clear as transparent. Or you can do something like, deviceContext.Clear(LinearColors.Red) or any ColorRgba128Float of your choosing. ColorRgba128Float takes the R, G, B, A as floats that should be in the range [0, 1]. You can also create a ColorBgra32 and there's an implicit cast to ColorRgba128Float. So ColorRgba128Float c = ColorBgra32.FromBgra(128, 128, 255, 255) would auto-convert to [ 0.502, 0.502, 1.0, 1.0 ] Note that in your effect you will be working in linear gamma space. Previously you'd have been in companded (sRGB) gamma space. This becomes very important when pulling colors off of a color wheel control, or when reading the source bitmap. This will make more sense in PDN 5.1, as the color management facilities are fully baked. For now, @BoltBait has a lot of examples he can show where he's converting between ColorBgra32, SrgbColorA, LinearColorA, and ColorRgba128Float. Anyway that's enough for now, see how far you get and then ask more questions ... 1 1 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 August 28 Share Posted August 28 2 hours ago, Red ochre said: 5. Single-threaded effects; Previously 'OnSetRenderInfo' could be used for single threaded calculations to set 'global' variables, which could then be used in 'Render'. Is this approach still possible and if not how should this be done? For this effect I would not worry about this. OnDraw() (GpuDrawingEffect) and OnCreateOutput() (GpuImageEffect) are already called on a single thread, and the rest of the rendering process waits for them. So treat those as your init-and-render method. 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 August 29 Author Share Posted August 29 Thank you @BoltBait , and @Rick Brewster Both very informative! I just rolled a few questions together into one thread. Single-theadedness is hopefully not relevant for 'Slinky', but probably is for other effects. Mixing some form of 'drawing', previously G.D.I.+, with values from the src image is needed for effects like my 'Cuboids' and 'Facet', which also use gradient brushes. So Many thanks @BoltBait for the example code... I do hope there is a geometry for a closed path... for me to discover! Yes, plenty to read, hopefully understand and experiment with but I'm sure to get stuck, so may well be pestering you both again. I do appreciate some of the advantages (and problems) of changing the effect system. Hopefully this thread will be useful to others too. Thanks again! 2 Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
Rick Brewster Posted August 29 Share Posted August 29 17 hours ago, Red ochre said: The MS tutorials seem to assume a much deeper knowledge of C# and C++ than I have and I'm not sure how relevant they are within a Pdn effect. Eg. https://learn.microsoft.com/en-us/windows/win32/direct2d/how-to-create-a-radial-gradient-brush There is (almost) a 1:1 mapping between the native Direct2D types and namings, and those for Paint.NET's wrappers. Any interface starting with ID2D1 will just start with I over on the PDN side, e.g. ID2D1SolidColorBrush --> ISolidColorBrush Similarly, D2D1_STRUCT_NAME would map to just StructName, e.g. D2D1_GRADIENT_STOP --> GradientStop Some of the names are changed to have "device" in them, e.g. ID2D1Brush --> IDeviceBrush ID2D1RenderTarget, ID2D1DeviceContext, and ID2D1DeviceContext1-6 are all contained within IDeviceContext. These had a small cascade on name changes elsewhere, such as D2DERR_RECREATE_TARGET mapping to RecreateDeviceContextException (you won't have to deal with that one). Also, ID2D1BitmapRenderTarget maps to ICompatibleDeviceContext (because there's already an IBitmapDeviceContext, which is using the name in a better way). etc. The point is that while Microsoft's C++ examples are weird to read, it is very translatable/transcribable to PDN effect code once you get used to it. 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...
Tactilis Posted August 29 Share Posted August 29 The only thing in this thread that I can help with is: 9 hours ago, Red ochre said: The quotes are Rick's from E.E.R.'s Box Outlining thread. ( I don't even know how to use quotes from different threads in this forum! 🙄) 1. In the original thread, select the text you want to quote and click the Quote selection popup in the normal way. This will create the quote in the Reply box in that thread. 2. In the Reply box press Ctrl+A then Ctrl+C to select and copy the quote. Then Delete to discard it. 3. Start your new thread and do Ctrl+V to paste the quote. The forum software will paste all the formatting, so that the date-time, the author, and the link arrow to the original thread will be preserved: 4. Repeat for any additional quotes you need to include. -- Aside: Reading @Rick Brewster's and @BoltBait's replies to your questions leaves me in awe of the effort that has been put into the design and development of the paint.net GPU rendering system and the API to utilise it. Amazing work. 1 Quote Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted August 30 Share Posted August 30 I've just posted my learnings thus far in the Box Outlining thread. I'll repeat the code here, because I wrote myself some pertinent notes... Spoiler // 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); } 1 1 Quote ebook: Mastering Paint.NET | resources: Plugin Index | Stereogram Tut | proud supporter of Codelab plugins: EER's Plugin Pack | Planetoid | StickMan | WhichSymbol+ | Dr Scott's Markup Renderer | CSV Filetype | dwarf horde plugins: Plugin Browser | ShapeMaker Link to comment Share on other sites More sharing options...
BoltBait Posted August 30 Share Posted August 30 1 hour ago, Ego Eram Reputo said: I've just posted my learnings thus far Just add "No selection clip" to the options... 3 Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game Link to comment Share on other sites More sharing options...
_koh_ Posted August 30 Share Posted August 30 On 8/29/2024 at 6:35 AM, Red ochre said: 4. Can GpuDrawingEffects be added to a BitmapEffect and does this need to done in a separate method? ... say you wanted to mess with the colour of the Src pixel by pixel then 'draw' something on top using Direct2D. Classic Effect + GDI drawing -> Bitmap Effect + D2D drawing is almost 1 to 1 conversion. Surface -> IBitmap ColorBgra -> ColorBgra32 GDI drawing -> D2D drawing BoltBait's tutorial covers the rest so just GDI -> D2D part. GDI+ surface ??= new Surface(src.Size); using var gfx = new RenderArgs(surface).Graphics; using var pen = new Pen(color, width); surface.Clear(); gfx.SmoothingMode = SmoothingMode.AntiAlias; gfx.DrawCurve(pen, points); D2D bitmap ??= Environment.ImagingFactory.CreateBitmap<ColorPrgba128Float>(Environment.Document.Size); using var bdc = Services.GetService<IDirect2DFactory>().CreateBitmapDeviceContext(bitmap); using var brush = bdc.CreateSolidColorBrush(color); using (bdc.UseBeginDraw()) { bdc.Clear(); bdc.DrawCurve(points, brush, width); } D2D only takes pre-multiplied bitmap so you need to use either ColorPrgba128Float or ColorPbgra32. You still need to blend the result with the src and copy to the dst, but I don't know if there is a simple way to do this when the pixel formats are different. This is a relatively simple thing to do in a GPU effect, but I was doing per pixel blending as a part of the per pixel rendering in my CPU effect. 1 Quote Link to comment Share on other sites More sharing options...
Red ochre Posted August 30 Author Share Posted August 30 @Ego Eram Reputo and @_koh_ thanks both for your input. I'm making good progress converting the code as a GPU drawing effect. I did have problems with the colour change from 'companded' to 'linear' but have found a conversion that seems to give results equal to the original code: //psuedocode ColorBgra oldCol;//was used in G.D.I.+ pens etc ColorRgba128Float newCol;// to be used in D2D drawEllipse byte oB = oldCol.B;byte oG = oldCol.G;byte oR = oldCol.R; float nB = Math.Pow((float)(oB)/255f),2.2); float nG = Math.Pow((float)(oG)/255f),2.2); float nR = Math.Pow((float)(oR)/255f),2.2); newCol = new ColorRgba128Float(nR,nG,nB,1f);//opaque I still have some (presumably) scope problems accessing src pixel values within the loop... but I hope to be able to work those out. I will post my new code here when happy with it but am certainly making better progress than before asking!... thanks all. @_koh_ your explanation and example code are very welcome and I intend to try the Bitmap effect approach later too. Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
_koh_ Posted August 30 Share Posted August 30 36 minutes ago, Red ochre said: I did have problems with the colour change from 'companded' to 'linear' but have found a conversion that seems to give results equal to the original code Never used GpuDrawingEffect but having this method in your class sets the color space original instead of the linear I believe. And yeah, if you want to mimic built-in brush and layer blending, you have to use the original color space. protected override void OnInitializeRenderInfo(IGpuDrawingEffectRenderInfo renderInfo) { renderInfo.ColorContext = GpuEffectColorContext.WorkingSpace; base.OnInitializeRenderInfo(renderInfo); } 1 Quote Link to comment Share on other sites More sharing options...
Rick Brewster Posted August 30 Share Posted August 30 4 hours ago, Red ochre said: float nB = Math.Pow((float)(oB)/255f),2.2); float nG = Math.Pow((float)(oG)/255f),2.2); float nR = Math.Pow((float)(oR)/255f),2.2); Not necessary! Cast your ColorBgra32 to SrgbColorA, then cast to LinearColorA, which then implicitly casts to ColorRgba128Float. You can also use ColorBgr24 which, for instance, implicitly casts to ColorBgra32 with A=255. You can also use SrgbColor (no "A") and LinearColor (no "A") which do not have alpha. And ColorRgb96Float is the non-alpha supporting version of ColorRgba128Float e.g. ColorRgb96Float n = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(oB, oG, oB) You can also use the code @_koh_ mentions to switch the color context to WorkingSpace. However, this does come at the cost of reducing the quality and correctness of blending and sampling. It is highly advantageous to work in linear gamma space, which is why it's the default. There are still some "teething issues" with the whole color management thing. 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 August 31 Author Share Posted August 31 Thanks for that, it makes the code much tidier. Here is a zip with the .dll and codelab.cs (I have'nt built via VS, or added a sample image yet). SlinkyPlus2_04.zip Please let me know if you find any bugs. I'll show the code in hidden code block below too. Many thanks for all the help... I think I have a much better idea of what I'm doing with new system now! Spoiler // Name:SlinkyPlus // Submenu:Iterative lines // Author:John Robbins (Red ochre) // Title:SlinkyPlus 2.04 Sept 2024 Red ochre // Version:2.04 // 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 = 25; // [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 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 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|Colours from source image CheckboxControl Amount12 = true; // Clear background #endregion protected override unsafe void OnDraw(IDeviceContext deviceContext) { deviceContext.DrawImage(Environment.SourceImage); 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(); // Introducing option to clear the background as the above code seems to render the background to dst too. if(Amount12){deviceContext.Clear();} ColorBgra PC = Environment.PrimaryColor;//thanks Rick! ColorRgb96Float Pcol = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(PC.B, PC.G, PC.R);//now in linear colour space ColorBgra SC = Environment.SecondaryColor; ColorRgb96Float Scol = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(SC.B, SC.G, SC.R); 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; int LineNum = Amount1; //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 ISolidColorBrush PriBrush = deviceContext.CreateSolidColorBrush(PC); IStrokeStyle PriStrokeStyle = deviceContext.Factory.CreateStrokeStyle(StrokeStyleProperties.Default); // 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 so now no div by zero worries 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)mcx; int mcyi = (int)mcy;// these used to access sourceRegion so effectively clamped below. if(mcxi < selection.Left){mcxi = selection.Left;} if(mcxi >= selection.Right){mcxi = selection.Right;} if(mcyi < selection.Top){mcyi = selection.Top;} if(mcyi >= selection.Bottom){mcyi = selection.Bottom;} //Colour options double Drat = (double)(N + 1) / (double)(LineNum); //for rainbows double C1d = (Math.Sin(Drat * Pi * 2) * 128) + 128; if (C1d > 255) { C1d = 255; } double C2d = (Math.Sin((Drat * Pi * 2) + (Pi * 0.666666)) * 128) + 128; if (C2d > 255) { C2d = 255; } double C3d = (Math.Sin((Drat * Pi * 2) + (Pi * 1.333333)) * 128) + 128; if (C3d > 255) { C3d = 255; } byte C1 = (byte)(C1d);// calculating as doubles then casting to byte is long winded but more readable (for me). byte C2 = (byte)(C2d); byte C3 = (byte)(C3d); switch (Amount11) { case 0://rainbow YRMBCG ColorRgb96Float RBG = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C3, C2, C1);// Thanks Rick! PriBrush.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, C2, C3); PriBrush.Color = BRG; break; case 2://rainbow MRYGCB ColorRgb96Float RGB = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C2, C3, C1); PriBrush.Color = RGB; break; case 3://Primary PriBrush.Color = Pcol; break; case 4://Secondary PriBrush.Color = Scol; break; case 5://Primary to Secondary double iDrat = 1 - Drat; C1 = (byte)((iDrat * PC.R) + (Drat * SC.R));//use companded colorspace to mix C2 = (byte)((iDrat * PC.G) + (Drat * SC.G)); C3 = (byte)((iDrat * PC.B) + (Drat * SC.B)); PriBrush.Color = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(C3, C2, C1);//Convert to linear for D2D brush break; case 6://use source colours //Must be protected from out of bounds exeptions, see line 150 // perhaps there is 'clamped' option?... but could not find it with intellisense. ColorBgra32 srcCol = sourceRegion[mcxi, mcyi];//clamped to canvas PriBrush.Color = (LinearColor)(SrgbColor)ColorBgr24.FromBgr(srcCol.B, srcCol.G, srcCol.R); break; }// end colour switch block //draw the ellipses Ellipse E = new Ellipse(mcx,mcy,Wrad,Hrad);//not clamped coords deviceContext.DrawEllipse(E,PriBrush,lineW,PriStrokeStyle);//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...
BoltBait Posted August 31 Share Posted August 31 Hmmm... deviceContext.DrawImage(Environment.SourceImage); // This line renders the background to dst So, you could combine, like this: if (!Amount12) { deviceContext.DrawImage(Environment.SourceImage); } So, no need for this: if(Amount12){deviceContext.Clear();} Remember, if you don't draw the source image to the deviceContext, your image will start out transparent. 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 August 31 Author Share Posted August 31 Very good point! Quote Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings Link to comment Share on other sites More sharing options...
_koh_ Posted September 1 Share Posted September 1 On 8/31/2024 at 4:32 AM, Rick Brewster said: this does come at the cost of reducing the quality and correctness of blending and sampling. How brush color interact image color is a human perception thing in my view, so using sRGB brush is my preferable choice so far. One thing I'm sure is manually using linear brush would be a bit cumbersome. Using sRGB brush is kinda like using log scale for convenience. Quote Link to comment Share on other sites More sharing options...
Red ochre Posted September 1 Author Share Posted September 1 That annoying bloke with the ellipses is back again! 😁 How do I rotate an ellipse? Here are the D2D C++ links but I cannot find anything in codelab's intellisense that looks likely. Are these methods available in codelab? (under another name). https://learn.microsoft.com/en-us/windows/win32/learnwin32/applying-transforms-in-direct2d https://learn.microsoft.com/en-us/windows/win32/api/d2d1helper/nf-d2d1helper-matrix3x2f-rotation Ideally I would rotate an ellipse 'E' by a small angle on each iteration to change from a start angle to an end angle? Thanks for your patience. 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 2 hours ago, Red ochre said: How do I rotate an ellipse? You can use deviceContext.Transform for this. But this transforms whole image so setting the center of the ellipse (0,0) and moving the ellipse through the transform matrix might be simpler. Also you can use Math.Min(), Math.Max(), Math.Clamp() for if (x > a) x = a kind of things. Quote Link to comment Share on other sites More sharing options...
Red ochre Posted September 2 Author Share Posted September 2 18 minutes ago, _koh_ said: You can use deviceContext.Transform for this. No, I can't?... I don't see that option?... that's why I asked. 18 minutes ago, _koh_ said: Also you can use Math.Min(), Math.Max(), Math.Clamp() for if (x > a) x = a kind of things. Yes, I could write the formula for a Bitmap ellipse but I'm trying to use D2D? https://forums.getpaint.net/topic/23194-squirkle/ I'll get my coat.😕 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 7 minutes ago, Red ochre said: No, I can't?... I don't see that option?... that's why I asked. Umm. I have that property in my CodeLab. 8 minutes ago, Red ochre said: Yes, I could write the formula for a Bitmap ellipse but I'm trying to use D2D? 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); 1 Quote 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.