Gradiently shifting colors in Swing? - java

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

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 :
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 :

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...
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...
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 😉)

Here is a class that can generate a series of colors that can transition from one color to another for a given number of steps.
Simple usage would be:
ColorTransition ct = new ColorTransition(Color.RED, Color.BLUE, 8);
If you need multiple transitions you could do:
ColorTransition ct = new ColorTransition(Color.RED, Color.BLUE, 8);
ct.transitionTo(Color.GREEN, 4);
which would then transition from BLUE to GREEN.
Once all the transition colors are generated you can access them separately:
import java.awt.*;
import javax.swing.*;
import java.util.*;
public class ColorTransition
{
private ArrayList<Color> colors;
public ColorTransition(Color startColor, Color endColor, int steps)
{
colors = new ArrayList<>(steps);
colors.add( startColor );
transitionTo(endColor, steps);
}
public void transitionTo(Color endColor, int steps)
{
Color startColor = colors.get(colors.size() - 1);
float rDelta = endColor.getRed() - startColor.getRed();
float gDelta = endColor.getGreen() - startColor.getGreen();
float bDelta = endColor.getBlue() - startColor.getBlue();
for (int i = 1; i < steps; i++)
{
float stepIncrement = (float)i / (steps - 1);
int rValue = (int)(startColor.getRed() + (rDelta * stepIncrement));
int gValue = (int)(startColor.getGreen() + (gDelta * stepIncrement));
int bValue = (int)(startColor.getBlue() + (bDelta * stepIncrement));
Color color = new Color(rValue, gValue, bValue);
colors.add( color );
}
}
public int size()
{
return colors.size();
}
public Color getColorAt(int index)
{
return colors.get( index );
}
private static void createAndShowGUI()
{
ColorTransition ct = new ColorTransition(Color.RED, Color.BLUE, 8);
// ct.transitionTo(Color.GREEN, 4);
JPanel panel = new JPanel( new GridLayout(0, 1) );
for (int i = 0; i < ct.size(); i++)
{
JTextField textField = new JTextField(25);
textField.setBackground( ct.getColorAt(i) );
panel.add( textField );
}
JFrame frame = new JFrame("Color Transition");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args) throws Exception
{
java.awt.EventQueue.invokeLater( () -> createAndShowGUI() );
}
}

Related

Modify color array java

