3

I'm creating some custom Swing components that I'd like to have fade from one color to another. At the moment I'm converting from RGB to HSB then incrementing through the Hue value and converting back to RGB before painting, work's fine.

However, this cycles through all the colors (i.e. attempting to fade from blue to green cycles through yellow, orange, red etc). Is there a decent algorithm/method to fade directly from one color into another?

Edit: I already had it updating via a Swing Timer (I try to steer clear of touching components with Threads like the plague). I'll have a go this evening with your suggestions, THANKS GUYS!

Michael Hillman
  • 1,217
  • 3
  • 15
  • 24

5 Answers5

5

Based on this example, the Queue<Color> below cycles from Color.green to Color.blue and back to Color.green again in N = 32 steps. Note that Color.green is numerically less than Color.blue in the HSB model. See also this related example using HSB.

enter image description here

public Flash(JComponent component) {
    this.component = component;
    float gHue = Color.RGBtoHSB(0, 1, 0, null)[0];
    float bHue = Color.RGBtoHSB(0, 0, 1, null)[0];
    for (int i = 0; i < N; i++) {
        clut.add(Color.getHSBColor(gHue + (i * (bHue - gHue) / N), 1, 1));
    }
    for (int i = 0; i < N; i++) {
        clut.add(Color.getHSBColor(bHue - (i * (bHue - gHue) / N), 1, 1));
    }
}
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
3

I use a combination of approaches to achieve the same result.

Baiscally I use a simular API interface as the LinearGradientPaint, where I supply an array of fractions and an array of colors, then based a float percentage, I calculate the resulting blend color.

This allows me to produce a number of, effective, results through the same algorithm.

enter image description here

While this example is designed to demonstrate a blending of a range of colors, you could simply supply two colors and a fraction of {0f, 1f} for two colors

This allows me to effectively do color animation as well.

public class ColorFade {

    public static void main(String[] args) {
        new ColorFade();
    }

    public ColorFade() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
//                frame.add(new FadePane());
                frame.add(new ColorFadePane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class FadePane extends JPanel {

        private float[] fractions = new float[]{0f, 0.25f, 0.5f, 1f};
        private Color[] colors = new Color[]{Color.GREEN, Color.BLUE, Color.YELLOW, Color.RED};
        private float direction = 0.05f;
        private float progress = 0f;

        public FadePane() {
            Timer timer = new Timer(125, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (progress + direction > 1f) {
                        direction = -0.05f;
                    } else if (progress + direction < 0f) {
                        direction = 0.05f;
                    }
                    progress += direction;
                    repaint();
                }
            });
            timer.setCoalesce(true);
            timer.setRepeats(true);
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(100, 100);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            int width = getWidth();
            int height = getHeight();
            Color startColor = blendColors(fractions, colors, progress);
            g2d.setColor(startColor);
            g2d.fillRect(0, 0, width, height);
            g2d.dispose();
        }
    }

    public class ColorFadePane extends JPanel {

        private float[] fractions = new float[]{0f, 0.25f, 0.5f, 1f};
        private Color[] colors = new Color[]{Color.GREEN, Color.BLUE, Color.YELLOW, Color.RED};

        public ColorFadePane() {
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 100);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g.create();
            int width = getWidth();
            int height = getHeight();
            int bandWidth = width / 100;
            for (int index = 0; index < 100; index++) {
                float progress = (float)index / (float)100;
                Color color = blendColors(fractions, colors, progress);

                int x = bandWidth * index;
                int y = 0;
                g2d.setColor(color);
                g2d.fillRect(x, y, bandWidth, height);
            }
            g2d.dispose();
        }
    }

    public static Color blendColors(float[] fractions, Color[] colors, float progress) {
        Color color = null;
        if (fractions != null) {
            if (colors != null) {
                if (fractions.length == colors.length) {
                    int[] indicies = getFractionIndicies(fractions, progress);

                    float[] range = new float[]{fractions[indicies[0]], fractions[indicies[1]]};
                    Color[] colorRange = new Color[]{colors[indicies[0]], colors[indicies[1]]};

                    float max = range[1] - range[0];
                    float value = progress - range[0];
                    float weight = value / max;

                    color = blend(colorRange[0], colorRange[1], 1f - weight);
                } else {
                    throw new IllegalArgumentException("Fractions and colours must have equal number of elements");
                }
            } else {
                throw new IllegalArgumentException("Colours can't be null");
            }
        } else {
            throw new IllegalArgumentException("Fractions can't be null");
        }
        return color;
    }

    public static int[] getFractionIndicies(float[] fractions, float progress) {
        int[] range = new int[2];

        int startPoint = 0;
        while (startPoint < fractions.length && fractions[startPoint] <= progress) {
            startPoint++;
        }

        if (startPoint >= fractions.length) {
            startPoint = fractions.length - 1;
        }

        range[0] = startPoint - 1;
        range[1] = startPoint;

        return range;
    }

    public static Color blend(Color color1, Color color2, double ratio) {
        float r = (float) ratio;
        float ir = (float) 1.0 - r;

        float rgb1[] = new float[3];
        float rgb2[] = new float[3];

        color1.getColorComponents(rgb1);
        color2.getColorComponents(rgb2);

        float red = rgb1[0] * r + rgb2[0] * ir;
        float green = rgb1[1] * r + rgb2[1] * ir;
        float blue = rgb1[2] * r + rgb2[2] * ir;

        if (red < 0) {
            red = 0;
        } else if (red > 255) {
            red = 255;
        }
        if (green < 0) {
            green = 0;
        } else if (green > 255) {
            green = 255;
        }
        if (blue < 0) {
            blue = 0;
        } else if (blue > 255) {
            blue = 255;
        }

        Color color = null;
        try {
            color = new Color(red, green, blue);
        } catch (IllegalArgumentException exp) {
            NumberFormat nf = NumberFormat.getNumberInstance();
            System.out.println(nf.format(red) + "; " + nf.format(green) + "; " + nf.format(blue));
            exp.printStackTrace();
        }
        return color;
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
1

Hints

  1. use Swing Timer for slow motion effect
  2. Decrement values of RGB by a number, say x

Tadaaaa.. :-)

