2011-09-25 17 views
13

Estoy escribiendo un visor de fractal Mandelbrot, y me gustaría implementar el ciclo de color de una manera inteligente. Dada una imagen, me gustaría modificar su IndexColorModel.Efectivamente el ciclo de color de una imagen en Java

Por lo que puedo decir, no hay forma de modificar un IndexColorModel, y no hay forma de darle a una imagen un nuevo IndexColorModel. De hecho, creo que no hay forma de extraer su modelo de color o datos de imagen.

Parece que la única solución es aferrarse a los datos de imágenes sin formato y la paleta de colores que se usaron para crear la imagen, crear manualmente una nueva paleta con los colores girados, crear un nuevo IndexColorModel y luego crear un nuevo imagen de los datos y nuevo modelo de color.

Todo esto parece demasiado trabajo. ¿Hay una manera más fácil y más rápida?

Aquí está la mejor solución que puedo encontrar. Este código crea una imagen de 1000x1000 píxeles y muestra una animación de los colores que recorren alrededor de 30 fotogramas por segundo.

(antigua)

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

Edición 2:

Ahora calcular previamente los IndexColorModels. Esto significa que en cada marco solo necesito actualizar MemoryImageSource con un nuevo IndexColorModel. Esta parece ser la mejor solución.

(También me acabo de dar cuenta de que en mi explorador de fractales, puedo reutilizar el único conjunto de IndexColorModels precalculado en cada imagen que genero. Eso significa que el costo único de 140K me permite realizar ciclos de todo en tiempo real. es grande)

Aquí está el código:.

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

Editar: (antiguo)

Heisenbug sugirió que use el nuevo método Pixel() de MemoryImageSource. La respuesta ha sido eliminada, pero resultó ser una buena idea. Ahora solo creo un MemoryImageSource y una Imagen. En cada marco, creo un nuevo IndexColorModel y actualizo el MemoryImageSource.

Aquí está el código de actualización: (antiguo)

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

¿Qué pasa con la precomputación un ciclo y luego animar las imágenes? –

+0

@thomas El ejemplo de código anterior muestra 216 fotogramas a 1000x1000 píxeles. Un marco calculado usa 4 bytes por píxel. Eso es 864 MB. Intenté esto, y específicamente lo estoy evitando ahora. – dln385

+2

No precompute todos los marcos, solo haga los tres clics: 3 * 216 * 216 = ~ 140K – trashgod

Respuesta

8

Además de pre-cálculo de los ciclos, como los comentarios @Thomas, factorizar el número mágico 1000. Aquí está un ejemplo relacionado de Changing the ColorModel of a BufferedImage y un project que te pueda gustar.

Addendum: Factorizar magic numbers le permitirá cambiarlos de manera confiable durante la creación de perfiles, lo cual es necesario para ver si está progresando.

Adición: Si bien sugerí tres tablas de búsqueda de color por cuadro, su idea de calcular previamente las instancias de IndexColorModel es aún mejor.Como alternativa a una matriz, considere un Queue<IndexColorModel>, con LinkedList<IndexColorModel> como una implementación concreta. Esto simplifica la rotación de su modelo como se muestra a continuación.

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

Addendum: Una variación más para cambiar dinámicamente los modelos de color y mostrar el tiempo.

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

¿A qué te refieres cuando dices "factorizar el número mágico 1000"? – dln385

+0

He elaborado anteriormente; '216' es otro candidato. – trashgod

+1

Perdón por todos los números mágicos. Pensé que las variables complicarían el código, pero creo que son necesarias. – dln385

2

que haría uso de LWJGL (interfaz de OpenGL para Java) con un sombreado de píxeles de Mandelbrot, y hacer que el color de la bicicleta en el sombreado. Mucho más eficiente que usar Java2D.

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

+0

Muy relevante y una buena idea, pero estoy escribiendo este programa para la experiencia en Swing y manipulación de imágenes. Gracias, sin embargo. – dln385

Cuestiones relacionadas