Attatched are 2 methods from code that prints out a gird of 25 boxes in jframe and when you click the space bar the boxes fill up one by one with random colors. I need to modify the code so the first 13 boxes show up in shades of red and the next 12 show up only in shades of blue. Also, I need to change the background color to a random color. Thanks.
public void setColor()
{
int r = (int) (Math.random()*256);
int g = (int) (Math.random()*256);
int b = (int) (Math.random()*256);
myColors[clicked] = new Color(r,g,b);
}
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.black);
counter = 0;
for (int x = 0; x < myFloor.length; x++)
{
for (int y = 0; y < myFloor[0].length; y++)
{
if (myColors[counter] != null)
{
for (int i = 0; i < counter+1; i++)
{
setColor();
g2.setColor(myColors[i]);
g2.fill(myFloor[x][y]);
}
}
else
{
g2.setColor(Color.BLACK);
g2.draw(myFloor[x][y]);
}
counter++;
}
}
}
I need to modify the code so the first 13 boxes show up in shades of red and the next 12 show up only in shades of blue. Also, I need to change the background color to a random color. Thanks.
You could...
Use a "color blending" algorithm, which could blend a range of colors together.
The following example basically constructs a color range of dark to light red, light blue to dark blue, split over a normalised range of 0-51% and 52-100%
This has a neat side effect of filling the first 13 squares with shades of red and the last 12 with shades of blue.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JPanel;
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 {
private int cols = 5;
private int rows = 5;
private int cellSize = 50;
private ColorGradient colorGradient;
public TestPane() {
colorGradient = new ColorGradient(
new float[]{0f, 0.51f, 0.52f, 1f},
new Color[]{Color.RED.darker(), Color.RED.brighter(), Color.BLUE.brighter(), Color.BLUE.darker()}
);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(cols * cellSize, rows * cellSize);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Color borderColor = new Color(0, 0, 0, 64);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
float progress = ((row * rows) + col) / (float)(rows * cols);
g2d.setColor(colorGradient.colorAt(progress));
g2d.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
g2d.setColor(borderColor);
g2d.drawRect(col * cellSize, row * cellSize, cellSize, cellSize);
}
}
g2d.dispose();
}
}
public class ColorGradient {
private float[] fractions;
private Color[] colors;
public ColorGradient(float[] fractions, Color[] colors) {
this.fractions = fractions;
this.colors = colors;
}
public Color colorAt(float progress) {
Color color = null;
if (fractions != null) {
if (colors != null) {
if (fractions.length == colors.length) {
int[] indicies = getFractionIndicies(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 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;
}
}
}
Oh, and if you want to generate a random color, you could simply use something like...
Random rnd = new Random();
Color color = new Color(rnd.nextInt(255), rnd.nextInt(255), rnd.nextInt(255));
You could...
Define the red and blue color bands separately, and then, based on the cell index, decide which band you're going to use.
This provides more "absolute" control over the decision making process. For example, the following index allows the cells between 0-12 inclusively to draw colors from the red band and 13-24 from blue band.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JPanel;
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 Range {
private int lower;
private int upper;
public Range(int lower, int upper) {
this.lower = lower;
this.upper = upper;
}
public int getLower() {
return lower;
}
public int getUpper() {
return upper;
}
public boolean contains(int value) {
return value >= getLower() && value <= getUpper();
}
public int getDistance() {
return getUpper() - getLower();
}
public float normalised(int value) {
return (value - getLower()) / (float)getDistance();
}
}
public class TestPane extends JPanel {
private int cols = 5;
private int rows = 5;
private int cellSize = 50;
private ColorBand redColorBand;
private ColorBand blueColorBand;
private Range redRange = new Range(0, 12);
private Range blueRange = new Range(13, 24);
public TestPane() {
redColorBand = new ColorBand(
new float[]{0f, 1f},
new Color[]{Color.RED.darker(), Color.RED.brighter()}
);
blueColorBand = new ColorBand(
new float[]{0f, 1f},
new Color[]{Color.BLUE.brighter(), Color.BLUE.darker()}
);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(cols * cellSize, rows * cellSize);
}
protected Color colorForSqaure(int index) {
if (redRange.contains(index)) {
return redColorBand.colorAt(redRange.normalised(index));
} else if (blueRange.contains(index)) {
return blueColorBand.colorAt(blueRange.normalised(index));
}
return Color.BLACK;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Color borderColor = new Color(0, 0, 0, 64);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
g2d.setColor(colorForSqaure(((row * rows) + col)));
g2d.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
g2d.setColor(borderColor);
g2d.drawRect(col * cellSize, row * cellSize, cellSize, cellSize);
}
}
g2d.dispose();
}
}
public class ColorBand {
private float[] fractions;
private Color[] colors;
public ColorBand(float[] fractions, Color[] colors) {
this.fractions = fractions;
this.colors = colors;
}
public Color colorAt(float progress) {
Color color = null;
if (fractions != null) {
if (colors != null) {
if (fractions.length == colors.length) {
int[] indicies = getFractionIndicies(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 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;
}
}
}
You could...
Predefine the colors up-front
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
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 {
private int cols = 5;
private int rows = 5;
private int cellSize = 50;
private Color[] colors = new Color [] {
new Color(64, 0, 0),
new Color(79, 0, 0),
new Color(94, 0, 0),
new Color(109, 0, 0),
new Color(124, 0, 0),
new Color(139, 0, 0),
new Color(154, 0, 0),
new Color(169, 0, 0),
new Color(184, 0, 0),
new Color(199, 0, 0),
new Color(214, 0, 0),
new Color(229, 0, 0),
new Color(244, 0, 0),
new Color(0, 0, 64),
new Color(0, 0, 80),
new Color(0, 0, 96),
new Color(0, 0, 112),
new Color(0, 0, 128),
new Color(0, 0, 144),
new Color(0, 0, 160),
new Color(0, 0, 176),
new Color(0, 0, 192),
new Color(0, 0, 208),
new Color(0, 0, 224),
new Color(0, 0, 240),
};
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(cols * cellSize, rows * cellSize);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Color borderColor = new Color(0, 0, 0, 64);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
g2d.setColor(colors[((row * rows) + col)]);
g2d.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
g2d.setColor(borderColor);
g2d.drawRect(col * cellSize, row * cellSize, cellSize, cellSize);
}
}
g2d.dispose();
}
}
}

