2011-09-25 67 views
13

我正在写一个曼德尔布罗分形观众骑自行车的形象,我想实现一个聪明的办法颜色循环。给定一个图像,我想修改它的IndexColorModel。高效彩色在Java中

据我所知,有没有办法来修改一个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。这似乎是最好的解决方案。 (我也注意到在我的分形浏览器中,我可以在每个生成的图像上重新使用一组预先计算好的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; 
    } 
} 

编辑:(旧)

Heisenbug建议我使用newPixels MemoryImageSource的()方法。答案已经被删除,但结果是一个好主意。现在我只创建一个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; 
    } 
} 
+3

关于预计算一个周期,然后动画图片会出现什么? –

+0

@thomas上面的代码示例显示216个1000x1000像素的帧。计算帧使用每个像素4个字节。这是864 MB。我已经尝试过了,现在我特意避开它。 – dln385

+2

不要预先计算所有的帧,只需做三个支架:3 * 216 * 216 =〜140K – trashgod

回答

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

附录:动态更改颜色模型和显示时间的另一种变体。

enter image description here

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(); 
     } 
    } 
} 
+0

当你说“分解魔法数字1000”时,你是什么意思? – dln385

+0

我已经详细阐述过了; '216'是另一位候选人。 – trashgod

+1

对不起所有的神奇数字。我认为变量会使代码复杂化,但我认为它们是必要的。 – dln385

2

我会用LWJGL(OpenGL的接口到Java)与曼德尔布罗像素着色器,并执行颜色循环在着色器。远比使用Java2D更高效。

http://nuclear.mutantstargoat.com/articles/sdr_fract/

+0

非常相关和一个好主意,但我正在编写这个程序的Swing和图像处理经验。谢谢,不过。 – dln385