我需要一种算法或方法来生成调色板以为Julia设置的图像着色。例如,当使用逃逸时间算法生成图像时,我想出了以下图像:
但是我需要一些方法来生成自定义调色板,例如the Wikipedia page:
如何获得类似的图像?另外,Julia集应使用哪种颜色平滑算法?
这是用于澄清的代码段:
int max_iter = 256;
ComplexNumber constant = new ComplexNumber(cReal,cImag);
float Saturation = 1f;
for(int X=0; X<WIDTH; X++)
{
for(int Y=0; Y<HEIGHT; Y++)
{
ComplexNumber oldz = new ComplexNumber();
ComplexNumber newz = new ComplexNumber(2.0*(X-WIDTH/2)/(WIDTH/2), 1.33*(Y-HEIGHT/2)/(HEIGHT/2) );
int i;
for(i=0;i<max_iter; i++)
{
oldz = newz;
newz = newz.square();
newz.add(constant);
if(newz.mod() > 2)
break;
}
float Brightness = i < max_iter ? 1f : 0;
float Hue = (i%256)/255.0f;
Color color = Color.getHSBColor((float)Hue, Saturation, Brightness);
img.setRGB(X,Y,color.getRGB());
}
}
最佳答案
有许多可能的方法用于这种颜色映射。下面的程序中概述了最简单的方法。
此代码段的核心是initColorMap
方法。它需要许多插值步骤以及要在其间进行插值的颜色数组。在屏幕截图中,这些是
该方法返回一个
int
数组,其中包含插值颜色的RGB值。这可以直接使用。但是为了提高通用性,这些数组被包装到ColorMap1D
接口中,该接口提供了一种方法,该方法可为0.0和1.0之间的任何给定值返回RGB颜色。对于您的应用程序案例,这可能像这样使用:
double value = (double)iterations / maxIterations;
int rgb = colorMap.getColor(value);
( EDIT :以下说明和代码已根据注释中的请求进行了更新和扩展)
这样的“归一化”到范围[0.0,1.0]和使用接口进行抽象通常是有益的。
为了说明这种抽象可能带来的影响:
ColorMaps1D
类包含几种创建ColorMap1D
实例的方法:ColorMaps1D#createDefault(int steps, Color ... colors)
:创建一个默认颜色图,该颜色图以给定的颜色序列以预定义的步数(颜色图的“分辨率”)进行插值ColorMaps1D#create(ColorMap1D delegate, DoubleFunction<Double> function)
:此方法创建一个颜色图,其中getColor
方法的参数在传递给定委托的getColor
方法之前,先使用给定函数进行转换。 因此,可以轻松地创建在颜色之间进行非线性插值的
ColorMap1D
。甚至可以创建一个ColorMap1D
实现,该实现可以插值到其他几种颜色图上。例如,我添加了一个色图,该色图使用默认的简单的Red-> Green-> Blue色图,但使用计算该参数正弦值的函数对其进行访问。这样,可以在“红色”->“绿色”->“蓝色”颜色映射中“循环”几次。
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()+")";
}
}
// NOTE: This is an interface that is equivalent to the functional
// interface in Java 8. In an environment where Java 8 is available,
// this interface may be omitted, and the Java 8 version of this
// interface may be used instead.
interface DoubleFunction<R>
{
R apply(double value);
}
/**
* Interface for classes that can map a single value from the range
* [0,1] to an int that represents an RGB color
*/
interface ColorMap1D
{
/**
* Returns an int representing the RGB color, for the given value in [0,1]
*
* @param value The value in [0,1]
* @return The RGB color
*/
int getColor(double value);
}
/**
* Default implementation of a {@link ColorMap1D} that is backed by
* a simple int array
*/
class DefaultColorMap1D implements ColorMap1D
{
/**
* The backing array containing the RGB colors
*/
private final int colorMapArray[];
/**
* Creates a color map that is backed by the given array
*
* @param colorMapArray The array containing RGB colors
*/
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];
}
}
/**
* Methods to create {@link ColorMap1D} instances
*/
class ColorMaps1D
{
/**
* Creates a {@link ColorMap1D} that walks through the given delegate
* color map using a sine function with the given frequency
*
* @param delegate The delegate
* @param frequency The frequency
* @return The new {@link ColorMap1D}
*/
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);
}
});
}
/**
* Creates a {@link ColorMap1D} that will convert the argument
* with the given function before it is looking up the color
* in the given delegate
*
* @param delegate The delegate {@link ColorMap1D}
* @param function The function for converting the argument
* @return The new {@link ColorMap1D}
*/
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));
}
};
}
/**
* Creates a new ColorMap1D that maps a value between 0.0 and 1.0
* (inclusive) to the specified color range, internally using the
* given number of steps for interpolating between the colors
*
* @param steps The number of interpolation steps
* @param colors The colors
* @return The color map
*/
static ColorMap1D createDefault(int steps, Color ... colors)
{
return new DefaultColorMap1D(initColorMap(steps, colors));
}
/**
* Creates the color array which contains RGB colors as integers,
* interpolated through the given colors.
*
* @param steps The number of interpolation steps, and the size
* of the resulting array
* @param colors The colors for the array
* @return The color array
*/
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 constructor to prevent instantiation
*/
private ColorMaps1D()
{
// Private constructor to prevent instantiation
}
}
/**
* A panel painting a {@link ColorMap1D}
*/
class ColorMapPanel1D extends JPanel
{
/**
* The {@link ColorMap1D} that is painted
*/
private final ColorMap1D colorMap;
/**
* Creates a new panel that paints the given color map
*
* @param colorMap The {@link ColorMap1D} to be painted
*/
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(或许多其他问题))