使用Java有效地对图像进行颜色循环
我正在写一个Mandelbrot分形浏览器,我想以一种聪明的方式实现颜色循环。 给定一个图像,我想修改它的IndexColorModel。
据我所知,没有办法修改IndexColorModel,也没有办法给一个图像一个新的IndexColorModel。 事实上,我认为没有办法提取其颜色模型或图像数据。
看来,唯一的解决scheme是坚持用于创build图像的原始图像数据和调色板,手动创build一个新的调色板与旋转的颜色,创build一个新的IndexColorModel,然后从数据创build一个全新的图像和新的颜色模型。
这一切似乎是太多的工作。 有没有更简单快捷的方法?
这是我能想出的最好的解决scheme。 此代码创build一个1000×1000像素的图像,并显示以每秒30帧循环的颜色animation。
(旧)
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。 这似乎是最好的解决scheme。
(我也注意到,在我的分形浏览器中,我可以在每个生成的图像上重新使用一组预先计算好的IndexColorModels,这意味着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; } }
编辑:( 老)
Heisenbugbuild议我使用MemoryImageSource的newPixels()方法。 答案已经被删除,但是原来是一个好主意。 现在我只创build一个MemoryImageSource和一个Image。 在每一帧我创build一个新的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; } }
除了预先计算周期,就像@Thomas注释一样,将幻数1000分解出来。下面是一个关于更改BufferedImage的ColorModel和您可能喜欢的项目的相关示例。
附录:分解幻数将使您可以在分析时可靠地更改它们,这是查看您是否正在取得进展所必需的。
附录:虽然我提出了每帧三个颜色查找表,但您预先计算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(); }
附录:dynamic更改颜色模型和显示时间的另一种变体。
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(); } } }
我将使用带有Mandelbrot像素着色器的LWJGL(OpenGL接口到Java),并在着色器中进行颜色循环。 远比使用Java2D更高效。