Efficiently color cycling an image in Java

前端 未结 2 1404
南方客
南方客 2020-11-28 14:44

I\'m writing a Mandelbrot fractal viewer, and I would like to implement color cycling in a smart way. Given an image, I would like to modify its IndexColorModel.

As

相关标签:
2条回答
  • 2020-11-28 15:07

    In addition to pre-computing the cycles, as @Thomas comments, factor out the magic number 1000. Here's a related example of Changing the ColorModel of a BufferedImage and a project you may like.

    Addendum: Factoring out magic numbers will allow you to change them reliably while profiling, which is required to see if you're making progress.

    Addendum: While I suggested three color lookup tables per frame, your idea to pre-compute IndexColorModel instances is even better. As an alternative to an array, consider a Queue<IndexColorModel>, with LinkedList<IndexColorModel> as a concrete implementation. This simplifies your model rotation as shown below.

    @Override
    public void actionPerformed(ActionEvent e) { // Called by Timer.
        imageSource.newPixels(imageData, models.peek(), 0, N);
        models.add(models.remove());
        repaint();
    }
    

    Addendum: One more variation to dynamically change the color models and display timing.

    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 讨论(0)
  • 2020-11-28 15:09

    I'd use LWJGL (OpenGL interface to Java) with a Mandelbrot pixel shader, and do the colour-cycling in the shader. Far more efficient than using Java2D.

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

    0 讨论(0)
提交回复
热议问题