Update : if you wish, you can play around value of x.

Use Math.Random() function to generate pseudo-random values to x during execution

HovercraftFullOfEels &mKorbel, thank you for your inputs

Mukul Goel
  • 8,387
  • 6
  • 37
  • 77
  • A Swing Timer would work better and easier than threads for this. – Hovercraft Full Of Eels Nov 04 '12 at 21:31
  • @HovercraftFullOfEels : little idea about swings. My answer is based purely on my knowledge of java. But i am sure `Swings` would be having something for it. – Mukul Goel Nov 04 '12 at 21:35
  • 3
    If you used Thread.sleep without care, you could freeze the Swing event thread, freezing the entire application. If you used a background thread directly, then you would have to take care to make sure that all Swing calls were queued properly on the Swing event thread using `SwingUtilities.invokeLater(new Runnable() {...});`. Since this is a Swing related question, the best solution is to use the tools that Swing already has available for this sort of thing, a Swing Timer. – Hovercraft Full Of Eels Nov 04 '12 at 21:37
  • @HovercraftFullOfEels : i perfectly agree with you. If its a swings app and swings provide something for it. Its wise to use that. and yup threads can go nuts if not "Handled with care" lol – Mukul Goel Nov 04 '12 at 21:39
  • @Mukul Goel `wrote use threads.sleep to give a slow motion effect.`, not never, there are bunch of similair question, where Thread.sleep(int) doesn't works, for AWT Components could be way, but for Swing JComponents could be very contraproductive, could be good answer, but change that to the [Swing Timer](http://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html) – mKorbel Nov 04 '12 at 21:59
  • @mKorbel thank you . Dont know much about swings, but thanks for the information. I updated my post. – Mukul Goel Nov 04 '12 at 22:06
1

The easiest way would be to interpolate between each of the RGB values. This would be the same for all languages -- the python code would look like:

steps = 10

rgb1 = [ 'AA', '08', 'C3' ]
rgb2 = [ '03', '88', '1C' ]

h1 = map( lambda s: int( '0x'+s, 0 ), rgb1 )
h2 = map( lambda s: int( '0x'+s, 0 ), rgb2 )

inc = [0, 0, 0]
for i in range(0,3):
    inc[i] = ( h2[i] - h1[i] ) / ( steps - 1 )

for i in range(0,steps-1):
    print '<span style="background: #%02x%02x%02x"> &nbsp; %i &nbsp; </span>' % ( 
            h1[0] + i * inc[0],
            h1[1] + i * inc[1],
            h1[2] + i * inc[2],
            i+1 )

print '<span style="background: #%02x%02x%02x"> &nbsp; %i &nbsp; </span>' % ( 
        h2[0], h2[1], h2[2], steps )
SammyO
  • 63
  • 1
  • 6
  • here is a gist with the code and output: [gist.github.com/4014407](https://gist.github.com/4014407) – SammyO Nov 04 '12 at 23:58
0

You could linearly interpolate a transition from the start rgb color to the one you want to have in the end.

This means if e.g. you have rgb(255,255,0) as start color and rgb(50,50,50) as goal and you want to reach the final color in 5 steps you adapt by (-41 = (255-50)/5, -41, 10) in every step leading to the following colors:

rgb(255,255,  0)
rgb(214,214, 10)
rgb(173,173, 20)
rgb(132,132, 30)
rgb( 91, 91, 40)
rgb( 50, 50, 50)

This is called a linear gradient and would be very easy to implement but of course there exist various other techniques to do nice transitions between colors.

s1lence
  • 2,188
  • 2
  • 16
  • 34