妖怪世捨て人The ramblings of a recluse.2015-06-11T22:02:19http://reader.tymoon.eu/Copyright (c) 2014, TymoonNET/NexT
radiance-reader
The Great UI Warts - Confession 563222015-06-11T22:02:192015-06-11T22:02:19shinmerahttps://shinmera.com/shinmera@tymoon.eu<p><img src="https://filebox.tymoon.eu/file/TmpNMw==" alt="header"><br>
It's now been <a href="https://github.com/Shinmera/parasol/commit/e395ab3b8ce749e4b64ad8eacccff42c613d8de8">about a year</a> since I first started work on Parasol. In the process, I had to learn about UI programming in Common Lisp. It pains me a lot to say this, but it is definitely not one of the great strengths of CL. It certainly wasn't back then, and it still isn't now. Since Parasol started I learned a lot about Qt and in particular the Common Lisp bindings, CommonQt. While using Qt is your best bet at writing a native GUI, it just isn't as pleasant as writing other lisp code. Too many things can break, too many brick walls are laying in wait for you to hit your head against, too many things are simply not there infrastructure wise. However, as Parasol grew, and I grew tired of CommonQt's shortcomings, I started to write more and more systems to work around these problems and make the UI experience for the developer a better one. This is the goal of <a href="https://shinmera.github.io/qtools">Qtools</a>.</p>
<p>This library started out as an innocent encapsulation of a few things I'd developed in tandem with Parasol. The first serious issue I had with Parasol was <em>memory leaking</em>. Since we‘re accessing Qt –a C++ library– we need to go back to the old times and deal with our memory by hand. This is a very arduous task and one prone to mistakes. So, a system was developed to alleviate this pain. The result of this is Qtools’ finalizers. At the core of it is a generic function that takes care of cleaning up the object it is passed. So in other words, a destructor function. Using this I could ensure that foreign objects were always properly cleaned up. However, I quickly came to realise that I did one very similar thing all the time: Add a finalizer method for my widget, and call finalize on its slot values. Thanks to the Meta Object Protocol's capabilities, I was able to hide this away completely. Now there's almost never a need to write a finalizer method again. It suffices to just add <code>:finalized T</code> to a widget's slot, and in the case of sub-widgets, the system already does it automatically.</p>
<p>The next issue I had was that writing in CommonQt's style is really uncomfortable. You need to duplicate a lot of information and keep track of the slots, signals, and overrides you define in the class definition. You also need to take care of different type and naming styles that come from C++ and leak into your CL application. This spawned Qtools' widget system. Not only does it take care of mapping naming styles and types, but it also allows a much more normal-looking way of defining your widgets. Instead of having to stuff information into your class definition, you can use multiple, separated forms. Just the way it works in your usual Lisp programs. At the heart of this system lies <code>reinitialize-instance</code>. Thanks to this fantastic function (and the MOP), I was able to separate everything out. What happens in the back when you compile a separate form is that it appends the option that should be in the class definition onto a class property, and calls <code>reinitialize-instance</code>. This call subsequently computes the effective class options and injects them into the class re/initialisation, effectively making it appear as if you had indeed added an option onto the class definition itself.</p>
<p>With this step done, much of the awkwardness was gone. Programs looked much more naturally structured, and things could be specified in a way that felt intuitive. However, one stain remained in the picture: Qt method calls. In order to call Qt methods, CommonQt provides a reader macro: <code>#_</code>. Sadly, in order for this reader macro to work, you need to specify the method name as it is in Qt, including the proper capitalisation. Since it is a foreign call, you also can't inspect it, or get any documentation information out of it. Argument list validity also isn't checked. Getting rid of this and allowing some form of normal-looking function call instead was a rather tricky problem to solve. My first thought was to dynamically analyse the available methods and generate Lisp wrapper functions for them. Those wrapper definitions are dumped to file, and then loaded. Sadly, doing so results in a couple hundred thousand method wrappers and a roughly 50Mb FASL file (on SBCL). The initial compile time also suffered because of this of course. This seemed like a less than stellar solution to me, mostly because the overwhelming majority of the wrappers included would never be called by the GUI anyway. So I sought a different solution. I did find one, albeit it is rather mad.</p>
<p>This solution is called Q+. The first part of it is the aforementioned wrapper compiler that I previously used to generate static wrappers. Modifying it a bit, I could use the same system to generate individual wrappers for any specific method I wanted. The second part is detecting when a supposed call to a wrapper function is made. Since it is not precompiled, the CL host cannot know of it. Thus, we need to somehow intercept when such a form is compiled, dynamically compile the wrapper, and then replace it with a call to the actual wrapper function. That sounds like a macro! And indeed, the <code>q+</code> macro does that. It takes a method name and an argument list, dynamically compiles the wrapper, and finally emits a call to the new wrapper. The truth is a bit trickier here, since the wrapper needs to be available when a file is merely <code>load</code>ed as well, which wouldn't be the case if it was only generated during macro expansion. So instead, a <code>load-time-value</code> form that generates the wrapper is emitted alongside the wrapper call. That way, methods are always around as needed, with no run-time overhead. The last trick to Q+ is the hiding of the <code>q+</code> macro call. Using the <code>q+</code> macro solved most of the problems, but it was essentially the same thing as the <code>#_</code> reader macro, with a bit nicer method name handling. What I wanted instead was to be able to write the actual wrapper function names. That would also allow slime to show docstrings, arguments, and similar information. In order to make this last trick work, I had to hack into the reader.</p>
<p>One of the greater blemishes of the the Common Lisp standard is the inability to hook into the reader's symbol creation process. This exclusion from the standard makes it impossible to write such things as package local nicknames as a library, or make a case like mine easy. What I had to do instead was to override the <code>(</code> reader macro. Q+ then reads ahead, to see whether you're trying to reference a symbol from the <code>q+</code> package. If so, it reads the rest of the form, and emits a call to the <code>q+</code> macro from above instead. It not, it delegates to the standard reader macro for <code>(</code>. Overriding this reader macro is a dirty trick, and I'd rather not have done it. However, there simply is no other way to accomplish this feat, short of writing a complete reader implementation and demanding that people use that instead of the host implementation's, which is a bit too much to ask for, in my opinion. Still, it works fine, and I haven't run into any obvious issues so far. Now Qtools applications look and read like regular lisp code.</p>
<p>However, how the code looks is only one of the aspects that influence writing GUIs. There's a lot more to it, like for example the initial installation and the binary deployment. Those two things are what I've worked on in the past few weeks now. Out of the first item grew <a href="http://shinmera.github.io/qt-libs/">qt-libs</a>, which should ensure that the required libraries like smoke and CommonQt are available easily. This currently works fine for Linux, however I did not get enough time before the Quicklisp release to find testers for Mac OS X. Windows is another problem entirely, one that I can only solve through downloading of precompiled libraries. I've wasted the entire day today with <a href="http://plaster.tymoon.eu/view/AM">trying</a> to get 64bit versions of the smoke libraries compiled on Windows. Hopefully I can push through with that and allow easy setup of a Qt environment on Windows as well. Qt-libs builds fine on Mac OS X now as well, though there's currently an issue remaining in loading the libraries. I'll get that sorted out before the next Quicklisp release though.</p>
<p>The second part grew into Qtools' new <a href="https://github.com/Shinmera/qtools/blob/master/deploy.lisp">deployment system</a> part. This allows really convenient and easy generation of ready-to-ship binaries of your application. The only thing you have to do is update your system definition a little:</p>
<pre><code>(asdf:defsystem :my-system
...
:defsystem-depends-on (:qtools)
:build-operation "qt-program-op"
:build-pathname "binary-name"
:entry-point "my-package:start-function-or-main-class")
</code></pre>
<p>Once these four lines are added, you can simply launch your implementation from a shell, invoke <code>(asdf:operate :program-op :my-system)</code> and it'll do all the magic –like closing foreign libraries before dump, restoring the proper library search paths after resume, reloading the foreign libraries again using the new paths, etc.– for you. All you'll get is a <code>bin</code> folder in your project folder that you can zip and ship. I've tried this for <a href="https://github.com/Shinmera/halftone/releases/tag/1.0.1">Halftone</a> and it Just Works™ on Linux so far.</p>
<p>But, the road ahead is still long and twisted. Once deployment and installation work flawlessly, there's still a lot of code left to be written to make working with Qt itself less painful. Hopefully some day I'll be able to say that writing native GUIs in Lisp is actually a nice experience!</p>
<p>The <a href="https://shinmera.github.io/qtools">Qtools documentation</a> is long and extensive. It contains a lot of talk on both how to start using Qtools, as well as what the internals are and how they work. If you're interested, have a read.</p>
<p><img src="https://filebox.tymoon.eu/file/TmpNNA==" alt="footer"></p>
Optimised Traversal - Confession 473092014-12-31T16:48:272014-12-31T16:48:27shinmerahttps://shinmera.com/shinmera@tymoon.eu<p><img src="http://filebox.tymoon.eu/file/TWpBMw==" alt="header">
Usually when we talk about optimisation of data structures the concern for optimisation lies within the operations of adding, updating, deleting, and/or retrieving. I've never really heard of any algorithms or data structures that try to optimise traversal. After all, if you have <code>n</code> elements, how can traversal be anything but <code>O(n)</code>?</p>
<p>Well, in the case of Parasol I need something that speeds up traversal. Let me explain why: in order to support a history, the paint operations you perform each create a new drawing layer of their own. This is absolutely necessary because there is no efficient means of calculating a delta between two steps and I can't just apply the operation directly onto the canvas as the operation is done, as potential effects such as brush composition mode have to apply to the entire stroke, not separately to each ellipse that constitutes a stroke.</p>
<p><img src="http://filebox.tymoon.eu/file/TVRnMw==" alt="comparison-stroke-point"></p>
<p>In the above the comparison is made using <code>0.5</code> opacity rather than a composition mode, but you get the idea. So, each stroke needs their own layer to draw on while it is being made. In that case we would end up with drawing potentially hundreds of layers each step to draw all the strokes. “That's stupid!” I hear you scream, “Just commit the stroke layer to that of the canvas and done! No performance problem anymore.” Well, yes, I was getting to that. That would be the simple solution, but now consider the problem of a history. In order to undo our last action, we would have to clear the canvas and redraw all our <code>n-1</code> strokes on top again. Undoing would start to take very long very quickly, and there's no way around it because we can't simply compute the delta of a stroke (or any other change) and do a “revert” to get the previous state without having to copy the canvas that underlies the stroke for each stroke and keep that around indefinitely, or at least for as long as your history is. This doesn't seem like the most efficient way to go about it to me.</p>
<p>So, aside from having no caching at all or caching the deltas, the only way left would be to speed up the traversal of the strokes so that you only have to paint a part of them. This can be done in our scenario since we have the ability to group a range of strokes together into a single layer that can then be drawn instead of all the strokes on their own (this is however complicated by only being able to group together strokes of the same composition mode). Another reason why this is easily possible is because any history is implemented as a stack, so we don't have to worry about removing or adding elements at arbitrary positions, only ever at the head. Knowing this we can quickly devise a structure that will give a <code>O(log(n))</code> traversal, push, and pop. If each stroke is pushed into a group on the stack, we can start to group groups of the same magnitude together into greater groups. If you draw this up, you'll quickly see that we're arriving at a logarithmic amount of actual items in the stack. Each push and pop we might have to re-balance this grouping tree, but that should also only take maximum one traversal of the base stack, meaning logarithmic steps again.</p>
<p>The initial idea for this didn't come from me and was actually already around during the time I was working on the first Parasol version. I asked around on <code>#lisp</code> and after some brainstorming someone came up with this sweet idea. I only now got around to starting to implement the necessary data structure. I really hope that this strategy is worth it, but I can't say for sure yet, as there are a couple of things that worry me: First, when grouping strokes together we have to assume that their layers are positioned relatively close together. If they happen to be far apart often, the memory performance will suffer greatly as we will have to allocate one giant layer to contain both with a lot of unused space in-between. Furthermore, this kind of grouping strategy has a couple of factors (max group size, head buffer size, total max size, whether to preemptively explode groups upon pop, etc) that could heavily influence how well it performs under different circumstances. I have no idea what's going to be the right choice to make here, so I'll have to do lots of tedious testing.</p>
<p>Due to the memory constraints it might actually even turn out that keeping the deltas of each stroke might turn out better in the average case. Currently I really don't know and I'm merely going by what intuition tells me. I'd be interested in knowing what kinds of strategies existing painting applications employ for this scenario, but I could not find any resources at all related to this and I'm not willing to dive into the huge sources of OSS painting applications to find out on my own.</p>
<p>I probably won't have anything more to talk about for another month now, as I'll have to dive into studying and put all my programming work behind me for quite a while. Who knows, maybe I'll have a better idea than this grouping stack until then, but I'm kind of doubtful of that.</p>
Sketching Painting Rendering - Confession 402982014-11-21T19:19:082014-11-21T19:19:08shinmerahttps://shinmera.com/shinmera@tymoon.eu<p><img src="http://shinmera.tymoon.eu/public/B02syh3CEAA0dA_.jpg" alt="header"><br>
I'm not writing this because I actually think I have much to say, but rather because I want to slack off from working on <a href="http://blog.tymoon.eu/tagged/parasol">Parasol</a>. In particular, I'm dealing with internals related to getting things actually drawn. Computer graphics in general have always been a huge headache area for me, so it's no surprise that I'm not having much luck with Parasol either in this regard. In order to minimise the pain, I've laid out a small plan of action for this.</p>
<p>Currently Parasol “just werks”, but it doesn't work well at all. Drawing is horrendously slow and lagging, especially on my workstation (don't ask me why my laptop performs better). This is related to two factors. First, all painting operations are currently done in the same thread as the event loop. This means that I'm blocking the event loop for rather long times, which in turn means it skips some input events. I've described this problem before and the solution is still to thread things and I haven't come to that yet.</p>
<p>But, I shouldn't have to yet either since performance is so bad that doing this wouldn't get rid of the lag. This means that I first need to optimise the rendering pipeline. I don't really know why this is performing so much worse than the original Parasol as I don't see that I'm doing much different, but no matter. The question is how to optimise things to go fast.</p>
<p>The first step that I've laid out to solve this conundrum is to abstract away the interaction with painting targets. Currently the system employed is a mix of direct and indirect target usage. Some things directly create and use QImage instances (and create a QPainter to draw on them). If I want to be able to keep things localised and switch between different targets to test performance and compatibility, I need to rip this all out and put it into its own abstraction layer.</p>
<p>I've started work on this layer today. What I've conjured up so far is a simple class based system that every render target has a subclass for. A special variable decides which target is currently active and the <code>make-target</code> function then sets up the proper target to use in your endpoint. Of course this means that we somehow need to generalise access to the target. From what I can see so far it should be sufficient to provide copy and draw methods, as well as a method that returns a usable QPainter object.</p>
<p>According to what I could gather of the Qt docs, the fastest way to render anything would be to use <a href="http://qt-project.org/doc/qt-4.8/qglframebufferobject.html">QGLFramebufferObject</a>s. This, as the name implies, relies on OpenGL to do the rendering work and has thus full hardware acceleration, rather than doing it all on the CPU as would usually be the case with a QImage. However, this also means that we have to deal with OpenGL and rendering contexts and all that bullpatootie, which is the cause of the aforementioned pain.</p>
<p>Working with the QPainter and QImage classes is surprisingly pain-free and really does just work nicely for the most part. But as is always the case, with speed comes pain and I need speed, so I'll have to take the pain too. I'm hoping that by abstracting all direct access to the render targets away I'll have an easier time debugging things and getting it all to run. Setup, copying, finalising and drawing will all be handled in the same parts, so I won't have to go hunt around my code-base for other uses and duplicates and whatnot. Or at least I hope I won't.</p>
<p>I have no idea how long this is going to take me. Today I've gone and just plainly written out the code for the rendering targets that I think should work without testing any of it yet. It's not integrated into any part of the system at this point though and even trying to just create a GLFramebuffer target seems to result in a memory fault error on SBCL, which is pretty bad news because it means I have to stumble in the dark for a while until I find a way to make it work. I'm guessing it's related to rendering contexts or some crud like that, but who knows. Anyway, the next step is to first integrate everything with the targets system using a QImages back-end so I can actually test things and then try to switch over to more optimised targets.</p>
<p>Depending on how long this takes me to do and whatever other kinds of performance hurdles I find it might be a while before I can show off something new and cool about Parasol. I've already spent this morning hunting down finalisation bugs, another problematic aspect that I don't think I've fully conquered yet. Though, I'd say it's more of a divergence from original plans that I got that much of the UI done so early on. I should've been doing much more back-end work for a long time, so I suppose I have to catch up on some of that now.</p>
<p>The next entry on this series is to be expected once I get everything sorted out related to performance, whenever that may be.</p>
<p>Wish me luck.<br>
<img src="http://shinmera.tymoon.eu/public/40335025_p18.jpg" alt="footer"></p>
Paranormal Parasols - Confession 362942014-11-04T16:01:412014-11-04T16:01:41shinmerahttps://shinmera.com/shinmera@tymoon.eu<p><img src="http://shinmera.tymoon.eu/public/tumblr_nc481xFman1qhttpto1_1280.jpg" alt="header">
It's been too long since my last entry. I just haven't had much that I felt safe talking about. But, now that I'm mostly done with everything that occupied me for a while (Radiance and Purplish), I have more time available for other things. One of these things happens to be <a href="http://blog.tymoon.eu/article/257">Parasol</a>.</p>
<p><img class="side" src="http://shinmera.tymoon.eu/public/46211445_p0.jpg">As you may or may not know, Parasol is a Common Lisp painting application that was born out of curiosity and wasn't really meant to be anything big, but then quickly exploded in size. But as it is with these things, at some point I hit a big roadblock: It couldn't process events fast enough and was thus not getting enough feedback from the tablet pen. This happened because the drawing operations took too long and clogged up the event loop.</p>
<p>To solve this problem, we need to put the operations that take a long time into a separate thread and queue up events while it runs. I tried to hack this in, but the results were less than fantastic. It could go either one of two ways; either the entire CL environment had a segmentation fault at a random time, or the painting would be littered with strange drawing artefacts.</p>
<p>The only thing that I could guess from this was that Qt wasn't happy with me using CL threads. But that wasn't the only issue. I really didn't like what I had built over the weeks of working on Parasol, as it reeked of patchwork design, without much of an ulterior architecture. So I put the project to rest for the time being, hoping to return to it some day and rewrite it anew.</p>
<p>In preparation for this day I recently wrote <a href="http://shinmera.github.io/qtools/">Qtools</a>, a collection of utilities that should make working with Qt easier. Writing that library alone caused me quite some amounts of grief, so I'm not very enthusiastic about diving deeper into the sea of tears that is C++ interaction.</p>
<p>Regardless, I couldn't keep my mind off of it and used some lecture time yesterday to write together a <a href="https://twitter.com/Shinmera/status/529253441772994560">crude design document</a> that should lay out a basic plan of action and architecture for the new Parasol version. I have started to set this plan into motion today.</p>
<p>First in line is building a skeleton main window with a pane for “gizmos” and a main area that can hold a range of documents or other forms of tabs. With that I'll have a minimal environment to test things out on. After that there's a large chunk of internal structure that needs to be completed: the document representation.</p>
<p>As I've laid it out so far, a document is composed of a set of layer-type objects, a history stack, and metadata such as the size and file associated with it. Layer objects themselves are composed of a position, size, drawables list, and an image buffer. A drawable itself is not specified to be anything fixed. Unlike before, it does not have to be a stroke, but simply an object that has the general methods a drawable has. This allows me to later add things that behave differently, such as images and 3D objects.</p>
<p>This change away from Parasol's original model necessitates two further, drastic alterations: First we need to have tools aside from a general brush that allow manipulating other kinds of objects, so we need to have a way to define ‘tools’ and their effects when used, in a uniform fashion. Secondly, we need a proper, generalised history that allows us to un/re-do any kind of operation on the document. Both of those things offer significant design challenges and I really hope I can pull it off.</p>
<p>Fortunately however, integrating these is a long while off, as after implementing a basic document I first need to add a way to publish these –so far purely virtual– documents to the UI. To tie these two worlds together, we need a view object that defines where to and how we're currently looking at the document. The view's job will also be to relay input events, such as tablet or mouse movement, to the actual document (and preserve the correct coordinate translation while doing so).</p>
<p>At this point I'd like to quickly talk about how I'm intending on solving the issue that initially brought Parasol to halt. After thinking things over I came to the realisation that my previous attempt of adding complex splines to the strokes to make smooth lines possible was an artefact of never getting enough tablet input to begin with. With enough events, a simple linear interpolation is a feasible approache. Knowing this it becomes apparent that we do not need to precalculate the spline's effective points (as linear interpolation is dirt cheap). From this it follows that I do not need to put the actual stroke updates into a separate thread, but can simply add points to the stroke in the event loop. The only thing that does have to be outsourced is the drawing of the buffers in the document.</p>
<p>In order to solve the drawing artefacts problem that arose in my previous attempt, I thought I'd take a look at a solution of threading from Qt out. After all, it might just be that Qt isn't happy with being called from threads it doesn't know about. After looking around I found out that the library CommonQt uses to bind to Qt (smokeqt) does not include these classes by default, even though they could be parsed.</p>
<p>Adding these classes to the library was easy enough, a simple XML config change and a recompilation made it available. But whether it actually worked or not was a different question. At first it seemed that it would not work at all. A callback from a QThread back into lisp would always cause <a href="http://www.sbcl.org/">SBCL</a> to segfault, which was a rather devastating sign. Fortunately enough it seems that <a href="http://ccl.clozure.com/">CCL</a> instead works just fine with foreign threads. Halleluyah!</p>
<p>I haven't tested whether everything works just fine and dandy with this idea yet, but hopefully I'll get to that soon enough. However, threading always comes at the price of an immense complexity increase. Object access needs to be carefully synchronised with mutexes or other techniques. Thanks to the fact that interaction between the two threads is minimal, this doesn't pose too big of an issue. Once the drawing thread is done it only needs a single atomic call to set the buffer of the object to its finished value and otherwise it only needs to read things from vectors, where it won't make a big difference if it misses an element or two ahead.</p>
<p>Or at least so I hope. I'm sure that I'll get plenty of headaches with threading once I get to it. For now I'll be content with imagining that it'll all work out perfectly fine.</p>
<p>So, when this is all well and done I can move on to writing the architecture for the tools and history operations and with that the general brushes tool, which I hope I can mostly copy over from the previous version.</p>
<p>With image operations, threaded drawing, history and document presentation all sorted out the final step will then be to make a front-end for it all: Layer management, colour picking, brush configuration toolbar, a pluggable gizmos panel and so on and so forth.</p>
<p>If all goes according to my dreams I'll end up with a painting application that is extensible in every aspect imaginable, exhibits the most powerful user-configurable brush-engine I've seen, offers support for integrating and rendering 3D models as well as plenty of other constructs, and allows rudimentary image manipulation.</p>
<p>All of this is left up to your and my imagination for the time being, but I certainly hope to make it a reality at some point.</p>
<p>I might talk more about my progress as I advance, but maybe I'll keep on thinking I have nothing to talk about, irregardless of how much truth there is to that in actuality.</p>
<p>…</p>
<p>Until the sun shines again.</p>
About Parasol2572014-06-11T20:32:232014-06-11T20:32:23shinmerahttps://shinmera.com/shinmera@tymoon.eu<p><img src="http://shinmera.tymoon.eu/public/screenshot-2014.06.10-21:58:07.png" alt="image">
In an effort to get at least something productive done today I decided to take it upon me to talk a bit about my latest project, <a href="https://github.com/Shinmera/parasol">Parasol</a>. I've attempted to write large GUI oriented applications before, but they never really went anywhere significant. This time seems to fare much better already though, as Parasol has, within barely a week, evolved into a tablet painting application with layers, history, infinite canvas and fancy colour pickers.</p>
<p>I never really planned on starting this project, it mostly seemed to happen by accident. I've been thinking about writing a tablet input library for a while, but after trying to inform myself on how to go about this I pretty much abandoned the idea. At some point the thought occurred to me that I could leverage an existing GUI framework to do this. Lo and behold, I found that Qt does have support for tablet input events already. Fantastic!</p>
<p>So, the next step was to make a little experiment to see how difficult it would be to use Qt from Lisp (using CommonQt) to make something primitive resembling a painting app. Turns out it was surprisingly simple. Enthralled by the simplicity of it I fell into the trap; a new project had begun. Over the next few days I implemented incremental spline curves, colour pickers and all the rest I mentioned.</p>
<p>My experience with Qt (and by extension CommonQt) has been largely positive. Qt offers an incredible amount of stuff and its documentation for everything has covered most of my questions. Whatever else there was could usually be resolved through a quick googling. Sadly, as with all GUI frameworks the work itself is still inherently painful. There's never a good mapping between UI elements and application data, so you end up with a weird duct-taped product that doesn't seem whole.</p>
<p>What I've been most dissatisfied with over the course of this though is myself, as I think I've done a rather miserable job at containing the mess. It doesn't feel on the same level as the rest of the things I've written ‒ whether that's the fault of UI or not ‒ and I hope I can rectify this once I've got the base system all hammered out.</p>
<p>Currently the next step in the development of Parasol is to add more curve types as I've found splines to be too resource intensive for the use in brush strokes. I'll try a few different interpolation algorithms and offer an option to choose between them in the UI for those who care. After I've found a good match for that I'll finally be able to move on to one of the most important aspects, the brush engine.</p>
<p>After that I'll have more fun implementing a variety of algorithms for input smoothing. Yes, I'll add a UI that will allow you to regulate how and by how much your strokes are smoothed out, similar to how Paint Tool SAI allows you to do it.</p>
<p>Once all these systems are in place I'll finally have the time to worry about code architecture and performance. At that point though it should already be a fairly well-enough usable painting application. Who knows, maybe some people will actually start using it for serious, that would be a nice surprise.</p>
<p>I might talk about what I've learned through this project in a later entry. Currently my thoughts are still too much over the place for me to feel comfortable in sharing much of the details.</p>