Home > Enterprise >  Gradiently shifting colors in Swing?
Gradiently shifting colors in Swing?

Time:03-06

So I add 8 TextFields and wanna set their background colors. My idea is to set the first one to red (255, 0, 0) the last one to blue (0, 0, 255) and the 8 (or any number actually) others gradient between these. I'm trying to figure out how to solve it in terms of "If the 'next' variable is 0 increase this variable with same amount as previous variable is decreasing with"

So it could look like in each iteration:

setBackground(255, 0, 0);
setBackground(191, 63, 0);
setBackground(127, 127, 0);
...
setBackground(0, 0, 255); 

Now I wanna try and fit this way of increase and decreasing into a for loop that will iterate n times where n is number of TextFields (now 8 for simplicity). Anyone know if there's a clever solution to this?

MRE:

import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class Apple{
    
    public Apple(int width, int height) {
        SwingUtilities.invokeLater(() -> initGUITest(width, height));
    }
    
    public void initGUITest(int width, int height) {
        JFrame frame = new JFrame();
        frame.setSize(width, height);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setVisible(true);
        
        JPanel panel = new JPanel();
        GridLayout gl = new GridLayout(10, 1);
        panel.setLayout(gl);
        
        frame.add(panel);
        
        for(int i = 0; i < 8; i  ) {
            JTextField jtf = new JTextField("Track "   (i   1));
            jtf.setBackground(new Color(255, 0, 0)); //Start color
            panel.add(jtf);
        }
        
    }
    
    public static void main(String args[]) {
        Apple a = new Apple(300, 300);
    }
}

CodePudding user response:

Checking if an value is zero and incrementing or decrementing a value from there is inefficient.

There are 2 ways to go about this

Linear

you calculate an blend value which is (i/stepSize) and use that to linearly interpolate between the start and end value as follows

   intermediateColor[0]=(int)(start.getRed() (end.getRed()-start.getRed())*alpha);
   intermediateColor[1]=(int)(start.getGreen() (end.getGreen()-start.getGreen())*alpha);
   intermediateColor[2]=(int)(start.getBlue() (end.getBlue()-start.getBlue())*alpha);

conversion of blend to float is necessary for interpolation to work here is logic

 private static void layout1(JFrame frame)
 {
  Color
  start=Color.RED,
  end=Color.BLUE;
  
  int[] intermediateColor=new int[3];
  
  int steps=8;
  float alpha;
  
  for(int i=0;i<=steps;i  )
  {
   JTextField field=new JTextField(10);
   
   alpha=((float)i/steps);
   
   intermediateColor[0]=(int)(start.getRed() (end.getRed()-start.getRed())*alpha);
   intermediateColor[1]=(int)(start.getGreen() (end.getGreen()-start.getGreen())*alpha);
   intermediateColor[2]=(int)(start.getBlue() (end.getBlue()-start.getBlue())*alpha);
  
   field.setBackground(new Color(intermediateColor[0],intermediateColor[1],intermediateColor[2]));
   
   frame.add(field);
  }
 }

Output :

enter image description here

KeyFrames

An more complicated example involves using key frames where you dynamically change the start and end points based on your i value

Here are the keyframes

 int[] checkPoints={0,2,4,6,8};
 Color[] colors={Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW,Color.MAGENTA};

what this means is that for

checkboxes 0->2 interpolate between RED & GREEN

checkboxes 3->4 interpolate between GREEN & BLUE

checkboxes 5->6 interpolate between BLUE & YELLOW

checkboxes 7->8 interpolate between YELLOW & MAGENTA

The logic lies in this code

 //loop through all checkpoints
  for(int j=0;j<checkPoints.length-1;j  )
   {
   //check if i lies in between these 2 checkpoints
    if(i>=checkPoints[j] && i<=checkPoints[j 1])
    {
      //interpolate between this & the next checkpoint
     checkPoint=j;
     start=colors[checkPoint];
     end=colors[checkPoint 1];
     //distance of i from start checkpoint/ total distance between checkpoints
     alpha=(float)(i-checkPoints[checkPoint])/(checkPoints[checkPoint 1]-checkPoints[checkPoint]);
    }
   } 

