Brush painting implementation questions

Started by
6 comments, last by meme83 15 years, 9 months ago
Hello there, for my master thesis I'm building an application that addresses the cartoons inbetweening problem. Since the target audience are mainly artists, I'm trying to build a user interface that respects their way of thinking, rather that the engineer way. What I'm trying to do at is to implement brush strokes. There are issues I'm trying to figure out: 1) What's the trick to have good performance. 2) How should I implement the undo/redo. 3) How can I get continuous strokes out of discrete positions? So this is what I tried now. 1) I think it might be relevant to say that I'm using C# and Managed DirectX. Although this choice might be unfortunate, it was convenient for many aspects. At the moment the solution I have looks like this. One thread reads the mouse input and populate a queue. A second thread reads the queue and passes the positions to a shader for the rendering. I build two triangles that span the screen and render them. The associated pixel shader computes the distance (u,v based) of every single pixel on the screen to the mouse position; if it's under a threshold value, computes the gaussian value and return it as color. The problem I'm experiencing now is that if I call a draw for every mouse position, the drawing is quite slow, and although you can continue to draw freely with no mouse position losses, it's quite cheap in terms of immediate feedback. I tried to speed it up by reducing the area of the drawing, changing the render target to only that area relevant for the current brush position. I actually didn't gain any performance doing this, and possibly the code is more dirty and cumbersome. I'm wondering if you know better ways to approach this. For the time being I didn't do proper profiling. If no better solutions are known, that will be the only way. 2) I have multiple options to support undo/redo, and the trade off is the classic memory space vs computational complexity. The undo/redo should work on strokes. Therefore I have a cumulation buffer for everything painted in the past and I have a buffer for the current stroke. I could store the cumulation buffers as the stroke are added, or I could store the stroke buffers. Both solutions require quite some memory. Or I could store the mouse positions, and then reuse the rendering process to reconstruct the strokes (this btw would allow to have sub-stroke undo, as the history brush). What do you suggest? 3) This is more a performance related questions. If I keep the shader that compute the radial based color, then I need to add centers between two mouse positions that are far from each other. But the app already struggle to draw the mouse positions; adding other centers will be its death. Any ideas? Thanks, meme
Advertisement
That weird. I made a drawing program with &#106avascript and that worked very good, so making it C# should then be even easier (in terms of language performance)

Here is how I did it:
While brush is active   if not been active for longer then maxLimit        no nothing    else      create a vector between current mousePos and      startMousePos in the form f(x) and do:      for i = 0, i less then length of vector          drawBrush      startMous = currentMouse


That did at least work very well in &#106avascript.<br><br>How many undo steps are you planning &#111;n supporting?<br> <br>
Did you use some libraries to draw the brush?
Yes, but that library function is only blitting the pixels to the screen. Unless your planning on painting with a 1400x1400 brush you should do fine without any library (I think)
I figured out the problem of speed. Drawing on the screen for every point is not a good idea. It's verty slow. Now for every point I draw on the texture, but I update the screen only when the windows form is redrawn. This gives a big boost on the speed, and allowed me to also add interpolated points.

So the only thing I need now is a good undo/redo design. Any ideas?
Hi meme,

With regards to Undo/Redo, you could possibly use the command design pattern:
http://en.wikipedia.org/wiki/Command_pattern

It would be pretty easy to implement this with your existing framework;
You can create a Command object on your thread that reads mouse input and put it on the queue. The Command object would have information for executing the command (drawing pixels) and also on how to undo the command (subtracting color values or however you want to implement this). It could have two functions execute() and undo() or whatever to handle this.

So you create these Command objects in your first thread, put it on the queue, and the second thread then processes the object. When the object is processed, push it on a stack.

Then, when the user wants to undo/redo, you just pop the first command object from the stack, and either run the execute() or undo() function as appropriate.

Just something to think about!

Best of luck-
Bryan
Hi Bryan,

thanks for the hint. I have the command pattern implemented, so my question is not a regarding the software design pattern, but rather what implementation I should use.

If I keep a track of all the cumulated strokes, saving the final result for every stroke, then undo redo is easy, but memory consuming.

To consume less I could store only the stroke. But to be able to subtract them to do the undo, I would need to scale down the color range (otherwise the clamping would make me lose the information, and subtracting the stroke would lead to inconsistent image states).

I probably limit the number of undo, and simply store the cumulated strokes trough time.

Thanks for the answer,
meme
Hello,

here you see a screenshot of the current prototype.
http://n.ethz.ch/~gnoris/download/paint_app3.PNG

Here you see how I implemented the brush so far:
http://n.ethz.ch/~gnoris/download/brush_profile.png

In order to achieve alpha blending, brush points within one stroke are combined with a max function. Strokes however are added. This works fine for the alpha, but leads to some artifacts (look at the brighter stroke, in the upper left corner).

I'm not sure whether the problem lies on the brush shape or in the max function.

Maybe a quarter of a cosinus is not a good choice. However one thing is relevant to me is that when you reduce the hardness the perceived brush size should not change (this for instance happens if I sobstitute the quarter of cosinus with half cosinus shifted up and scaled down: mathematically is still continue and reaches zero, but it feels wrong as if the brush shrinks).

Any idea?

thanks,
meme

This topic is closed to new replies.

Advertisement