2011-09-25 66 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