Motion blur on meters

I’ve been messing around with motion blur on a meter all afternoon after being annoyed with one that looked a bit jittery. Thought it was interesting to see what looked best …

Any other suggestions on how to make the motion look better greatly appreciated :slight_smile:

class BlurBar
:
public Component
{
public:
    Colour bgColour { 0xff202020 };
    Colour fgColour { 0xffd0d0d0 };

    void setBlurEnable(bool shouldEnable)
    {
        blur = shouldEnable;
    }

    void setValue(float newValue)
    {
        value = newValue;
        repaint();
    }

    void setBlurAlpha(double newAlpha)
    {
        alpha = float(newAlpha);
    }

    void paint (Graphics& g) 
    {
        g.fillAll(bgColour);
        
        auto rect = getLocalBounds().toFloat();

        rect.setWidth(rect.getWidth() * value); // scale our meter
        
        g.setColour(fgColour);
        g.fillRect(rect); // paint it


        if (blur && lastRect.getWidth() != rect.getWidth())
        {
            bool increasing = lastRect.getWidth() < rect.getWidth();
            
            float x1, x2;
            
            if (increasing)
            {
                x1 = lastRect.getRight();
                x2 = rect.getRight();
            }
            else
            {
                x1 = rect.getRight();
                x2 = lastRect.getRight();
            }
            
            g.setGradientFill({
                fgColour.withAlpha(alpha), x1, 0.0f,
                bgColour.withAlpha(alpha), x2, 0.0f,
                false
            });
            
            g.fillRect(x1, rect.getY(), x2 - x1, rect.getHeight());
        }
        
        lastRect = rect;
    }
private:
    Rectangle<float> lastRect;
    bool blur{false};
    float value{0.0f};
    float alpha{0.0f};
};

class MainContentComponent   : public Component, Timer, Slider::Listener, Button::Listener
{
public:
    LookAndFeel_V3 lookAndFeel;

    MainContentComponent()
    {
        getLookAndFeel().setDefaultLookAndFeel(&lookAndFeel);

        blurEnableButton.addListener(this);
        blurEnableButton.setClickingTogglesState(true);
        blurEnableButton.setColour(TextButton::buttonColourId, Colour{0xff15BB58}.withMultipliedBrightness(0.2f));
        blurEnableButton.setColour(TextButton::buttonOnColourId, Colour{0xff15BB58});

        framerateSlider.setRange(5.0, 200.0, 1.0);
        framerateSlider.setSkewFactor(0.5);
        framerateSlider.setColour(Slider::thumbColourId, Colour{0xffFFA71D});
        configSlider(framerateSlider);

        incrementSlider.setRange(0.0, 0.1, 0.001);
        incrementSlider.setColour(Slider::thumbColourId, Colour{0xff205FAB});
        configSlider(incrementSlider);

        blurAlphaSlider.setRange(0.0, 1.0, 0.001);
        blurAlphaSlider.setColour(Slider::thumbColourId, Colour{0xffFF481D});
        configSlider(blurAlphaSlider);

        setDefaults();

        startTimer(framerateSlider.getValue());

        controls.push_back(&framerateSlider);
        labels.push_back("Frame Rate");

        controls.push_back(&incrementSlider);
        labels.push_back("Speed");

        controls.push_back(&blurEnableButton);
        labels.push_back("Blur");

        controls.push_back(&blurAlphaSlider);
        labels.push_back("Blur Alpha");

        for (auto c: controls)
            addAndMakeVisible(c);

        addAndMakeVisible(blurBar);

        setSize (600, 400);
    }

    void configSlider(Slider & slider)
    {
        slider.addListener(this);
        slider.setSliderStyle(Slider::LinearBarVertical);
        slider.setColour(Slider::textBoxOutlineColourId, Colours::transparentBlack);
        slider.setColour(Slider::textBoxTextColourId, Colours::lightgrey);
    }

    void buttonClicked(Button *)
    {
        bool buttonDown = blurEnableButton.getToggleState();

        if (buttonDown)
            blurEnableButton.setButtonText("ON");
        else
            blurEnableButton.setButtonText("OFF");

        blurBar.setBlurEnable(buttonDown);
    }

    void setDefaults()
    {
        framerateSlider.setValue(1.0f);
        incrementSlider.setValue(0.05f);
        blurEnableButton.setToggleState(true, sendNotification);
        blurAlphaSlider.setValue(0.5f);
    }

    void paint (Graphics& g) 
    {
        g.fillAll(blurBar.bgColour);
        g.setColour(blurBar.fgColour);

        {
            g.setFont(Font("Century Gothic", 20.0, 0));
            auto r = getLocalBounds().withY(100).withHeight(30);
            auto w = getWidth() / labels.size();

            for (auto l: labels)
                g.drawText(l, r.removeFromLeft(w), Justification::centred, false);
        }

        g.setColour(blurBar.fgColour.withAlpha(0.5f));
        g.drawText("Shows the effect of motion blur at various speeds and framerates",
                   0, getHeight() - 40, getWidth(), 20, Justification::centred, false);
    }
    

private:
    void timerCallback()
    {
        auto inc = incrementSlider.getValue();

        value += direction * inc * (framerateSlider.getValue() / 30.0);

        if (value < 0.0f)
        {
            direction = 1.0f;
            value = -value;
        }

        if (value > 1.0f)
        {
            direction = -1.0f;
            value -= (value - 1.0f);
        }

        blurBar.setValue(value);
    }

    void resized() 
    {
        auto area = getLocalBounds().withHeight(100);
        auto w = area.getWidth() / controls.size();

        for (auto c: controls)
            c->setBounds(area.removeFromLeft(w));

        blurBar.setBounds(getLocalBounds().withY(200).withHeight(20));
    }

    void sliderValueChanged(Slider * slider)
    {
        if (slider == &framerateSlider)
            startTimer(std::round(framerateSlider.getValue()));

        if (slider == &blurAlphaSlider)
            blurBar.setBlurAlpha(blurAlphaSlider.getValue());
    }
    
    Slider framerateSlider;
    Slider incrementSlider;
    Slider blurAlphaSlider;

    BlurBar blurBar;

    std::vector<Component *> controls;
    std::vector<String> labels;

    float value {0.0f};
    float direction {1.0f};

    TextButton blurEnableButton{"Enable blur"};
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
3 Likes

I’m interested in this too esp for mobile apps. I’ve used simple smoothing with an accumulator to round off jittery meters but not tried blurring yet.

If you have time would be nice to see a video of your code in action :slight_smile:

I’m not sure an animated GIF does it justice really as it’s mucked with the framerate a bit - and there are some nasty artefacts showing up that I don’t see on the monitor here - however - here you go!

If you really want to see what it looks like, open jucer, New GUI application and replace the MainComponent.h with the code above which is the complete app.

5 Likes