Here is the full code

public class Test 
{
 public static void main(String[] args) 
 {
  JFrame frame=new JFrame("TEST");
  
  frame.setContentPane(new JPanel(new FlowLayout(FlowLayout.LEADING,10,0)));
  
  layout2(frame);
  
  frame.pack();
  
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  
  frame.setVisible(true);
 }
 
 private static void layout1(JFrame frame)
 {
  Color
  start=Color.RED,
  end=Color.BLUE;
  
  int[] intermediateColor=new int[3];
  
  int steps=8;
  float alpha;
  
  for(int i=0;i<=steps;i  )
  {
   JTextField field=new JTextField(10);
   
   alpha=((float)i/steps);
   
   intermediateColor[0]=(int)(start.getRed() (end.getRed()-start.getRed())*alpha);
   intermediateColor[1]=(int)(start.getGreen() (end.getGreen()-start.getGreen())*alpha);
   intermediateColor[2]=(int)(start.getBlue() (end.getBlue()-start.getBlue())*alpha);
  
   field.setBackground(new Color(intermediateColor[0],intermediateColor[1],intermediateColor[2]));
   
   frame.add(field);
  }
 }
 
 private static void layout2(JFrame frame)
 {
  int[] checkPoints={0,2,4,6,8};
  Color[] colors={Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW,Color.MAGENTA};
  
  int[] intermediateColor=new int[3];
  
  int steps=8;
  int checkPoint;
  float alpha=0;
  Color start=null,end=null;
  
  for(int i=0;i<=steps;i  )
  {
   JTextField field=new JTextField(10);
   
   for(int j=0;j<checkPoints.length-1;j  )
   {
    if(i>=checkPoints[j] && i<=checkPoints[j 1])
    {
     checkPoint=j;
     start=colors[checkPoint];
     end=colors[checkPoint 1];
     alpha=(float)(i-checkPoints[checkPoint])/(checkPoints[checkPoint 1]-checkPoints[checkPoint]);
    }
   } 
  
   
   intermediateColor[0]=(int)(start.getRed() (end.getRed()-start.getRed())*alpha);
   intermediateColor[1]=(int)(start.getGreen() (end.getGreen()-start.getGreen())*alpha);
   intermediateColor[2]=(int)(start.getBlue() (end.getBlue()-start.getBlue())*alpha);
  
   field.setBackground(new Color(intermediateColor[0],intermediateColor[1],intermediateColor[2]));
   
   frame.add(field);
  }
 }
}

Output :

enter image description here

CodePudding user response:

So, any number of ways you might do this, but for me, personally, I'd look towards using some kind of "blending" algorithm which would allow you to establish the "range" of colors you want and then based on some value (ie a index or percentage), generate a color which is blend of those colors (within the range).

For example...

enter image description here

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class Main {

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

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            ColorBlender blender = new ColorBlender(new float[] {0, 1}, new Color[] {Color.RED, Color.BLUE});
            for (int index = 0; index < 8; index  ) {
                Color color = blender.blendedColorAt(index / 7f);
                System.out.println(color);
                JTextField textField = new JTextField(10);
                textField.setBackground(color);
                add(textField, gbc);
            }
        }

    }

    public class ColorBlender {

        private float[] fractions;
        private Color[] colors;

        public ColorBlender(float[] fractions, Color[] colors) {
            this.fractions = fractions;
            this.colors = colors;
        }

        public Color blendedColorAt(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;
        }

        protected 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;
        }

        protected 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;
        }
    }
}

Okay, but I want to blend between three colors

Okay, not an issue. Simply add another "stop" and the color for that stop, for example...

ColorBlender blender = new ColorBlender(new float[] {0f, 0.5f, 1f}, new Color[] {Color.RED, Color.YELLOW, Color.BLUE});

will produce...

enter image description here

Want to add more fields? No worries, just change Color color = blender.blendedColorAt(index / 7f); to so that 7f becomes the number of expected fields - 1 (remember, we're starting the index at 0

  • Related