Thoughts on sliders

I'm thinking about a nice rescalable GUI for the next plugin.   So, I"ve made myself some software rendered sliders which match the PNG based ones I used in Gimp for the last plugin.  See below.  They rescale really nicely, and I don't have to use GIMP if I want them in a different colour, or with a knurled grip, a different angled light or whatever.  It's awesome!

But I've got some issues: 

Performance

DropShadow takes about 80% of the CPU when resizing even one knob.  The rest of the code is surprisingly quick.  Options: (a) don't draw the shadow when resizing (b) cache the shadow (c) write a quicker shadow routine ... 

Alignment

Should I centre the knob in the component?  I'm thinking I need to even if that makes the component bigger it'll be really annoying to try and line things up otherwise!

Integration with Slider  

There's quite a lot of pre-calcuation of stuff, Paths, tangents for the shadow (thanks Pi!) and other bits.  I guess I'm going to have to (a) subclass Slider (b) override resized() to do the pre-calcuation (c) put the rendering code into a LookAndFeel class (d) dynamic_cast the Slider& that gets passed to look and feel back to my slider subclass and get the precalcuation stuff from there.

 Unless anyone can think of a tidier method?  

Shadow

Because the shadow can extend down and right quite a long way, I'm wondering if the Slider shouldn't own two components, the knob and the shadow overlay so that the knob size itself is nice and tightly specified by mySlider.setBounds(.....) and then the shadow happens on top.

Any thoughts greatly appreciated, or failing that at least I've documented my list of problems :)

cheers! 

Looks cool!

Re: alignment, since most of the component isn't drawn, then there's no real harm in leaving empty space around it, so yes, probably best to make it too big and to put the slider in the middle. You can use hitTest to make only the knob react to the mouse.

How are you drawing the shadow? The fastest way would be to use a transformed radial gradient if that's possible, but you may already be doing that?

 

I draw the path shown in debugging-yellow on the picture. And then use DropShadow's method to fill it.  And then the sweat pours off the CPU. 

As most of the knobs will be the same size, I could cache the output of DropShadow. But I'm wondering if there's a more efficent way.  It'd be nice if it'd resize at 25 frames a second ... it'll barely do that with one knob at the moment. :)

You suggesting stretching the radial gradient into roughly the right shape?  A straight radial shadow (like a circle) makes the knob look like it's floating in front of the screen rather than attached to it.

Yeah, you'll get the best shape with DropShadow, but it involves creating a bitmap each time, doing some blurring and drawing it, which is a lot of pixels to move around. Caching it would probably be faster.

If you had the patience, you could probably approximate it by drawing multiple gradients for different sections of the shadow, but it'd be a pain to work out where they all go and to make them join up.

Is the blur as quick as it can sensibly be already? 

Caching sounds like the most straightforward solution for now though.

Feel free to have a go at speeding it up! I think it's as fast as it could be without writing CPU-specific intrinsics.

My thoughts would definitely be to pre-render (cache) the shadow texture, you'll get limited returns from trying to optimise the blurring code.

Since the shadow's a nice soft shape, it'll tolerate stretching during a resize operation without looking awful, and then you can regenerate the cached version at your leisure once the resize operation has completed. 

For extra poshness I'd blend from the old version to the new one over a few frames to avoid a visual pop - the human eye is much more sensitive to discontinuities than it is to blurred edges.

If anyone fancies a programming puzzle, it'd be fun to try writing an algorithm that takes a blurred alpha-map and calculates a minimal set of polygons + gradients that approximate it to within a given tolerence.. That'd be an interesting challenge!

Ah man, reminds me of an offline tool I once wrote for a games application - take a 2D image generated by a level designer, and convert it to a simplified 2D polygonal mesh suitable for the AI algorithms to use for navigation. 

It started off simple (like all software!) but dealing with the segmentation of multiple colours - 'nested holes' and flipped winding orders - aargh!  It's one of those algorithms where no matter how considered your approach and how careful you are in implementation, there's always an edge case out to get you!

Libraries like this are helpful : http://www.geometrictools.com/Documentation/Documentation.html

..related application note : http://www.geometrictools.com/Documentation/ExtractLevelCurves.pdf

 

 

 

I'm definitely going to come back to this.  I think you are right doing some caching is the way forward.  There's a lot that can be cached here really - the differences between a knob pointing left and one pointing right aren't really that great :) 

However, if slightly slow to resize, they are starting to look quite hot I think!  I'm managed to shoe-horn them into a LookAndFeel class and a component now. 

That's looking pretty tasty!

Yeah.  I think those are done!  Just need to do the buttons now :) 

Well, caching the shadows took about 15 lines of code.   There's a beautiful generic caching function in there waiting to get out. 

  • CPU usage down to a quarter.
  • Blur function relegated off the profiler charts. 
  • Resizing a more glorious user experience. 

I might have another sip of tea to celebrate.

Now to find out why it was so keen on redrawing those knobs anyway, even when they weren't moving.

1 Like

Hey Jim, is this still waiting to get out?! :grimacing: Or would you be willing to share some pointers on what approach you settled for?

Oh - which bit do you want to know about?

Final product ended up looking like this (better photo here maybe http://www.pluginboutique.com//system/resources/srcs/000/015/420/original/StereoSavage_PluginBoutique_Expand_120.jpg?1471956979):

I can’t remember why I gave up on the blurred shadows, might have been a graphic design decision rather than a CPU one.

The dark knobs and buttons are entirely software rendered, no PNG or even SVG in there.

4 Likes

Looks fantastic Jim!! Mine’s not all that fancy but the DropShdows are stacking up and preventing a smooth interface, so was interested in your cache solution!

Best,
Tom

Ah - well I think it was just rendering it to a bitmap and then drawing that when required. Though in the end, as you can see, i just ended up with a non-blurred solution using a path which was then fast to draw. I can see if the old cached bitmap code is there still tho…

2 Likes

Thanks Jim, I did something along those lines!

Created an image of the DropShadow, added it to a cache and then paint it using drawImage and redraw it only when the slider’s size has been changed

On another note I found that what was really creating the CPU-overhead was the fact that I had not set my cpu-consuming widget (which is constantly repainting) to opaque so… well yeah, that helped! So thanks to Jules for mentioning this in a thread dated from 2005!

Yeah - it’d almost be better if opaque was the default and you had to make a choice to set it non-opaque. At least it’s obvious when you’ve made that mistake :wink:

2 Likes

Looks amazing. Did you draw them entirely with Juce, or did you use any third party tools?