I am trying to achieve a scenario where the color of an object changes slowly from one color to another color.
I have the initial color as targetColor and final col
I don't know if I followed your math instructions.
I created a Swing GUI so i could watch the transition from one color to another.
The Reset Colors button generates a random starting color and a random finishing color. The Start button transforms the bottom panel from the starting color to the finishing color. Each transformation moves about 5 values in either the red, green, or blue dimension, with a delay of 300 milliseconds.
I tended to watch the numbers change, rather than the color. I wanted to make sure that the transformation was fairly even.
Anyway, here's the code. You can use this to model other Swing GUI's. I put all the classes together in one file so I could paste the code here. You should separate the classes into separate files.
package com.ggl.testing;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ColorChangeTest implements Runnable {
private static final Insets normalInsets = new Insets(10, 10, 0, 10);
private Color currentColor;
private Color targetColor;
private ColorPanel changingColorPanel;
private ColorPanel currentColorPanel;
private ColorPanel targetColorPanel;
private JLabel changingLabel;
private JLabel currentLabel;
private JLabel targetLabel;
public static void main(String args[]) {
SwingUtilities.invokeLater(new ColorChangeTest());
}
@Override
public void run() {
JFrame frame = new JFrame("Color Change Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
mainPanel.add(createColorPanel(), BorderLayout.NORTH);
mainPanel.add(createChangingPanel(), BorderLayout.CENTER);
frame.add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createColorPanel() {
JPanel colorPanel = new JPanel();
setNewColors();
JPanel currentPanel = new JPanel();
currentPanel.setLayout(new BorderLayout());
JPanel currentLabelPanel = new JPanel();
currentLabelPanel.setLayout(new BorderLayout());
JLabel startLabel = new JLabel("Starting Color");
startLabel.setHorizontalAlignment(JLabel.CENTER);
currentLabelPanel.add(startLabel, BorderLayout.NORTH);
currentLabel = new JLabel(getColorString(currentColor));
currentLabel.setHorizontalAlignment(JLabel.CENTER);
currentLabelPanel.add(currentLabel, BorderLayout.SOUTH);
currentPanel.add(currentLabelPanel, BorderLayout.NORTH);
currentColorPanel = new ColorPanel(100, 100, currentColor);
currentPanel.add(currentColorPanel, BorderLayout.CENTER);
colorPanel.add(currentPanel);
JPanel targetPanel = new JPanel();
targetPanel.setLayout(new BorderLayout());
JPanel targetLabelPanel = new JPanel();
targetLabelPanel.setLayout(new BorderLayout());
JLabel finishLabel = new JLabel("Finishing Color");
finishLabel.setHorizontalAlignment(JLabel.CENTER);
targetLabelPanel.add(finishLabel, BorderLayout.NORTH);
targetLabel = new JLabel(getColorString(targetColor));
targetLabel.setHorizontalAlignment(JLabel.CENTER);
targetLabelPanel.add(targetLabel, BorderLayout.SOUTH);
targetPanel.add(targetLabelPanel, BorderLayout.NORTH);
targetColorPanel = new ColorPanel(100, 100, targetColor);
targetPanel.add(targetColorPanel, BorderLayout.CENTER);
colorPanel.add(targetPanel);
colorPanel.add(createButtonPanel());
return colorPanel;
}
private JPanel createButtonPanel() {
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new GridBagLayout());
int gridy = 0;
JButton resetButton = new JButton("Reset Colors");
resetButton.addActionListener(new ResetColorsListener(this));
addComponent(buttonPanel, resetButton, 0, gridy++, 1, 1, normalInsets,
GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL);
JButton startButton = new JButton("Start");
startButton.addActionListener(new ColorChangeListener(this));
addComponent(buttonPanel, startButton, 0, gridy++, 1, 1, normalInsets,
GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL);
return buttonPanel;
}
private JPanel createChangingPanel() {
JPanel changingPanel = new JPanel();
changingPanel.setLayout(new BorderLayout());
changingLabel = new JLabel(getColorString(currentColor));
changingLabel.setHorizontalAlignment(JLabel.CENTER);
changingPanel.add(changingLabel, BorderLayout.NORTH);
changingColorPanel = new ColorPanel(300, 200, currentColor);
changingPanel.add(changingColorPanel, BorderLayout.CENTER);
return changingPanel;
}
public void setChangingColorLabelText(Color color) {
changingLabel.setText(getColorString(color));
}
public void setNewColors() {
currentColor = getRandomColor();
targetColor = getRandomColor();
}
public void displayNewColors() {
currentLabel.setText(getColorString(currentColor));
targetLabel.setText(getColorString(targetColor));
changingLabel.setText(getColorString(currentColor));
currentColorPanel.setColor(currentColor);
targetColorPanel.setColor(targetColor);
changingColorPanel.setColor(currentColor);
}
public Color getCurrentColor() {
return currentColor;
}
public Color getTargetColor() {
return targetColor;
}
public ColorPanel getChangingColorPanel() {
return changingColorPanel;
}
private Color getRandomColor() {
return new Color((float) Math.random(), (float) Math.random(),
(float) Math.random());
}
private String getColorString(Color color) {
int r = color.getRed();
int g = color.getGreen();
int b = color.getBlue();
return "(" + r + ", " + g + ", " + b + ")";
}
private void addComponent(Container container, Component component,
int gridx, int gridy, int gridwidth, int gridheight, Insets insets,
int anchor, int fill) {
GridBagConstraints gbc = new GridBagConstraints(gridx, gridy,
gridwidth, gridheight, 1.0D, 1.0D, anchor, fill, insets, 0, 0);
container.add(component, gbc);
}
public class ColorPanel extends JPanel {
private static final long serialVersionUID = -2894328511698328096L;
private Color color;
public ColorPanel(int width, int height, Color color) {
this.color = color;
this.setPreferredSize(new Dimension(width, height));
}
public void setColor(Color color) {
this.color = color;
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
public class ResetColorsListener implements ActionListener {
private ColorChangeTest colorChangeTest;
public ResetColorsListener(ColorChangeTest colorChangeTest) {
this.colorChangeTest = colorChangeTest;
}
@Override
public void actionPerformed(ActionEvent event) {
colorChangeTest.setNewColors();
colorChangeTest.displayNewColors();
}
}
public class ColorChangeListener implements ActionListener {
private ColorChangeTest colorChangeTest;
public ColorChangeListener(ColorChangeTest colorChangeTest) {
this.colorChangeTest = colorChangeTest;
}
@Override
public void actionPerformed(ActionEvent event) {
ColorChange colorChange = new ColorChange(colorChangeTest);
new Thread(colorChange).start();
}
}
public class ColorChange implements Runnable {
private static final long sleepTime = 300L;
private double r, g, b, dr, dg, db;
private int tr, tg, tb, cr, cg, cb;
private ColorChangeTest colorChangeTest;
public ColorChange(ColorChangeTest colorChangeTest) {
this.colorChangeTest = colorChangeTest;
}
@Override
public void run() {
calculateColorChange();
sleep(sleepTime);
while (calculateNextColor()) {
sleep(sleepTime);
}
setColor(colorChangeTest.getTargetColor());
}
private void calculateColorChange() {
double increment = 5D;
// step 1
r = cr = colorChangeTest.getCurrentColor().getRed();
g = cg = colorChangeTest.getCurrentColor().getGreen();
b = cb = colorChangeTest.getCurrentColor().getBlue();
// step 2
tr = colorChangeTest.getTargetColor().getRed();
tg = colorChangeTest.getTargetColor().getGreen();
tb = colorChangeTest.getTargetColor().getBlue();
dr = tr - cr;
dg = tg - cg;
db = tb - cb;
// step 3
double d = Math.sqrt(dr * dr + dg * dg + db * db);
int steps = (int) (d / increment);
dr /= (double) steps;
dg /= (double) steps;
db /= (double) steps;
setColor(new Color(cr, cg, cb));
}
private boolean calculateNextColor() {
// step 5
r += dr;
g += dg;
b += db;
if (isFinished()) {
return false;
} else {
setColor(new Color(round(r), round(g), round(b)));
return true;
}
}
private boolean isFinished() {
return isColorFinished(cr, tr, round(r))
|| isColorFinished(cg, tg, round(g))
|| isColorFinished(cb, tb, round(b));
}
private int round(double value) {
return (int) Math.round(value);
}
private boolean isColorFinished(int original, int target, int current) {
boolean isFinished = false;
if (current < 0 || current > 255) {
isFinished = true;
} else if ((target >= original) && (current >= target)) {
isFinished = true;
} else if ((target <= original) && (current <= target)) {
isFinished = true;
}
return isFinished;
}
private void setColor(final Color color) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
colorChangeTest.getChangingColorPanel().setColor(color);
colorChangeTest.setChangingColorLabelText(color);
}
});
}
private void sleep(long sleepTime) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
}
}
}
}
Yea, instead of using the for loop in line
while(i<=10);
you can actually use your changing speed. Meanwhile your changingSpeed should
be an array of length 3 because the different offset of colors interval i.e
dr, dg, db so that they can be taking care of independently.
like this...
int [] changingSpeed(int []Offset){
int loop = 5;
// 5 means the color should be change 5 times
int [] changeSpeed = new int[3];
changeSpeed[0]= offset[0]/loop;
changeSpeed[1]= offset[1]/loop;
changeSpeed[2]= offset[2]/loop;
return changeSpeed;
}
// your update method will now look like this
updateColor(int [] changeSpeed) throws AWTException{
int dr = changeSpeed[0];
int dg = changeSpeed[1];
int db = changeSpeed[2];
Robot slow = new Robot();
int i=0;
int f= loop; // the number of time you want the color to change
while(i<=f){
slow.delay(1000)
//sleep will sleep for 1000ms
setColor(targetColor.getRed() + dr/10, targetColor.getGreen(),targetColor.getBlue());
setColor(targetColor.getRed(), targetColor.getGreen() + (dg/10),targetColor.getBlue());
setColor(targetColor.getRed(), targetColor.getGreen(),targetColor.getBlue() + db/10);
i++;
}
}
Here is an example the fades the background as you move from component to component:
import java.awt.*;
import java.awt.event.*;
import java.util.Hashtable;
import java.util.ArrayList;
import javax.swing.*;
public class Fader
{
// background color when component has focus
private Color fadeColor;
// steps to fade from original background to fade background
private int steps;
// apply transition colors at this time interval
private int interval;
// store transition colors from orginal background to fade background
private Hashtable backgroundColors = new Hashtable();
/*
* Fade from a background color to the specified color using
* the default of 10 steps at a 50 millisecond interval.
*
* @param fadeColor the temporary background color
*/
public Fader(Color fadeColor)
{
this(fadeColor, 10, 50);
}
/*
* Fade from a background color to the specified color in the
* specified number of steps at the default 5 millisecond interval.
*
* @param fadeColor the temporary background color
* @param steps the number of steps to fade in the color
*/
public Fader(Color fadeColor, int steps)
{
this(fadeColor, steps, 50);
}
/*
* Fade from a background color to the specified color in the
* specified number of steps at the specified time interval.
*
* @param fadeColor the temporary background color
* @param steps the number of steps to fade in the color
* @param intevral the interval to apply color fading
*/
public Fader(Color fadeColor, int steps, int interval)
{
this.fadeColor = fadeColor;
this.steps = steps;
this.interval = interval;
}
/*
* Add a component to this fader.
*
* The fade color will be applied when the component gains focus.
* The background color will be restored when the component loses focus.
*
* @param component apply fading to this component
*/
public Fader add(JComponent component)
{
// Get colors to be used for fading
ArrayList colors = getColors( component.getBackground() );
// FaderTimer will apply colors to the component
new FaderTimer( colors, component, interval );
return this;
}
/*
** Get the colors used to fade this background
*/
private ArrayList getColors(Color background)
{
// Check if the color ArrayList already exists
Object o = backgroundColors.get( background );
if (o != null)
{
return (ArrayList)o;
}
// Doesn't exist, create fader colors for this background
ArrayList colors = new ArrayList( steps + 1 );
colors.add( background );
int rDelta = ( background.getRed() - fadeColor.getRed() ) / steps;
int gDelta = ( background.getGreen() - fadeColor.getGreen() ) / steps;
int bDelta = ( background.getBlue() - fadeColor.getBlue() ) / steps;
for (int i = 1; i < steps; i++)
{
int rValue = background.getRed() - (i * rDelta);
int gValue = background.getGreen() - (i * gDelta);
int bValue = background.getBlue() - (i * bDelta);
colors.add( new Color(rValue, gValue, bValue) );
}
colors.add( fadeColor );
backgroundColors.put(background, colors);
return colors;
}
class FaderTimer implements FocusListener, ActionListener
{
private ArrayList colors;
private JComponent component;
private Timer timer;
private int alpha;
private int increment;
FaderTimer(ArrayList colors, JComponent component, int interval)
{
this.colors = colors;
this.component = component;
component.addFocusListener( this );
timer = new Timer(interval, this);
}
public void focusGained(FocusEvent e)
{
alpha = 0;
increment = 1;
timer.start();
}
public void focusLost(FocusEvent e)
{
alpha = steps;
increment = -1;
timer.start();
}
public void actionPerformed(ActionEvent e)
{
alpha += increment;
component.setBackground( (Color)colors.get(alpha) );
if (alpha == steps || alpha == 0)
timer.stop();
}
}
public static void main(String[] args)
{
// Create test components
JComponent textField1 = new JTextField(10);
textField1.setBackground( Color.YELLOW );
JComponent textField3 = new JTextField(10);
JComponent textField4 = new JTextField(10);
JComponent button = new JButton("Start");
JComponent checkBox = new JCheckBox("Check Box");
JFrame frame = new JFrame("Fading Background");
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().add(textField1, BorderLayout.NORTH );
frame.getContentPane().add(button, BorderLayout.SOUTH );
frame.getContentPane().add(textField3, BorderLayout.WEST );
frame.getContentPane().add(textField4, BorderLayout.EAST );
frame.getContentPane().add(checkBox);
// Gradual Fading (using defaults)
// Fader fader = new Fader( new Color(155, 255, 155) );
Fader fader = new Fader( new Color(155, 255, 155), 10, 50 );
fader.add( textField1 );
fader.add( textField3 );
fader.add( checkBox );
// Instant Fading
fader = new Fader( new Color(255, 155, 155), 1, 1 );
fader.add( textField4 );
fader.add( button );
frame.pack();
frame.setVisible( true );
}
}
It uses a Timer to update the backgrounds at the specified interval. It then just interpolates between the two color based on the number of steps desired.
In order to achieve the act of slowness, it might be a good idea to use the instance of Robot class.
//Method to obtain the offset of the color
static int [] getColorOffset(Color initial, Color final){
int [] colorOffset = new int[3];
colorOffset[0]= final.getRed()-initial.getRed();
colorOffset[1] = final.getGreen()-initial.getGreen();
colorOffset[2]= final.getBlue()-initial.getBlue();
return colorOffset;
}
updateColor(int [] colorOffset) throws AWTException{
int dr = colorOffset[0];
int dg = colorOffset[1];
int db = colorOffset[2];
Robot slow = new Robot();
int i=0;
while(i<=10){
slow.delay(1000)
//sleep will sleep for 1000ms
setColor(targetColor.getRed() + dr/10, targetColor.getGreen(),targetColor.getBlue());
setColor(targetColor.getRed(), targetColor.getGreen() + (dg/10),targetColor.getBlue());
setColor(targetColor.getRed(), targetColor.getGreen(),targetColor.getBlue() + db/10);
i++;
}
}
public static void main(String args[]) throws AWTException{
Color initial = Color.black;
Color final = Color,white;
int [] colorOffset = getColorOffset(initial, final);
updateColor(colorOffset);
}
Look at this example:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Test {
public static void main(String args[]) {
final JFrame frame = new JFrame();
frame.setBounds(100, 100, 300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// set some random initial color
final Component comp = frame.getContentPane();
comp.setBackground(new Color(
(float) Math.random(),
(float) Math.random(),
(float) Math.random()));
frame.setVisible(true);
final Timer timer = new Timer(50, new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
final Color targetColor = new Color(30,40,50);
final int changingSpeed = 5;
final Color currentColor = comp.getBackground();
// step 1
int r = currentColor.getRed();
int g = currentColor.getGreen();
int b = currentColor.getBlue();
// step 2
double dr = targetColor.getRed() - r;
double dg = targetColor.getGreen() - g;
double db = targetColor.getBlue() - b;
// step 3
double norm = Math.sqrt(dr*dr+dg*dg+db*db);
if (norm < .001) {
((Timer)(evt.getSource())).stop();
return;
}
dr /= norm;
dg /= norm;
db /= norm;
// step 4
dr *= Math.min(changingSpeed, norm);
dg *= Math.min(changingSpeed, norm);
db *= Math.min(changingSpeed, norm);
// step 5
r += dr;
g += dg;
b += db;
comp.setBackground(new Color(r,g,b));
frame.repaint();
}
});
timer.start();
}
}
A few things to note:
Use a timer to fire the updates. This ensures, they are done in the EventThread, what is mandatory for manipulating Swing GUIs.
Test the norm for beeing very small. This means, your delta is near zero and you should stop updating the color.
Use the minimum of the norm and your changing speed. If your changing speed is high enough, your color will alternate around the target color and your delta will swap the sign forever and the process doesn't terminate. So if your delta is smaller than your changing speed, use the delta!
Don't forget to call repaint, when you are done with manipulating the color.
I wouldnt rely on a specific delay time to compute the next step in an animation as this will certainly provide different execution times in different machines, although it might not be a relevant difference.
Instead of a changing speed factor your could use a long representing the animation total time and use a Thread
(or another multithreading mecanism) to control the animation lifecycle (such as calculating how much time has passed since the last repaint and whats the percentage of completion for the next iteration).