在 Java 中高效地对图像进行色彩循环
我正在编写一个曼德布洛特分形查看器,我想以智能方式实现颜色循环。给定一个图像,我想修改它的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。这似乎是最好的解决方案。
(我还注意到,在我的分形资源管理器中,我可以在我生成的每个图像上重用一组预先计算的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建议我使用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;
    }
}
 
					 
				
 
				    		 
				    		 
				    		