Best way to fill shapes in Java (no recursion)

I have a BufferedImage whit some shapes drawn down on it, I'm trying to fill those whit a random color; I have made a method whit recursion to do this job, and it does it, but:
It's painfully slow
It requires a lot of stack memory (I had to scale it up to 1GB to avoid problems)
I heard that is always possible to avoid recursion but I can't come up whit a more efficient way to do it, any help would be appreciated
The method returns an ArrayList of int[] which represent all the pixel that I have to fill (then I color those pixel by pixel) It start from a random point that hasn't be colored yet.
I would like to have a result similar to the one that we can have using the instrument "fill" on Microsoft Paint
Here's the code of the method that find all points in a section:
ArrayList<int[]> populatesSection(ArrayList<int[]> section, int[] is) {
int[][] neighbors=new int[4][2];
int i;
neighbors[0][0]=is[0];
neighbors[0][1]=is[1]+1;
neighbors[1][0]=is[0]-1;
neighbors[1][1]=is[1];
neighbors[2][0]=is[0];
neighbors[2][1]=is[1]-1;
neighbors[3][0]=is[0]+1;
neighbors[3][1]=is[1];
toBeColored.remove(contains(toBeColored, is));
section.add(is);
for(i=0;i<neighbors.length;i++)
if(isValid(neighbors[i]) && contains(toBeColored, neighbors[i])>=0 && contains(section, neighbors[i])<0)
populatesSection(section, neighbors[i]);
return section;
}
initial BufferedImage:
colored BufferedImage:
I borrowed the flood method from camickr'a answer and enhanced it to auto flood fill the entire image.
I also took the long flood-related calculations off the EDT by performing it on a SwingWorker:
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.*;
import javax.imageio.ImageIO;
import javax.swing.*;
public class FloodFill extends JPanel {
private final BufferedImage image;
private static final Color background = Color.WHITE;
private final Random rnd = new Random();
FloodFill(BufferedImage image) {
this.image = image;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(image.getWidth(), image.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0,0, this);
}
private void autoFloodFill(){
//take long process off the EDT by delegating it to a worker
new FloodFillWorker().execute();
}
private Optional<Point> findBackgrounPoint() {
int backgroundRGB = background.getRGB();
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
int imageRGB = image.getRGB(x, y);
if(imageRGB == backgroundRGB)
return Optional.of(new Point(x, y));
}
}
return Optional.empty();
}
private Color randomColor() {
return new Color(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
}
class FloodFillWorker extends SwingWorker<Void, Void>{
//todo set sleep to 0
private final long SLEEP = 500; //used to slow sown for demo purposed.
#Override
protected Void doInBackground() throws Exception {
Optional<Point> backgroundPoint = findBackgrounPoint();
while(backgroundPoint.isPresent()){
floodFill(backgroundPoint.get(), randomColor());
SwingUtilities.invokeLater(()-> repaint()); //invoke repaint on EDT
backgroundPoint = findBackgrounPoint(); //find next point
Thread.sleep(SLEEP);
}
return null;
}
private void floodFill(Point point, Color replacement) {
int width = image.getWidth();
int height = image.getHeight();
int targetRGB = image.getRGB(point.x, point.y);
int replacementRGB = replacement.getRGB();
Queue<Point> queue = new LinkedList<>();
queue.add( point );
while (!queue.isEmpty()){
Point p = queue.remove();
int imageRGB = image.getRGB(p.x, p.y);
if (imageRGB != targetRGB) { continue; }
//Update the image and check surrounding pixels
image.setRGB(p.x, p.y, replacementRGB);
if (p.x > 0) {
queue.add( new Point(p.x - 1, p.y) );
}
if (p.x +1 < width) {
queue.add( new Point(p.x + 1, p.y) );
}
if (p.y > 0) {
queue.add( new Point(p.x, p.y - 1) );
}
if (p.y +1 < height) {
queue.add( new Point(p.x, p.y + 1) );
}
}
}
}
public static void main(String[] args) throws Exception {
String imageAdress = "https://i.stack.imgur.com/to4SE.png";
BufferedImage image = ImageIO.read(new URL(imageAdress));
FloodFill ff = new FloodFill(image);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(ff);
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
ff.autoFloodFill();
}
}
A slowed down demo:
Run it on line
Here is a flood fill method I found on the web a long time ago:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.LinkedList;
import java.util.Queue;
import javax.imageio.ImageIO;
import javax.swing.*;
public class ImageUtil
{
public static void floodFill(BufferedImage image, Point point, Color target, Color replacement)
{
int width = image.getWidth() - 1;
int height = image.getHeight() - 1;
int targetRGB = target.getRGB();
int replacementRGB = replacement.getRGB();
Queue<Point> queue = new LinkedList<Point>();
queue.add( point );
while (!queue.isEmpty())
{
Point p = queue.remove();
int imageRGB = image.getRGB(p.x, p.y);
Color imageColor = new Color(imageRGB);
if (imageRGB != targetRGB) continue;
// Update the image and check surrounding pixels
image.setRGB(p.x, p.y, replacementRGB);
if (p.x > 0) queue.add( new Point(p.x - 1, p.y) );
if (p.x < width) queue.add( new Point(p.x + 1, p.y) );
if (p.y > 0) queue.add( new Point(p.x, p.y - 1) );
if (p.y < height) queue.add( new Point(p.x, p.y + 1) );
}
}
public static void main(String[] args)
throws Exception
{
if (args.length != 1) {
System.err.println("ERROR: Pass filename as argument.");
return;
}
String fileName = args[0];
BufferedImage image = ImageIO.read( new File( fileName ) );
JLabel north = new JLabel( new ImageIcon( fileName ) );
JLabel south = new JLabel( new ImageIcon( image ) );
north.addMouseListener( new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e)
{
try
{
BufferedImage image = ImageIO.read( new File( fileName ) );
int rgb = image.getRGB(e.getX(), e.getY());
Color target = new Color( rgb );
floodFill(image, e.getPoint(), target, Color.ORANGE);
south.setIcon( new ImageIcon(image) );
}
catch (Exception e2) {}
}
});
JLabel label = new JLabel("Click on above image for flood fill");
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(north, BorderLayout.NORTH);
frame.add(label);
frame.add(south, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
}
I'll let you decide if it is any more efficient.
You'd typically use a Queue to implement a flood fill, seeded each time you detect a background (in your case white) pixel during a raster scan of the image.
static int[] xd = {-1, 0, 1, 0};
static int[] yd = { 0, 1, 0, -1};
static BufferedImage colorImage(BufferedImage im, Color background, Color[] colors) throws Exception
{
im = ensureImageType(im, BufferedImage.TYPE_INT_ARGB);
int width = im.getWidth();
int height = im.getHeight();
int[] pix = ((DataBufferInt)im.getRaster().getDataBuffer()).getData();
Queue<Integer> q = new LinkedList<>();
for (int i = 0, r = 0; i < width * height; i++)
{
if(pix[i] == background.getRGB())
{
q.add(i);
pix[i] = colors[r++ % colors.length].getRGB();
while(!q.isEmpty())
{
int pos = q.poll();
int x = pos % width;
int y = pos / width;
for(int j = 0; j < 4; j++)
{
int xn = x + xd[j];
if(xn >= 0 && xn < width)
{
int yn = y + yd[j];
if(yn >= 0 && yn < height)
{
int npos = yn * width + xn;
if(pix[npos] == background.getRGB())
{
q.add(npos);
pix[npos] = pix[i];
}
}
}
}
}
}
}
return im;
}
With the helper class:
static BufferedImage ensureImageType(BufferedImage im, int imageType)
{
if (im.getType() != imageType)
{
BufferedImage nim = new BufferedImage(im.getWidth(), im.getHeight(), imageType);
Graphics g = nim.getGraphics();
g.drawImage(im, 0, 0, null);
g.dispose();
im = nim;
}
return im;
}
Test:
Color[] colors = {Color.BLUE, Color.RED, Color.GREEN, Color.ORANGE,
Color.PINK, Color.CYAN, Color.MAGENTA, Color.YELLOW};
BufferedImage im = ImageIO.read(new File("to4SE.png"));
im = colorImage(im, Color.WHITE, colors);
ImageIO.write(im, "png", new File("color.png"));
Output:

