Knobman - best practises


#1

Hi all,

I don’t really have a Juce coding issue but I’m just looking for some advice from any experienced plugin developers out there. Basically I’m working on a package that allows users to customise their own plugins etc. So essentially their sliders can be of any size. I have designed a rotary knob using knobman, and use both 32x32 and 64x64 image strips. However, when the size of the slider is different to these dimensions it has to either upscale or downscale, and causes them to either look blurry or rough around the edges. What’s the best practise here? Should I be using Juce Graphics instead where possible?

Thanks…


#2

Why would anyone use a pre-calculated set of bitmaps when they could just draw the vector object, with an applied rotation transform??


#3

Because I don’t want to rotate the light and shade aspects of the image too. Knobman is very useful for creating sliders but resizing the images ain’t working out so good. So, you’re saying I should paint an image manually using Graphics methods, and then apply a transform to scale it up or down?


#4

When I’ve done this before I’ve loaded the image as a Juce Image and then drawn a section of it in the paint method using Graphics::drawImage. I would make sure your image is larger than the desired slider size to avoid pixelating. You can always set Graphics::setImageResamplingQuality to high before you draw your image section if you notice some artifacts but I’ve always found the default to be adequate.

Alternatively you could re-cache your image to the size being used in resized() with Image::rescaled, but this might be overkill.

But as TheVinn says for the best results you could use the component parts of your source image and apply your own transform to them.


#5

Then don’t apply a rotation transform to the light and shade aspects. If I recall, doesn’t Juce have some image compositing operators? Like Photoshop’s “Lighten”, “Darken”, “Screen”, “Multiply”, etc… transfer modes? Those are quite useful for applying lighting effects.

Not exactly. I would just draw the control using Graphics, period. No need to draw into an Image and then copy it to the screen, unless you want to use more advanced image compositing operators like the ones I described.

In my app, I accomplish lighting and shadow effects by drawing shaped gradient fills, where the color goes from fully opaque white = RGBA(255, 255, 255, 255) to fully transparent white = RGBA(255, 255, 255, 0) for lighting, and fully opaque black = RGB (0, 0, 0, 255) to fully transparent black (0, 0, 0, 0) for shadows.

Draw the knob face with a transform, and then overlay the lighting and shadow effects to exclude the transform.

For really nice looking knob shadows you can use the Gaussian Blur code I posted. Draw the knob in all-black into a separately allocated Image with associated transparency channel, then use my Gaussian Blur class to create a pixel-accurate blurred image which includes a blurred transparency channel and handles the edge pixels correctly. Copy the blurred image to the screen, with the drop shadow offset, then draw your knob over that with no offset. Juce also has a Component drop shadow class somewhere. After you draw the knob with a transform you can overlay some lighting, using white at various levels of transparency.

Here’s my blur class:

Fast Image Convolution Using Radial Symmetry (with source)

Not only is this orders of magnitude faster O(N) instead of O(N^2) where N is the blur radius but it correctly handles edge pixels and transparency.


#6

[quote=“dave96”]
But as TheVinn says for the best results you could use the component parts of your source image and apply your own transform to them.[/quote]

Not sure I fully understand how to do this. Does this invovle not using knobman at all and just using the native Graphics methods to draw the image?


#7

[quote=“omen”][quote=“dave96”]
But as TheVinn says for the best results you could use the component parts of your source image and apply your own transform to them.[/quote]

Not sure I fully understand how to do this. Does this invovle not using knobman at all and just using the native Graphics methods to draw the image?[/quote]

Yes.


#8

Can you post an image of the knob in its centered position?


#9

Just saw your reply now Vinn, appreciate the info. I’ll check out that blur class too.
Thanks.


#10

[quote=“TheVinn”]
Can you post an image of the knob in its centered position?[/quote]

Here you go…


#11

In the image above, I had it set up so that the inner circle’s colour and marker could be changed. I was just filling in the alpha channel for this using g.drawImage(…)


#12

This knob is easily drawn using Graphics routines, in this order:

  1. black circle fill with an ~8 pixel radius gaussian blur
  2. dark gray 12 sided regular polygon fill with a rotation
  3. light gray circle fill
  4. black rounded rectangle fill with a rotation
  5. arc section, 6 pixel stroke, fixed starting angle, ending angle determined by knob position

