Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


AnneYusual last won the day on October 14 2023

AnneYusual had the most liked content!

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

AnneYusual's Achievements


Apprentice (3/14)

  • Collaborator
  • Reacting Well Rare
  • First Post
  • Addicted Rare
  • Week One Done

Recent Badges



  1. Sorry for the extended absence, all: after finally publishing the plugin I returned the Windows laptop I was borrowing for this, and got down to wrangling a workable linux install onto my old one - so not much uptime in the past week. To answer the burning question on several people's lips: This will happen if you have any options set to random on the "Random" tab (besides color - that one only changes the colors). It's both intended behavior and not: any time ANY of the controls change, the entire ornament is re-generated, and, if any of the parameters are random, then new random values will be picked. I can't change this behavior so long as I stick with using the standard IndirectUI interface - and given how MANY controls there are, it's by far the better option than trying to cobble together standard Winforms controls that come with VisualStudio - that's basically what I did on the standalone app - which DOES NOT generate anything until you've finished playing with all the controls and hit the "Preview" button. I kinda feel like there are upsides and downsides to both ways of doing it - and I may change the standalone app to do a little of both in the future. As far as the plugin is concerned, there's not much I can do there, sorry. As for "flipping all over the screen" - I know what you mean, but it's not QUITE what's happening, which is also why it would be hard to "fix" - actually, the "origin" curl is always centered in the rectangle of the canvas, but from there, the direction of "growth" is either very random (if the options are random), or follows specific growth rules inherent in the ornament generation. I CAN change the rules, but not for one specific option: the growth algorithm is global, across all option permutations. But ultimately, that means new spirals will only be added to existing ones in specific order and in specific places. If you want to get the hang of how this thing behaves, and DON'T feel like reading through all the code (which is pretty boring and not necessarily obvious anyway), you can try decreasing the number of nodes and de-selecting all the random options. In the standalone app, there are even debug options that generate a log of what's going on AND display the order of curl growth for you (these are cleverly hidden in plain sight as two small/low contrast links next to the output window). Actually, that was the FIRST thing I wanted to do - well, actually, I was hoping to go with Fibonacci spirals originally, but logarithmic ones are pretty too, more so than Archimedes anyway, which is what I'm using. As far as trying to adapt my algorithm to it, I doubt that would work... Here's why: The reason this ornament looks pretty is that the spirals are all packed very closely together -- spread them farther apart, and it quickly loses visual appeal. However, geometrically, spirals are complicated beasts -- they aren't even symmetrical. So packing them closely in a hands-off, algorithmic manner, is much more complicated than I expected. Actually, even packing circles is a more complex task than I first thought, when you consider the geometry of the process. The only reason I was able to get this thing to work was to find an Archimedes spiral variation that approximates a circle VERY closely, then create an algorithm that grows a tree of circles that are packed closely in some directions and NOT others, and avoid collisions with each other and the edges of the container. I further adjusted the collision DETECTION based on the specific features of the spirals (their outer envelope, if you will), but the collision RESOLUTION mechanism is based entirely around the idea of fitting CIRCLES (which, given how much I apparently suck at geometry, was already pretty hard to get right -- and "right" in this case is measured by whether the result still looks good). But that's essentially the problem with using any less "circular" species of spirals -- I'm not saying it can't be done, mind you, just saying I have no idea how to go about it, and I don't think my algorithm would work. You are certainly welcome to tweak it to your heart's content and find out. I don't think it's anything mathematically revolutional anyway, so no worries about hijacking some discovery. I'm not familiar with that one, which is why I haven't considered it. Maybe I should add "yet" to it -- I might give it a try at some later point. I certainly feel like the order of growth (which is a BFS tree, if you were wondering) leaves much to be desired, but when I played with variations (during early stages of development, anyway), they didn't look any better, so I dropped the idea. I'm definitely not going to get to it soon though -- other, more pressing things on my plate. Again, this is open source, you are welcome to hack it to bits -- I certainly won't be offended! Also on the backburner, but I feel like it's "unfinished business", so it might get done sooner: I developed most of the "growth" logic for this in javascript (p5.js to be precise, if you want to check it out - fun stuff). I kinda feel like it's a lot more rapid-prototyping friendly. So it was my original intention to translate the thing back into javascript when it was finished -- but it's gotten a great deal more complex as I worked on it, so the translation no longer seems like such a trivial matter. The reason I bring it up though -- way easier to screw around with this in p5. If you really care, I can give you my "last working prototype" to play with, with the warning that it's necessarily clean or pretty... But it IS way less annoying in javascript than C#.
  2. From wikipedia, under Scroll (art): "The scroll in art is an element of ornament and graphic design featuring spirals and rolling incomplete circle motifs, some of which resemble the edge-on view of a book or document in scroll form, though many types are plant-scrolls, which loosely represent plant forms such as vines, with leaves or flowers attached. Scrollwork is a term for some forms of decoration dominated by spiralling scrolls, today used in popular language for two-dimensional decorative flourishes and arabesques of all kinds, especially those with circular or spiralling shapes." This plugin will generate a variety of scroll ornament patterns any time you feel a craving for some sprawling spirals coming on. ScrollGenerator.zip contains the ScrollGenerator.DLL and (CodeLab-generated) Install_ScrollGenerator.bat that will install the plugin for you. After installation, look for Effects > Render > Scroll Ornament If you are interested, the code for the plugin can be found at: https://github.com/annayudovin/PDN-ScrollGeneratorPlugin Here are some screenshots: ScrollGenerator.zip
  3. As the title says, I can't really tell if it's a bug or feature of IndirectUI, so I'm posting it here rather than under Bug Reports. The ColorWheelControl (possibly other controls as well, but I wasn't testing them for this behavior) seems to be extremely limited in accepting programmatic feed-forward state control, (as opposed to feed-back of user selections). In case that's too impenetrable, I'll try to explain what I mean. While I was working out the best way to categorize all of my plugin's controls into different tabs (so that the UI looks less overwhelming AND fits on a screen w/o scrolling) I thought of adding a checkbox to randomly assign colors other than pre-selected primary/secondary (or, black and white, by default) to the ornament and the background. Inevitably, while flipping through these random combinations, some looked better than others, making me want to tweak either value or saturation to "fix" the look. However, I quickly discovered this was actually impossible to achieve: so long as the "random" color option is selected, you can only accept or reject the generated combination, while deselecting the "random" option resets the preview colors back to the default values. At first, I thought this was fixable: a matter of setting the "internal" value of ColorWheel controls every time a new color pair is generated. But it turned out that the controls ignore any and all attempts to change their value that don't come from a user click. Actually, I couldn't even find a way to display the numeric RGB or HSV values of the randomly generated combinations, so they could be manually entered/selected in the ColorWheelControls (and adjusted from there). This may well be the intended behavior - I really can't tell, but since it disallows something a user might reasonably want, I thought I ought to bring it up. For all I know, I could just be missing something. Here's what I tried to get the controls to display another value: in OnSetRenderInfo(): scrollColor = ColorBgra.FromBgra((byte)colorPair[0][2], (byte)colorPair[0][1], (byte)colorPair[0][0], 255); backgroundColor = ColorBgra.FromBgra((byte)colorPair[1][2], (byte)colorPair[1][1], (byte)colorPair[1][0], 255); ColorBgra _scrollColor = ColorBgra.FromBgra((byte)colorPair[0][2], (byte)colorPair[0][1], (byte)colorPair[0][0], 255); ColorBgra _backgroundColor = ColorBgra.FromBgra((byte)colorPair[1][2], (byte)colorPair[1][1], (byte)colorPair[1][0], 255); token.SetPropertyValue(PropertyNames.ScrollColor, ColorBgra.ToOpaqueInt32(_scrollColor)); token.SetPropertyValue(PropertyNames.BackgroundColor, ColorBgra.ToOpaqueInt32(_backgroundColor)); //the following two lines are just to double check that the value ColorWheelControls really changed - they did, and showed up in preview //but were re-set by debugger-inaccessible code before the next call to OnSetRenderInfo scrollColor = ColorBgra.FromUInt32(unchecked((uint)token.GetProperty<Int32Property>(PropertyNames.ScrollColor).Value)).NewAlpha(255); backgroundColor = ColorBgra.FromUInt32(unchecked((uint)token.GetProperty<Int32Property>(PropertyNames.BackgroundColor).Value)).NewAlpha(255); in OnCreatePropertyCollection(): //works as intended according to the debugger: i.e. control values change //but the values are not reflected by the visible state of the controls themselves propRules.Add(new SetTargetWhenSourceEqualsAnyValueRule(PropertyNames.ScrollColor, unchecked((int)scrollColor.Bgra), PropertyNames.ChkRandColor, true)); propRules.Add(new SetTargetWhenSourceEqualsAnyValueRule(PropertyNames.BackgroundColor, unchecked((int)backgroundColor.Bgra), PropertyNames.ChkRandColor, true)); Anyway, it's hardly vital, but a nice-to-have I was kind of disappointed not to be able to provide. The rest is done and will be published shortly.
  4. In principle, sounds fine to me. In practice? I've never done graphics programming. Up until fairly recently I've been exclusively into data analysis of one sort or another, so my understanding of how it's done is rudimentary at best. The closest I've come to graphics of any sort is dipping my toes into generative art, using p5.js - but, so far, have stayed on the abstract side of things, basically exploring mathematical objects. Even the plugin I'm working on: 90% of the code is abstract rules for how an object should behave. When it comes down to actually producing something visible, it'll generate a list of points which MUST be given to something that can manage to plot them on a surface and join them with curves, because I wouldn't know how to, myself. The Drawing2D.Graphics class was simply the object I was able to find that would do the point plotting for me - I googled it. If you tell me I should use another object instead (and there are docs I can read about how to use it), that's cool, I'll use that one. But, I suspect, if I were to try something that involves actual pixel manipulation, the learning curve would look a lot more like a learning cliff (when you say GDI and GDI+ I have no clue what you are referring to, I'd have to look it up and figure it out before so much as having an opinion). Since graphics programming has never been a personal ambition of mine (possibly because Linear Algebra, which I had to take in college, was a scarring experience), I don't see scaling much of said cliff - unless, for some reason, someone decided to hire me as a graphics dev - possibly not the best plan. I'm not unwilling to learn, it's just that the current 5-year plan involves a lot of statistics and data science. This project is most likely a one-off.
  5. I managed to miss it - and, I promise, not for lack of trying. It got buried pretty deep since 2007. I don't know if your "admin" powers extend to "pinning" posts, but I think it's really in need of being pinned. Better yet - the four-line version - less chance of the next clueless novice plugin writer like myself missing the most important part: DON'T expect it to do the same thing when run in VS as when run in CodeLab, and here's what to do to make it work.
  6. Well, I finally got it to draw a spiral! Unbelievable. I think this ought to be a tutorial, possibly the shortest ever. Of course, it must be preceded by the most important bit of information: What To Expect When You Are Expecting... Your Plugin To Execute In VisualStudio For The First Time: absolutely nothing is going to happen except for Paint.NET being started up for you. Enlightenment hurts: not only did I not have a clue, but there was no way I was actually going to arrive at it, without being told. And after wasting a lot time and effort trying to solve a non-existent problem I got a bunch of people to do more of the same on my behalf. On that note, SORRY and THANK YOU!!! to everyone who chimed in to help! 👍
  7. I don't believe so... Just checked my downloads, seems I have a pdn-project-templates-for-vs-master.zip and a PdnV5EffectSamples-main.zip. Is it the pdn-project-templates-for-vs-master? Was I confusing one for the other? It seems the biggest problem at this point was that I was expecting something to happen that wasn't *going to* happen, and when it didn't - I assumed something wasn't working right. No wonder nothing I tried worked - it tends not to, when one is barking up the wrong tree. 🫣 🤓
  8. Ok, now we are getting somewhere. So, I don't actually know what I should be expecting - I've never written a plugin before. I tried creating a sample one in CodeLab - and when you click "run" in CodeLab, it shows the plugin "activated" with the user interface. Conversely, when I code something in VS, and hit "run/debug", it's going to run my code, whether as an app or in a console. I can also set breakpoints in my code and the execution will get to that point and pause there. It seemed reasonable that when attempting to "run" a plugin in VS, it might have to activate Paint.NET as the "parent" app, but then would actually, you know, RUN MY CODE. Apparently an incorrect assumption on my part... So, what exactly do I need to do to run my code? Do some/all of the build files go into Paint.NET's regular plugins directory, the same way as "production" plugins and then I activate them from the Paint.NET instance that VS triggers? If I set breakpoints, will they be reached that way?
  9. Derp, my bad, I misinterpreted the screenshot (that has a trackbar on it) in combination with the words " It will show you how to set up your csproj" - and thought you'd made a video tutorial to go with the project templates - which I *didn't* watch (good thing too - since it doesn't actually exist). In any case, I did get the templates and used them. Actually I just double checked, because I was starting to think I was seeing things, but no, I'm not, and I have the screenshots to prove it! So, as I mentioned, I've created a bunch of test solutions with sample code just to get anything to run in VS and show up in Paint.NET. I've also deleted a bunch of them, because they were multiplying. But two of them are still sitting around in their respective directories. I know one was generated through CodeLab, and the other by using the project templates, but unfortunately, I can't remember which is which. I just opened each one in VS, and looked at the solution properties. Here are the screenshots: Apparently, ONE of them can see .NET 7.0, but not the other one... Btw, I did download/install .NET 7 by selecting that "install other frameworks" option, which sends you to a website to download other versions. It was an .exe, it ran and installed itself with no incident, but STILL wouldn't show up in that dropdown. I updated VS after that, hoping that would make it show up, but no, it didn't. Apparently it still doesn't show up for THAT solution, but it's available on another, and I actually have no idea what the difference is (or why the solution properties interface looks slightly different on them).
  10. Ugh, sorry, no joy - I repeated the steps, and all went well, except that when I clicked run/debug Paint.NET showed up with a new document but no plugin "activated". Am I supposed to open it in CodeLab from there? Is that the missing action? Or was running VS as an admin supposed to make it come up? I mean, on the bright side, everything compiles - but I can't get a plugin to pop up the same way as it would pop up a test one in CodeLab when you hit run - that shows the user interface (and if there's anything functional, it actually makes a preview in the open document).
  11. I didn't watch the video -- didn't realize there was a video. I did download the templates from github and used them to create a project. I even suspected that the error was about .NET 7 - so I downloaded and installed that and updated VS. However, when I tried to change the target framework, .NET 7 was still not showing up. I even tried to sneak it in by removing all the System references and referencing the relevant .NET DLLs from Paint.NET installation directory, but VS wasn't having it. I'll give it another shot in a minute, maybe it's in a better mood today. I'm about to try out AndrewDavid's recipe - but I suspect it's only going to work if VS decides to *see* the .NET 7 framework that I've already installed. So, fingers crossed!
  12. Sorry, just realized how godawful it looked and edited 🫢
  13. Hi AndrewDavid! Thank you for taking a look! You are right, I'm not very familiar with .NET - in fact, this was partly an exercise in *getting* familiar with it. Yes, I did choose a form based interface for my app -- I did try to guess what Paint.NET was using, but since I'm NOT familiar with all the options the various versions of the framework offer, it was kinda pointless. In any case however, the one part of my app that really doesn't need to migrate IS the interface: it sucks, it's clunky (the "sliders" that CodeLab produces for your plugin are 10X better than the poor imitations of the same functionality I cobbled together). On the bright side, like 90% of the code in my MainForm.cs is about making all the controls work together and update the options that make the SpiralTree generate a particular design. It mainly exists for my testing convenience, and can be ditched without any regrets. The classes that DO need to be imported into Paint.NET are all the rest: SpiralTree, SpiralNode, Trig, Polygon and Configs. Once SpiralTree (the big boss) gets "grown", it will helpfully plot itself into a bunch of points and return those from scrollTree.PlotSpiralTreePoints(). From there, the points get handed over to the Graphics class which will draw them on a surface and join them with curves (good thing, too, because I have no idea how to do that). If I recall correctly, Graphics lives in using System.Drawing.Drawing2D, and I KNOW I've seen Paint.NET referencing that, so that part should NOT be a problem. All I really need to do is get SpiralTree to introduce itself to Paint.NET and it ought to take care of itself from there. Unfortunately, I can't figure out a way to do that from CodeLab - if I had, this would be a plugin already. It's also in charge of logging itself (which I thought was funny, for a tree, but, in all seriousness, that bit is disabled by default, and won't be interfering with anything Paint does) and even saving (those same points) as an .svg. The latter wasn't really meant to go into Paint.NET - I was just temporarily frustrated with trying to get Graphics to give me a reference to the image it was plotting everything into, so I could put it on the clipboard. While I was cursing at it, it occurred to me to see what the vector format was all about, and I was surprised to learn that it was all XML, and that the same set of points I was handing over to Graphics can be, with a little formatting, saved into a conveniently resizable format. Oh, btw, before I forget to explain: when you click the .SVG button, nothing actually "happens" from your perspective. If, however, ScrollGenApp can locate your "Documents" directory and get write access to it, it will create a "ScrollGenerator" directory, and save the current design in there as scroll-date-and-time.svg. You may want to check - if you liked that scroll, and nothing unexpected happened, it's probably sitting there. Actually, I did (on my first try, before I lost hope and started trying to get anything at all to show up in Paint) produce a "sketch" of what the plugin interface would have to do to make the tree hand over its points and plot them, and, in a separate try, took a crack at making controls for the relevant tree parameters from those on offer from CodeLab. The controls ended up being this set, which, I think ought to do quite nicely: trkMaxNodes = token.GetProperty<Int32Property>(PropertyNames.TrkMaxNodes).Value; chkRandNum = token.GetProperty<BooleanProperty>(PropertyNames.ChkRandNum).Value; drpLeafNum = (byte)(int)token.GetProperty<StaticListChoiceProperty>(PropertyNames.DrpLeafNum).Value; chkGrad = token.GetProperty<BooleanProperty>(PropertyNames.ChkGrad).Value; optLeafSize = (byte)(int)token.GetProperty<StaticListChoiceProperty>(PropertyNames.OptLeafSize).Value; optRandLrgSz = (byte)(int)token.GetProperty<StaticListChoiceProperty>(PropertyNames.OptRandLrgSz).Value; optStyle = (byte)(int)token.GetProperty<StaticListChoiceProperty>(PropertyNames.OptStyle).Value; chkTwin = token.GetProperty<BooleanProperty>(PropertyNames.ChkTwin).Value; chkRandAng = token.GetProperty<BooleanProperty>(PropertyNames.ChkRandAng).Value; originAngle = token.GetProperty<DoubleProperty>(PropertyNames.OriginAngle).Value; chkResprout = token.GetProperty<BooleanProperty>(PropertyNames.ChkResprout).Value; resproutAngle = token.GetProperty<DoubleProperty>(PropertyNames.ResproutAngle).Value; trkShiftFactor = token.GetProperty<Int32Property>(PropertyNames.TrkShiftFactor).Value; trkSpreadFactor = token.GetProperty<Int32Property>(PropertyNames.TrkSpreadFactor).Value; scrollColor = ColorBgra.FromUInt32(unchecked((uint)token.GetProperty<Int32Property>(PropertyNames.ScrollColor).Value)); backgroundColor = ColorBgra.FromUInt32(unchecked((uint)token.GetProperty<Int32Property>(PropertyNames.BackgroundColor).Value)); btnPreview = (byte)token.GetProperty<Int32Property>(PropertyNames.BtnPreview).Value; randomSeed = btnPreview; Aside from those, just about the only code that would need to go into the plugin itself is this: Surface aux = null; private SpiralTree scrollTree; private List<PointF[]> cachedTreePoints = new(); private Polygon Boundry; private Color drawColor = Color.Black; private Color bkgColor = Color.White; private Graphics plotter; protected override void OnDispose(bool disposing) { if (disposing) { // Release any surfaces or effects you've created aux?.Dispose(); aux = null; } base.OnDispose(disposing); } // This single-threaded function is called after the UI changes and before the Render function is called // The purpose is to prepare anything you'll need in the Render function void PreRender(Surface dst, Surface src) { if (aux == null) { aux = new Surface(src.Size); } plotter = Graphics.FromImage(aux.CreateAliasedBitmap()); CreateTree(); } private void CreateTree() { float ht = aux.Size.Height; float wd = aux.Size.Width; float[][] coordArry = new float[4][] { new float[2] { 0f, 0f }, new float[2] { 0f, ht }, new float[2] { wd, ht }, new float[2] { wd, 0f } }; Boundry = new Polygon(coordArry, true); scrollTree = new SpiralTree(Boundry); //set Configs values from interface Configs.maxNodes = trkMaxNodes; Configs.maxLeaves = drpLeafNum; Configs.RANDNUM = chkRandNum; Configs.GRAD = chkGrad; Configs.SMTOLG = optLeafSize != 0; Configs.DIVOPTION = optStyle + 1; Configs.RANDLRG = (optRandLrgSz == 1 || optRandLrgSz == 2) ? true : false; Configs.RANDSZ = (optRandLrgSz == 2) ? true : false; Configs.TWIN = chkTwin; Configs.RANDANG = chkRandAng; Configs.rootAngle = (float)(originAngle * Math.PI) / 180f; Configs.GROWTINY = chkResprout; Configs.sproutAdjustment = (float)(resproutAngle * Math.PI) / 180f; Configs.spreadFactor = trkSpreadFactor * Configs.nodeHalo / 2f; Configs.shiftFactor = trkShiftFactor / 100f; scrollTree.Grow(); cachedTreePoints = scrollTree.PlotSpiralTreePoints(); } // Here is the main multi-threaded render function // The dst canvas is broken up into rectangles and // your job is to write to each pixel of that rectangle void Render(Surface dst, Surface src, Rectangle rect) { drawColor = EnvironmentParameters.PrimaryColor; bkgColor = EnvironmentParameters.SecondaryColor; plotter.Clear(bkgColor); float penWidth = 1.7f; //int BrushWidth = (int)EnvironmentParameters.BrushWidth; Pen pen = new(drawColor, penWidth); foreach (PointF[] _pArry in cachedTreePoints) { plotter.DrawCurve(pen, _pArry); } // Step through each row of the current rectangle for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; // Step through each pixel on the current row of the rectangle for (int x = rect.Left; x < rect.Right; x++) { dst[x,y] = aux[x, y]; } } } Again, I couldn't get anything to RUN, so I can't tell if it actually does the job, but if it doesn't, it ought to, with some minor modifications. The only hitch to making THAT work in code lab is the "using BFSSpiralTree;" directive, which immediately stops anything from compiling. I'm hardly a C# expert, so I don't know of any other way of making CodeLab aware of the existence of my classes. I tried sneaking them in as partial classes - but it balked as soon as I tried adding a method call, because that wasn't in its context. So, back to square one. If you know of a way to get it to work without a "using" directive, please enlighten me - I spent several hours reading C# docs yesterday in search of some alternative, and, although did I learn a few interesting things I didn't know before, I'm no closer to an answer.
  14. Well, I'm happy to see other scroll fans out there! I've been wanting one of these for ages, and finally got around to just making one. But seriously, if you like the images, go download the app at https://github.com/annayudovin/ScrollPatternGen/releases - I promise, despite the clunky interface it will cause you no pain whatsoever, either installing it or running it. The colors of both the ornament and the background can be changed, and, as a free bonus (just kidding, it's all free), you get to save your favorites to a .svg (vector image), not just copy them to clipboard. Even if I never get it properly "plugged in", you could still have a lot of scroll ornaments for your drawing/decorating pleasure. Here's a screenshot of the standalone:
  • Create New...