Why are JLabels being painted over higher components in a JLayeredPane?

I have a JLayeredPane that has four layers:
JPanel set as a background
Grid of JPanels each holding a JLabel
Grid of JPanels each holding several JLabels that are only set to visible if the label in the panel below is empty
A custom component that is only used to override the paintComponent() method to draw over everything below
For some reason if I change the background colour of the labels in layer 3 and then draw to layer 4, the labels in layer 3 are painted over the graphics painted in level 4. I have tried to set ignoreRepaint() on various components as well as playing around with the opacity and code structure but all to no avail.
Does anyone know how to prevent this from happening?
I won't attach the source code because the project is quite large but I've attached an example that runs as a stand alone program and demonstrates my problem when you hit the "add arrow" button.
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
public class GraphicsTest {
#SuppressWarnings("serial")
class Painter extends JComponent {
public Painter(int x, int y) {
setBounds(0, 0, x, y);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
}
private static final int CELL_SIZE = 40;
private static final int NOTE_SIZE = 20;
private JFrame frame;
private static JButton test;
private static JButton clear;
private static JLayeredPane pane = new JLayeredPane();
private static JPanel back = new JPanel();
private static JPanel[][] cellPanels = new JPanel[10][10];
private static JLabel[][] cells = new JLabel[10][10];
private static JPanel[][] notePanels = new JPanel[10][10];
private static JLabel[][][] notes = new JLabel[10][10][4];
private static Painter painter;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
GraphicsTest window = new GraphicsTest();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public GraphicsTest() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setSize(600, 700);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
pane.setBounds(50, 50, 500, 500);
pane.setLayout(null);
frame.getContentPane().add(pane);
back.setBounds(0, 0, 500, 500);
back.setBackground(Color.BLACK);
pane.add(back, new Integer(100));
for (int i = 0; i < 10; i++) {
for (int k = 0; k < 10; k++) {
String text = "";
if ((i % 2) == 1 && (k % 2) == 1) text = (i + k) + "";
cellPanels[i][k] = new JPanel();
cellPanels[i][k].setBounds(k * CELL_SIZE, i * CELL_SIZE, CELL_SIZE, CELL_SIZE);
cellPanels[i][k].setBackground(Color.WHITE);
cellPanels[i][k].setBorder(new LineBorder(Color.BLACK, 1));
cells[i][k] = new JLabel(text);
cells[i][k].setBounds(0, 0, CELL_SIZE, CELL_SIZE);
cells[i][k].setOpaque(false);
cellPanels[i][k].add(cells[i][k]);
pane.add(cellPanels[i][k], new Integer(200));
}
}
boolean display;
for (int i = 0; i < 10; i++) {
for (int k = 0; k < 10; k++) {
if ((i % 2) == 0 && (k % 2) == 0) {
display = true;
} else {
display = false;
}
notePanels[i][k] = new JPanel();
notePanels[i][k].setBounds(k * CELL_SIZE, i * CELL_SIZE, CELL_SIZE, CELL_SIZE);
notePanels[i][k].setBackground(Color.WHITE);
notePanels[i][k].setBorder(new LineBorder(Color.BLACK, 1));
notePanels[i][k].setLayout(null);
for (int m = 0; m < 2; m++) {
for (int p = 0; p < 2; p++) {
notes[i][k][(m * 2) + p] = new JLabel(30 + "");
notes[i][k][(m * 2) + p].setBounds(m * NOTE_SIZE, p * NOTE_SIZE, NOTE_SIZE, NOTE_SIZE);
notes[i][k][(m * 2) + p].setOpaque(true);
notePanels[i][k].add(notes[i][k][(m * 2) + p]);
}
}
if (display) {
notePanels[i][k].setVisible(true);
} else {
notePanels[i][k].setVisible(false);
}
pane.add(notePanels[i][k], new Integer(300));
}
}
painter = new Painter(500, 500);
pane.add(painter, new Integer(400));
test = new JButton("Add Arrow");
test.setBounds(50, 600, 100, 25);
test.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
highlightNotes();
Arrow.drawArrow(painter.getGraphics(), 20, 20, 400, 400, 20, 30, 40, Color.BLACK, Color.GREEN);
}
});
frame.getContentPane().add(test);
clear = new JButton("Clear");
clear.setBounds(175, 600, 100, 25);
clear.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
painter.repaint();
}
});
frame.getContentPane().add(clear);
}
private static void highlightNotes() {
for (int i = 0; i < 10; i++) {
for (int k = 0; k < 10; k++) {
for (int n = 0; n < 4; n++) {
notes[i][k][n].setBackground(Color.BLUE);
}
}
}
}
static class Arrow {
public static void drawArrow(Graphics g, int tailx, int taily, int headx, int heady,
int shaftw, int headw, int headh, Color outline, Color fill) {
if ((shaftw % 2) == 0) {
shaftw--;
}
if ((headw % 2) == 0) {
headw--;
}
if ((headh % 2) == 0) {
headh--;
}
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
double length = Math.sqrt((double) (((headx - tailx) * (headx - tailx))
+ ((heady - taily) * (heady - taily))));
int tailLength = (int) (length - headw) + 1;
double theta = Math.atan2(heady - taily, headx - tailx);
Point point1 = new Point(0, -(shaftw / 2));
point1 = getTransPoint(point1, theta);
point1.x += tailx;
point1.y += taily;
Point point2 = new Point(tailLength, -(shaftw / 2));
point2 = getTransPoint(point2, theta);
point2.x += tailx;
point2.y += taily;
Point point3 = new Point(tailLength, -(headw / 2));
point3 = getTransPoint(point3, theta);
point3.x += tailx;
point3.y += taily;
Point point4 = new Point((int) length, 0);
point4 = getTransPoint(point4, theta);
point4.x += tailx;
point4.y += taily;
Point point5 = new Point(tailLength, (headw / 2));
point5 = getTransPoint(point5, theta);
point5.x += tailx;
point5.y += taily;
Point point6 = new Point(tailLength, (shaftw / 2));
point6 = getTransPoint(point6, theta);
point6.x += tailx;
point6.y += taily;
Point point7 = new Point(0, (shaftw / 2));
point7 = getTransPoint(point7, theta);
point7.x += tailx;
point7.y += taily;
//Create arrow at tail coordinates passed in
Polygon arrow = new Polygon();
arrow.addPoint(point1.x, point1.y);
arrow.addPoint(point2.x, point2.y);
arrow.addPoint(point3.x, point3.y);
arrow.addPoint(point4.x, point4.y);
arrow.addPoint(point5.x, point5.y);
arrow.addPoint(point6.x, point6.y);
arrow.addPoint(point7.x, point7.y);
//Draw and fill the arrow
g2.setColor(fill);
g2.fillPolygon(arrow);
g2.setColor(outline);
g2.drawPolygon(arrow);
}
private static Point getTransPoint(Point point, double theta) {
int x = (int) ((point.x * Math.cos(theta)) - (point.y * Math.sin(theta)));
int y = (int) ((point.y * Math.cos(theta)) + (point.x * Math.sin(theta)));
return new Point(x, y);
}
}
}