You would have to compromise a little on the value arc, there are two difficult aspects to it: One is the blur on the entire arc. The other is that the starting point has a “ramping up” blend. There are several ways of dealing with it, or you could just simplify that part a bit.


#13

[quote=“TheVinn”]This knob is easily drawn using Graphics routines, in this order:

  1. black circle fill with an ~8 pixel radius gaussian blur
  2. dark gray 12 sided regular polygon fill with a rotation
  3. light gray circle fill
  4. black rounded rectangle fill with a rotation
  5. arc section, 6 pixel stroke, fixed starting angle, ending angle determined by knob position

You would have to compromise a little on the value arc, there are two difficult aspects to it: One is the blur on the entire arc. The other is that the starting point has a “ramping up” blend. There are several ways of dealing with it, or you could just simplify that part a bit.[/quote]

Appreciate the help. I can get over not having a blend for the external arc. I’ll use your steps and see if I can reproduce it.
Thanks


#14

Actually, what’s the best way to draw a polygon using Graphics?!


#15

Declare a Path on the stack, call startNewSubPath() then do a series of lineTo() operations on the vertices. If Juce doesn’t have a routine to create an N-sided polygon with a prescribed radius you should be able to write one yourself, just divide 2*pi by the number of sides and loop over radians, call cos() and sin() with the appropriate angle, mutiply by the radius and use that in the call to lineTo() when building the Path. Or can create a “unit polygon” (radius=1) and just rely on the Graphics transform to scale it.

When you are done you can fill the path using the appropriate Graphics function. Or, you can stroke it. You would use the Path to create the arc section, the routine is called Path::addArc()

In fact now that I look at juce_Path.h, it has Path::addPolygon() which does exactly what you want.


#16

Declare a Path on the stack, call startNewSubPath() then do a series of lineTo() operations on the vertices. If Juce doesn’t have a routine to create an N-sided polygon with a prescribed radius you should be able to write one yourself, just divide 2*pi by the number of sides and loop over radians, call cos() and sin() with the appropriate angle, mutiply by the radius and use that in the call to lineTo() when building the Path. Or can create a “unit polygon” (radius=1) and just rely on the Graphics transform to scale it.

When you are done you can fill the path using the appropriate Graphics function. Or, you can stroke it. You would use the Path to create the arc section, the routine is called Path::addArc()

In fact now that I look at juce_Path.h, it has Path::addPolygon() which does exactly what you want.[/quote]
Nice one, I’ll check it out. Thanks.


#17

I’ve pretty much got the slider drawn using Juce graphics methods. I’m pretty pleased with the results. It looks great with a diameter of 50 pixels or over. Anything less and it looks very rough. Any tips for this? My attachment shows sliders of 50x50 and 100x100. The 2nd one looks so much better?
Is there any benefit to putting the image to cache? I wouldn’t think so since it gets redrawn every time the value changes anyway.


#18

This isn’t really to do with how you’ve drawn it, its a limitation of the screen’s resolution. Personally I wouldn’t worry too much about it, the 50px one looks fine. If you need really small controls you might want to consider simplifying the design of them below a certain size, Juce does this with rotary sliders below 12px.

What dpi screen are you using? The advantage of drawing vector based graphics like this is they scale properly, the higher dpi screens will look sharper and more detailed. If you hard code filmstrip images to a certain number of pixels your interface will just get smaller as the dpi increases or risk getting blurred as they’re scaled up. Ever compared an iPhone 3 & 4 side by side? Its all the same size but the 4 is pin-sharp.

Personally I only cache things that change a lot like scrolling waveforms or meters. Like you said, it gets re-drawn every time the value changes anyway. I doubt its worth doing any caching here unless it becomes an obvious bottleneck (possible if you have hundreds of sliders changing constantly).

To be honest I think the vector based knob looks better than the original one you posted.


#19

Maybe I should just simplify really small sliders then. I suppose I can’t split the pixel!
Thanks


#20

Well your knob is looking great now! I would just tweak a few things:

  • Larger corner radius for the rounded rect knob tick
  • Reduce the number of sides in the polygon to 10 if the size is below 60px

You don’t need to cache it.