我正在写一个Mandelbrot分形查看器,我想以聪明的方式实现色彩循环。给定一个图像,我想修改它的IndexColorModel。
据我所知,没有办法修改IndexColorModel,并且无法为图像提供新的IndexColorModel。事实上,我认为没有办法提取其颜色模型或图像数据。
似乎唯一的解决方案是保持用于创建图像的原始图像数据和调色板,手动创建带有旋转颜色的新调色板,创建新的IndexColorModel,然后创建一个全新的图像来自数据和新的颜色模型。
这似乎太多了。有更简单快捷的方法吗?
这是我能提出的最佳解决方案。此代码创建一个1000x1000像素的图像,并显示以每秒约30帧的速度循环的颜色动画。
的(旧) 的
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
public class ColorCycler {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame jFrame = new JFrame("Color Cycler");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(new MyPanel());
jFrame.pack();
jFrame.setVisible(true);
}
}
class MyPanel extends JPanel implements ActionListener {
private byte[] reds = new byte[216];
private byte[] greens = new byte[216];
private byte[] blues = new byte[216];
private final byte[] imageData = new byte[1000 * 1000];
private Image image;
public MyPanel() {
generateColors();
generateImageData();
(new Timer(35, this)).start();
}
// The window size is 1000x1000 pixels.
public Dimension getPreferredSize() {
return new Dimension(1000, 1000);
}
// Generate 216 unique colors for the color model.
private void generateColors() {
int index = 0;
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 6; j++) {
for (int k = 0; k < 6; k++, index++) {
reds[index] = (byte) (i * 51);
greens[index] = (byte) (j * 51);
blues[index] = (byte) (k * 51);
}
}
}
}
// Create the image data for the MemoryImageSource.
// This data is created once and never changed.
private void generateImageData() {
for (int i = 0; i < 1000 * 1000; i++) {
imageData[i] = (byte) (i % 216);
}
}
// Draw the image.
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, 1000, 1000, null);
}
// This method is called by the timer every 35 ms.
// It creates the modified image to be drawn.
@Override
public void actionPerformed(ActionEvent e) { // Called by Timer.
reds = cycleColors(reds);
greens = cycleColors(greens);
blues = cycleColors(blues);
IndexColorModel colorModel = new IndexColorModel(8, 216, reds, greens, blues);
image = createImage(new MemoryImageSource(1000, 1000, colorModel, imageData, 0, 1000));
repaint();
}
// Cycle the colors to the right by 1.
private byte[] cycleColors(byte[] colors) {
byte[] newColors = new byte[216];
newColors[0] = colors[215];
System.arraycopy(colors, 0, newColors, 1, 215);
return newColors;
}
}
编辑2:
现在我预先计算了IndexColorModels。这意味着在每个帧上我只需要使用新的IndexColorModel更新MemoryImageSource。这似乎是最好的解决方案。
(我也注意到,在我的分形探测器中,我可以在我生成的每个图像上重复使用单组预先计算的IndexColorModel。这意味着140K的一次性成本让我可以实时地对所有内容进行颜色循环。这是大。)
以下是代码:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
public class ColorCycler {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame jFrame = new JFrame("Color Cycler");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(new MyPanel());
jFrame.pack();
jFrame.setVisible(true);
}
}
class MyPanel extends JPanel implements ActionListener {
private final IndexColorModel[] colorModels = new IndexColorModel[216];
private final byte[] imageData = new byte[1000 * 1000];
private final MemoryImageSource imageSource;
private final Image image;
private int currentFrame = 0;
public MyPanel() {
generateColorModels();
generateImageData();
imageSource = new MemoryImageSource(1000, 1000, colorModels[0], imageData, 0, 1000);
imageSource.setAnimated(true);
image = createImage(imageSource);
(new Timer(35, this)).start();
}
// The window size is 1000x1000 pixels.
public Dimension getPreferredSize() {
return new Dimension(1000, 1000);
}
// Generate 216 unique colors models, one for each frame.
private void generateColorModels() {
byte[] reds = new byte[216];
byte[] greens = new byte[216];
byte[] blues = new byte[216];
int index = 0;
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 6; j++) {
for (int k = 0; k < 6; k++, index++) {
reds[index] = (byte) (i * 51);
greens[index] = (byte) (j * 51);
blues[index] = (byte) (k * 51);
}
}
}
for (int i = 0; i < 216; i++) {
colorModels[i] = new IndexColorModel(8, 216, reds, greens, blues);
reds = cycleColors(reds);
greens = cycleColors(greens);
blues = cycleColors(blues);
}
}
// Create the image data for the MemoryImageSource.
// This data is created once and never changed.
private void generateImageData() {
for (int i = 0; i < 1000 * 1000; i++) {
imageData[i] = (byte) (i % 216);
}
}
// Draw the image.
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, 1000, 1000, null);
}
// This method is called by the timer every 35 ms.
// It updates the ImageSource of the image to be drawn.
@Override
public void actionPerformed(ActionEvent e) { // Called by Timer.
currentFrame++;
if (currentFrame == 216) {
currentFrame = 0;
}
imageSource.newPixels(imageData, colorModels[currentFrame], 0, 1000);
repaint();
}
// Cycle the colors to the right by 1.
private byte[] cycleColors(byte[] colors) {
byte[] newColors = new byte[216];
newColors[0] = colors[215];
System.arraycopy(colors, 0, newColors, 1, 215);
return newColors;
}
}
编辑:(旧)
Heisenbug建议我使用MemoryImageSource的newPixels()方法。答案已被删除,但事实证明这是一个好主意。现在我只创建一个MemoryImageSource和一个Image。在每个帧上,我创建一个新的IndexColorModel并更新MemoryImageSource。
以下是更新后的代码: (旧)
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
public class ColorCycler {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame jFrame = new JFrame("Color Cycler");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(new MyPanel());
jFrame.pack();
jFrame.setVisible(true);
}
}
class MyPanel extends JPanel implements ActionListener {
private byte[] reds = new byte[216];
private byte[] greens = new byte[216];
private byte[] blues = new byte[216];
private final byte[] imageData = new byte[1000 * 1000];
private final MemoryImageSource imageSource;
private final Image image;
public MyPanel() {
generateColors();
generateImageData();
IndexColorModel colorModel = new IndexColorModel(8, 216, reds, greens, blues);
imageSource = new MemoryImageSource(1000, 1000, colorModel, imageData, 0, 1000);
imageSource.setAnimated(true);
image = createImage(imageSource);
(new Timer(35, this)).start();
}
// The window size is 1000x1000 pixels.
public Dimension getPreferredSize() {
return new Dimension(1000, 1000);
}
// Generate 216 unique colors for the color model.
private void generateColors() {
int index = 0;
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 6; j++) {
for (int k = 0; k < 6; k++, index++) {
reds[index] = (byte) (i * 51);
greens[index] = (byte) (j * 51);
blues[index] = (byte) (k * 51);
}
}
}
}
// Create the image data for the MemoryImageSource.
// This data is created once and never changed.
private void generateImageData() {
for (int i = 0; i < 1000 * 1000; i++) {
imageData[i] = (byte) (i % 216);
}
}
// Draw the image.
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, 1000, 1000, null);
}
// This method is called by the timer every 35 ms.
// It updates the ImageSource of the image to be drawn.
@Override
public void actionPerformed(ActionEvent e) { // Called by Timer.
reds = cycleColors(reds);
greens = cycleColors(greens);
blues = cycleColors(blues);
IndexColorModel colorModel = new IndexColorModel(8, 216, reds, greens, blues);
imageSource.newPixels(imageData, colorModel, 0, 1000);
repaint();
}
// Cycle the colors to the right by 1.
private byte[] cycleColors(byte[] colors) {
byte[] newColors = new byte[216];
newColors[0] = colors[215];
System.arraycopy(colors, 0, newColors, 1, 215);
return newColors;
}
}
答案 0 :(得分:8)
除了预先计算周期之外,还有@Thomas评论,将幻数1000分解出来。以下是您可能喜欢的Changing the ColorModel of a BufferedImage和project的相关示例。
附录:分析magic numbers将允许您在分析时可靠地更改它们,这是查看您是否正在取得进展所必需的。
附录:虽然我每帧建议三个颜色查找表,但您预先计算IndexColorModel
个实例的想法更好。作为数组的替代方案,请考虑Queue<IndexColorModel>
,LinkedList<IndexColorModel>
作为具体实现。这简化了模型旋转,如下所示。
@Override
public void actionPerformed(ActionEvent e) { // Called by Timer.
imageSource.newPixels(imageData, models.peek(), 0, N);
models.add(models.remove());
repaint();
}
附录:动态改变颜色模型和显示时间的另一种变体。
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.IndexColorModel;
import java.awt.image.MemoryImageSource;
import java.util.LinkedList;
import java.util.Queue;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/** @see http://stackoverflow.com/questions/7546025 */
public class ColorCycler {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new ColorCycler().create();
}
});
}
private void create() {
JFrame jFrame = new JFrame("Color Cycler");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final ColorPanel cp = new ColorPanel();
JPanel control = new JPanel();
final JSpinner s = new JSpinner(
new SpinnerNumberModel(cp.colorCount, 2, 256, 1));
s.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
cp.setColorCount(((Integer) s.getValue()).intValue());
}
});
control.add(new JLabel("Shades:"));
control.add(s);
jFrame.add(cp, BorderLayout.CENTER);
jFrame.add(control, BorderLayout.SOUTH);
jFrame.pack();
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
}
private static class ColorPanel extends JPanel implements ActionListener {
private static final int WIDE = 256;
private static final int PERIOD = 40; // ~25 Hz
private final Queue<IndexColorModel> models =
new LinkedList<IndexColorModel>();
private final MemoryImageSource imageSource;
private final byte[] imageData = new byte[WIDE * WIDE];
private final Image image;
private int colorCount = 128;
public ColorPanel() {
generateColorModels();
generateImageData();
imageSource = new MemoryImageSource(
WIDE, WIDE, models.peek(), imageData, 0, WIDE);
imageSource.setAnimated(true);
image = createImage(imageSource);
(new Timer(PERIOD, this)).start();
}
// The preferred size is NxN pixels.
@Override
public Dimension getPreferredSize() {
return new Dimension(WIDE, WIDE);
}
public void setColorCount(int colorCount) {
this.colorCount = colorCount;
generateColorModels();
generateImageData();
repaint();
}
// Generate MODEL_SIZE unique color models.
private void generateColorModels() {
byte[] reds = new byte[colorCount];
byte[] greens = new byte[colorCount];
byte[] blues = new byte[colorCount];
for (int i = 0; i < colorCount; i++) {
reds[i] = (byte) (i * 256 / colorCount);
greens[i] = (byte) (i * 256 / colorCount);
blues[i] = (byte) (i * 256 / colorCount);
}
models.clear();
for (int i = 0; i < colorCount; i++) {
reds = rotateColors(reds);
greens = rotateColors(greens);
blues = rotateColors(blues);
models.add(new IndexColorModel(
8, colorCount, reds, greens, blues));
}
}
// Rotate colors to the right by one.
private byte[] rotateColors(byte[] colors) {
byte[] newColors = new byte[colors.length];
newColors[0] = colors[colors.length - 1];
System.arraycopy(colors, 0, newColors, 1, colors.length - 1);
return newColors;
}
// Create some data for the MemoryImageSource.
private void generateImageData() {
for (int i = 0; i < imageData.length; i++) {
imageData[i] = (byte) (i % colorCount);
}
}
// Draw the image.
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
long start = System.nanoTime();
imageSource.newPixels(imageData, models.peek(), 0, WIDE);
models.add(models.remove());
double delta = (System.nanoTime() - start) / 1000000d;
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
g.drawString(String.format("%1$5.3f", delta), 5, 15);
}
// Called by the Timer every PERIOD ms.
@Override
public void actionPerformed(ActionEvent e) { // Called by Timer.
repaint();
}
}
}
答案 1 :(得分:2)
我使用Mandebrot像素着色器的LWJGL(Java的OpenGL接口),并在着色器中进行颜色循环。比使用Java2D更有效。