Is it possible to choose between two background colors for JFrame without creating a variable and assigning a random number of 0 or 1 to it?

I am new to java. I have a guessing number game homework. The user inputs a guess and the JLabel on JFrame shows if the guess is too high or too low or is it correct. The background color for the JFrame should change to either red or blue when a guess is entered. I know how to change it to one color but is there any way I can pick a color between red or blue without using math.random to assign 0 or 1 to a variable and then using if else statements? Thank you.
Your questions a little vague, so I'm not sure if this will meet your requirements, but...
What you could do is set up a blending process, so you would have "worst" and "best" case scenarios, for example, red for way below, blue for way above and green for spot on. Then based on the distance that the user was away from your guess, you could generate a blend of the two colors (worse-good-worse, based on direction), for example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ColorBlend {
public static void main(String[] args) {
new ColorBlend();
}
public ColorBlend() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
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;
add(new JLabel("Enter a value between 1-100 (press enter)"), gbc);
JTextField field = new JTextField(4);
field.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Colors to be used
Color[] colors = new Color[]{Color.RED, Color.GREEN, Color.BLUE};
// The ratio at which the colors applied across the band...
float[] ratios = new float[]{0f, 0.5f, 1f};
String text = field.getText();
try {
int value = Integer.parseInt(text);
if (value >= 1 && value <= 100) {
float percentage = value / 100f;
// Get the color that best meets out needs
Color color = blendColors(ratios, colors, percentage);
setBackground(color);
} else {
field.setText("Out of range");
field.selectAll();
}
} catch (NumberFormatException exp) {
field.setText("Bad Value");
field.selectAll();
}
}
});
add(field, gbc);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 200);
}
}
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;
}
}

