有许多可能的方法来进行颜色映射。最简单的方法在下面的程序中概述。
核心代码是initColorMap方法。它需要一些插值步骤和一个要插值的颜色数组。在屏幕截图中,这些颜色分别为:
- 红色
- 红色、绿色
- 红色、绿色、蓝色(如问题第一张图片所示)
- 红色、黄色、绿色、青色、蓝色、洋红色
- 黑色、橙色、白色、蓝色、深蓝色(试图获得类似于问题第二张图片中的颜色映射)
- 用正弦函数采样的红色、绿色、蓝色
该方法返回一个包含插值颜色的RGB值的int数组。这可以直接使用。但为了提高灵活性,这些数组被封装到一个ColorMap1D接口中,该接口提供了一个方法,用于返回0.0到1.0之间任何给定值的RGB颜色。
对于您的应用程序案例,可能会像这样使用:
double value = (double)iterations / maxIterations;
int rgb = colorMap.getColor(value);
(编辑: 根据评论的要求,以下描述和代码已经更新和扩展)
将值范围归一化到[0.0, 1.0]并使用接口进行抽象通常是有益的。
作为演示这种抽象可能产生的效果的方法: ColorMaps1D
类包含几种创建ColorMap1D
实例的方法:
ColorMaps1D#createDefault(int steps, Color ... colors)
: 创建一个默认的颜色映射,该映射在给定的颜色序列上进行插值,并具有预定义数量的步骤(颜色映射的“分辨率”)
ColorMaps1D#create(ColorMap1D delegate, DoubleFunction<Double> function)
: 此方法创建一个颜色映射,在将getColor
方法的参数传递给给定委托的getColor
方法之前,使用给定函数对其进行转换。
因此,可以轻松地创建在颜色之间进行非线性插值的ColorMap1D
。甚至可以创建一个ColorMap1D
实现,该实现在多个其他颜色映射之间进行插值。
例如,我添加了一个颜色映射,它使用默认的简单红色->绿色->蓝色颜色映射,但使用计算参数正弦值的函数访问它。这样,可以多次“循环”通过红色->绿色->蓝色颜色映射。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.util.Arrays;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ColorMapsTest
{
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().setLayout(new GridLayout(0,1));
int steps = 1024;
f.getContentPane().add(
createPanel(steps, Color.RED));
f.getContentPane().add(
createPanel(steps, Color.RED, Color.GREEN));
f.getContentPane().add(
createPanel(steps, Color.RED, Color.GREEN, Color.BLUE));
f.getContentPane().add(
createPanel(steps,
Color.RED, Color.YELLOW,
Color.GREEN, Color.CYAN,
Color.BLUE, Color.MAGENTA));
f.getContentPane().add(
createPanel(steps,
Color.BLACK, Color.ORANGE, Color.WHITE,
Color.BLUE, new Color(0,0,128)));
JPanel panel = new JPanel(new BorderLayout());
Color colors[] = new Color[]{ Color.RED, Color.GREEN, Color.BLUE };
String info = "With sine over "+createString(colors);
panel.add(new JLabel(info), BorderLayout.NORTH);
ColorMapPanel1D colorMapPanel =
new ColorMapPanel1D(
ColorMaps1D.createSine(
ColorMaps1D.createDefault(steps, colors), Math.PI * 4));
panel.add(colorMapPanel, BorderLayout.CENTER);
f.getContentPane().add(panel);
f.setSize(500, 400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static JPanel createPanel(int steps, Color ... colors)
{
JPanel panel = new JPanel(new BorderLayout());
String info = "In "+steps+" steps over "+createString(colors);
panel.add(new JLabel(info), BorderLayout.NORTH);
ColorMapPanel1D colorMapPanel =
new ColorMapPanel1D(ColorMaps1D.createDefault(steps, colors));
panel.add(colorMapPanel, BorderLayout.CENTER);
return panel;
}
private static String createString(Color ... colors)
{
StringBuilder sb = new StringBuilder();
for (int i=0; i<colors.length; i++)
{
sb.append(createString(colors[i]));
if (i < colors.length - 1)
{
sb.append(", ");
}
}
return sb.toString();
}
private static String createString(Color color)
{
return "("+color.getRed()+","+color.getGreen()+","+color.getBlue()+")";
}
}
interface DoubleFunction<R>
{
R apply(double value);
}
interface ColorMap1D
{
int getColor(double value);
}
class DefaultColorMap1D implements ColorMap1D
{
private final int colorMapArray[];
DefaultColorMap1D(int colorMapArray[])
{
this.colorMapArray = colorMapArray;
}
@Override
public int getColor(double value)
{
double d = Math.max(0.0, Math.min(1.0, value));
int i = (int)(d * (colorMapArray.length - 1));
return colorMapArray[i];
}
}
class ColorMaps1D
{
static ColorMap1D createSine(ColorMap1D delegate, final double frequency)
{
return create(delegate, new DoubleFunction<Double>()
{
@Override
public Double apply(double value)
{
return 0.5 + 0.5 * Math.sin(value * frequency);
}
});
}
static ColorMap1D create(
final ColorMap1D delegate, final DoubleFunction<Double> function)
{
return new ColorMap1D()
{
@Override
public int getColor(double value)
{
return delegate.getColor(function.apply(value));
}
};
}
static ColorMap1D createDefault(int steps, Color ... colors)
{
return new DefaultColorMap1D(initColorMap(steps, colors));
}
static int[] initColorMap(int steps, Color ... colors)
{
int colorMap[] = new int[steps];
if (colors.length == 1)
{
Arrays.fill(colorMap, colors[0].getRGB());
return colorMap;
}
double colorDelta = 1.0 / (colors.length - 1);
for (int i=0; i<steps; i++)
{
double globalRel = (double)i / (steps - 1);
int index0 = (int)(globalRel / colorDelta);
int index1 = Math.min(colors.length-1, index0 + 1);
double localRel = (globalRel - index0 * colorDelta) / colorDelta;
Color c0 = colors[index0];
int r0 = c0.getRed();
int g0 = c0.getGreen();
int b0 = c0.getBlue();
int a0 = c0.getAlpha();
Color c1 = colors[index1];
int r1 = c1.getRed();
int g1 = c1.getGreen();
int b1 = c1.getBlue();
int a1 = c1.getAlpha();
int dr = r1-r0;
int dg = g1-g0;
int db = b1-b0;
int da = a1-a0;
int r = (int)(r0 + localRel * dr);
int g = (int)(g0 + localRel * dg);
int b = (int)(b0 + localRel * db);
int a = (int)(a0 + localRel * da);
int rgb =
(a << 24) |
(r << 16) |
(g << 8) |
(b << 0);
colorMap[i] = rgb;
}
return colorMap;
}
private ColorMaps1D()
{
}
}
class ColorMapPanel1D extends JPanel
{
private final ColorMap1D colorMap;
ColorMapPanel1D(ColorMap1D colorMap)
{
this.colorMap = colorMap;
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
for (int x=0; x<getWidth(); x++)
{
double d = (double)x / (getWidth() - 1);
int rgb = colorMap.getColor(d);
g.setColor(new Color(rgb));
g.drawLine(x, 0, x, getHeight());
}
}
}
(关于颜色平滑:这应该是一个单独的问题。或者也许不需要,因为在StackOverflow上已经有很多关于这个问题的提问了。例如,请参见Smooth spectrum for Mandelbrot Set rendering(或其他很多问题)。