Custom LookAndFeel, drawRotarySlider, frames not original


#1

Hi,
I have downloaded the Juce-look-and-feel-examples (skinTest program):


and have replaced the image file with my JKnobMan image file for
the slider.

[attachment=2]Original.png[/attachment]
(Screenshot inside XnView)

Left side 3 of 101 knobs and right the original Test Knob from
JKnobMan. One frame size is 50x50. The first image and the test knob
looks like the same. I have zoomed in. But when I start the skinTest
program the image looks like not the same:
[attachment=0]skinTest01.PNG[/attachment]

Left the knob from the program with modules version 2.0.21,
center (blue arrow) the test knob from JKnobMan
and right the knob with the current modules version 2.0.25.
(I have resized in the Jucer the slider to 51x51 and no textbox.)
The ring left looks like thicker and between the ring and the knob is
the color darker (the shadow below too). The right side needs no comment,
something is wrong.

For this program I have used the stable release of Juce v2.0 from
Sourceforge (modules version 2.0.21), MS Visual Studio 2010 Express and
MS Windows 7.
The important code snippet:

[code]void LookAndFeelCustom::drawRotarySlider(Graphics &g,
int x,
int y,
int width,
int height,
float /sliderPosProportional/,
float /rotaryStartAngle/,
float /rotaryEndAngle/,
Slider &slider)
{
/* This is the binary image data that uses very little CPU when rotating */
Image image = ImageCache::getFromMemory(BinaryData::NI_Razor_Knob_png,
BinaryData::NI_Razor_Knob_pngSize);

int imageWidth = image.getWidth();
int imageHeight = image.getHeight();

/* Value between 0 and 1 for current amount of rotation */
const double fractRotation = (slider.getValue() - slider.getMinimum()) / (slider.getMaximum() - slider.getMinimum());

/* Number of frames for vertical film strip */
const int frames = imageHeight / imageWidth;

/* Current index from 0 --> nFrames - 1 */
const int frameIndex = (int) ceil(fractRotation * ((double) frames - 1.0));

const float radius = jmin(width / 2.0f, 
						  height / 2.0f);

const float xCentre = x + width * 0.5f;
const float yCentre = y + height * 0.5f;

const int destX = (int) (xCentre - radius/* - 1.0f*/);
const int destY = (int) (yCentre - radius/* - 1.0f*/);
const int destWidth = 2 * (int) radius;
const int destHeight = 2 * (int) radius;

const int srcX = 0;
const int srcY = frameIndex * imageWidth;
const int srcWidth = imageWidth;
const int srcHeight = imageWidth;

DBG(("LookAndFeelCustom::drawRotarySlider destX:") + String(destX) + 
	(", destY: ") + String(destY) + 
	(", destWidth: ") + String(destWidth) + 
	(", destHeight: ") + String(destHeight)
	)

DBG(("LookAndFeelCustom::drawRotarySlider srcX:") + String(srcX) + 
	(", srcY: ") + String(srcY) + 
	(", srcWidth: ") + String(srcWidth) + 
	(", srcHeight: ") + String(srcHeight)
	)


//g.setOpacity(1.0f);
g.fillAll(Colour((uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0));


AffineTransform affineTransform = AffineTransform::scale(1.0, 1.0);

g.drawImageTransformed(image.getClippedImage(Rectangle<int>(srcX, srcY, srcWidth, srcHeight)),
	affineTransform,
	false
);

/*g.drawImage(image,
            destX,
            destY,
            destWidth,
            destHeight,
            srcX,
            srcY,
            srcWidth,
            srcHeight
            );*/

}
[/code]
The next try was to set the ‘fillAlphaChannelWithCurrentBrush’ to true:
[attachment=1]fillAlphaChannelWithCurrentBrush.png[/attachment]
The shape looks like good but between the ring and the knob the color is darker.
(Right the test knob)

Please, can somebody help me?
Have I consider something?
Is it a bug?

Thanks,
Thomas


#2

I gotta say, that KnobMan is pretty cool! Where’s the source code? And why hasn’t this been developed into something that works at run-time instead of creating a bunch of images?


#3

You’d have to ask g200kg that, but you might find it’s not fast enough to do procedural drawing. I’ve used it to create knobs for SynthMaker plugins (where the image strip approach is preferred) and have found that detailed knobs with anti-aliasing and nice shadows can take quite a while to generate.


#4

sigh… pre-generating them as images in a separate application is an optimization step. It would be trivial to draw them at run time and just cache all the individual images, if performance is an issue.


#5

If you’re generating a complex knob with nice shadows in the KnobMan paradigm, then you render the whole shebang for each angle of the knob. I think this would cause a noticeable delay each time you did a resize for example.

Of course, this could be optimised with rotating static parts of the knob and only rendering the non static bits, but then you’re getting away from the idea of porting KnobMan and more towards optimised procedural drawing with layer effects :smiley:


#6

You mean resize the window, or resize the knob? Controls are usually fixed in size. And if you render them beforehand there’s no meaningful way to rescale them.

I assume you mean resize the window. If knobs are generated at run time and the images cached, then the performance would be identical to creating them in KnobMan and importing the images.


#7

Resizing the knob would be the problem, not the window. I know not many people are interested in resizing GUI elements, but I am - call me weird!


#8

Well you can’t resize the knob using KnobMan generated bitmaps either so rendering them at run-time is no loss (and all gain).


#9

Even with rendering the controls at run time, resizing can still be handled. Just cache the control image and resize that instead, and when the window is finished resizing start a background task to re-render cached elements at the new size.

All paths lead to run-time rendering!


#10

Even with rendering the controls at run time, resizing can still be handled. Just cache the control image and resize that instead, and when the window is finished resizing start a background task to re-render cached elements at the new size.

All paths lead to run-time rendering![/quote]
Yeah I thought about doing that, but it sounded like too much work! I’m already so far behind where I want to be…


#11

Cool or not, that was not the question. I think it is bug. I must repeat: the left knob image (1 image or better frame) was drawn with the modules v2.0.21 and it is not the same from the image strip. The right
knob image was drawn with modules v2.0.25 and is bad (I used the same program without changes). For a smaller image I have started both programs and placed one above the other. In the center is
the original test knob from JKnobMan to compare.
I don’t want to resize the image(s). It is only a test, how easy it is to change the look and feel.
BTW: I have copied the original LookAndFeel::drawRotarySlider into the custom LookAndFeel class to ‘play’ something with it.

Thomas


#12

The drop-shadow rendering code has changed recently, maybe that made a difference? The old version was a bit broken and drew the shadow in the wrong place, but maybe this code relied on that broken behaviour?


#13

Everything is ok! :smiley:
As I played with the original rotary slider, I saw that there is a shadow, but I hadn’t add a shadow function.
So, I looked into the custom LookAndFeel class functions and found these two:

virtual ImageEffectFilter* getScrollbarEffect(); virtual ImageEffectFilter* getSliderEffect(); :roll:
I disabled both and everything works (v2.0.21 and v2.0.25).

So, it is not a bug, … uff I can draw what I want. :lol:

Thomas


#14

But you need all the functions first. I don’t know it exactly, but I mean Juce have not all functions yet, but JKnobMan. I understand what you want. I have watched with the Debugger
what happens during the knob spin. There are a lot of function calls.
For the beginning with Juce it is faster, with more experience I prefer your way. :wink:
More interesting is, what is a good practice if I have more than one L&F for multiple rotary sliders.
Example: I have 10 sliders for input, 5 sliders for output and 4 common sliders and all ‘categories’ have the own L&F.
My solution is, I set properties with ‘NameValueSet’ like (“Category”, “Input”), (“Category”, “Output”) … and in
the ‘drawRotarySlider’ function I check the category and select the L&F.
Is there a better solution?

Thanks,
Thomas


#15

Idk if anyone still needs to reference this post, but I did so I'll give my solution for this. If you look near the bottom of the drawRotarySlider function, you'll see this:

const float rx = centreX - radius - 1.0f;
const float ry = centreY - radius - 1.0f;

See the problem there? It shouldn't have "- 1.0f" at the end. Just make it:

const float rx = centreX - radius;
const float ry = centreY - radius;

That should fix the problem if anyone is still using audioplastic's LookAndFeel examples. I imagine he had that there to fix his own image or something. Hope this helps someone. :)