Set Color dynamically based on value Java Swing

I am using Java Swing. I would like to display colors based on a double values which I compute.
Edit - I need to fill the color of a Path2D object. Currently, this is how I do it
Path2D shape;
// some code here
g2d.setColor(Color.Red);
g2d.fill(shape);
Now I do not want the color to be fixed as Color.Red but need to set it based on a value I compute. The double value can be negative or positive. The more negative the value is, the darker the color should be. The color need not be red. How can I do so?
As a concrete example of the TableCellRenderer approach suggested here, implement the Icon interface, as shown here. Instead of varying the size, choose a color based on varying saturation or brightness using Color.getHSBColor(), as shown here.
Addendum: Like the example cited, the render below assumes that the data values are normalized in the interval [0, 1). You'll need to scale to your data model's minimum and maximum values instead. If the model is updated frequently, it may be worth updating these values with each addition.
/**
* #see https://stackoverflow.com/a/21756629/230513
* #see https://stackoverflow.com/a/2834484/230513
*/
private static class DecRenderer extends DefaultTableCellRenderer implements Icon {
private static final int N = 256;
private static final int SIZE = 32;
private static List<Color> clut = new ArrayList<>(N);
private DecimalFormat df;
public DecRenderer(DecimalFormat df) {
this.df = df;
this.setIcon(this);
this.setHorizontalAlignment(JLabel.RIGHT);
this.setBackground(Color.lightGray);
for (float i = 0; i < N; i++) {
clut.add(Color.getHSBColor(1, 1, i / N));
}
}
#Override
protected void setValue(Object value) {
setText((value == null) ? "" : df.format(value));
}
#Override
public void paintIcon(Component c, Graphics g, int x, int y) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
double v = Double.valueOf(this.getText());
final int i = (int) (v * N - 1);
g2d.setColor(clut.get(i));
g2d.fillOval(x, y, SIZE, SIZE);
}
#Override
public int getIconWidth() {
return SIZE;
}
#Override
public int getIconHeight() {
return SIZE;
}
}
You have to know the possible range of the double values. (At least, this would make things easier to understand). In any case, you can transform your double value to be in the range [0,1]. And it's in many cases beneficial to do such a kind of normalization anyhow.
So you can create a method
private static double normalize(double min, double max, double value) {
return (value - min) / (max - min);
}
This method will convert any "value" between min and max to a value in [0,1]. In order to map this normalized value to a color, you can use
private static Color colorFor(double value) {
value = Math.max(0, Math.min(1, value));
int red = (int)(value * 255);
return new Color(red,0,0);
}
This method will convert a value between 0.0 and 1.0 into a color: 0.0 will be black, and 1.0 will be red.
So assuming you have a double "value" between "min" and "max", you can map it to a color like this:
g2d.setColor(colorFor(normalize(min, max, value)));
g2d.fill(shape);
EDIT: Reply to the comment: I think there are basically two options:
You could constantly keep track of the current min/max values that you have encountered so far.
Alternatively, you could map the interval of [-Infinity,+Infinity] to the interval [0,1] using a sigmoidal function
The first option has the disadvantage that values that have previously been associated with a certain color may suddenly have a different color. For example assume that you mapped the interval [0,1] to the color range [black, red]. Now you obtain a new maximum value like 100000. Then you have to adjust your range, the values 0 and 1 will no longer have a visual difference: They will both be mapped to 'black'.
The second option has the disadvantage that small differences in the initial values will not have a noticable effect on the color, depending on the range in which these differences occur. For example, you will not be able to see a difference between 10000 and 10001 there.
So you have to be clear about what color mapping and behavior you want.
However, here is an example that uses a sigmoidal function to map the interval of [-Infinity,+Infinity] to the interval [0,1], and this interval to a color:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
class ColorRange
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new ColorRangePanel());
f.setSize(1000, 200);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ColorRangePanel extends JPanel
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
double values[] = {
-1e3, -1e2, -1e1, -1, -0.5, 0.0, 0.5, 1, 1e1, 1e2, 1e3
};
for (int i=0; i<values.length; i++)
{
double value = values[i];
int w = getWidth() / values.length;
int x = i * w;
g.setColor(Color.BLACK);
g.drawString(String.valueOf(value), x, 20);
g.setColor(colorFor(value));
g.fillRect(x, 50, w, 100);
}
}
private static Color colorFor(double value)
{
double v0 = value / Math.sqrt(1 + value * value); // -1...1
double v1 = (1 + v0) * 0.5; // 0...1
return colorForRange(v1);
}
private static Color colorForRange(double value)
{
value = Math.max(0, Math.min(1, value));
int red = (int)(value * 255);
return new Color(red,0,0);
}
}
The question lacks context. All Swing components have the setBackground method which can change the background of opaque components.
If you want to change the background color of a JTable cell or a JList or a JTable or JComboBox, then you need to provide a custom cell renderer capable of perfoming this task, in which case you should have a look at...
JTable, Using Custom Renderers
JList, Writing a Custom Cell Renderer
JComboBox, Providing a Custom Renderer
JTree, Customizing a Tree's Display
Updated with example, based on updates to the OP's question
The following is an example of color blending algorithm that allows you to specify the colors you want to use and at what ratio/fraction they should appear. The example is very simply, it uses only three colors spread evenly across the range, but you could put more colors in or adjust the weight according to your requirements.
The example takes a series of randomly generated values and normalises them, so that as the value tends towards 0 (or the lower normal range) it will get darker, as it approaches 1, it will get lighter.
You could change the algorithm to normalise positive and negative values separately if you chose.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ColorBars {
public static void main(String[] args) {
new ColorBars();
}
public ColorBars() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private PlotPane plotPane;
private JSpinner valueAdd;
public TestPane() {
setLayout(new BorderLayout());
plotPane = new PlotPane();
add(plotPane);
}
}
public class PlotPane extends JPanel {
private Color[] colorRange = new Color[]{Color.BLACK, Color.RED, Color.WHITE};
private float[] ratioRanges = new float[]{0f, 0.5f, 1f};
// private Color maxColor = Color.WHITE;
// private Color minColor = Color.RED;
private List<Double> values;
private double min = Double.MAX_VALUE;
private double max = Double.MIN_VALUE;
public PlotPane() {
values = new ArrayList<>(25);
Random rnd = new Random();
for (int index = 0; index < 10; index++) {
addValue((rnd.nextDouble() * 2000) - 1000);
}
}
public void addValue(double value) {
max = Math.max(max, value);
min = Math.min(min, value);
values.add(value);
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics g2d = (Graphics2D) g.create();
int height = getHeight();
int width = getWidth();
int barWidth = width / values.size();
int x = 0;
for (Double value : values) {
double norm = value - min;
norm /= (max - min);
int barHeight = (int) (height * norm);
System.out.println(NumberFormat.getInstance().format(norm));
Color color = blendColors(ratioRanges, colorRange, (float)norm);
g2d.setColor(color);
g2d.fillRect(x, height - barHeight, barWidth, barHeight);
x += barWidth;
}
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;
}
}
You can see this color blend algorithm in use at:
Color fading algorithm?
Java: Smooth Color Transition

Categories