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;
}
}
¿Qué pasa con la precomputación un ciclo y luego animar las imágenes? –
@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
No precompute todos los marcos, solo haga los tres clics: 3 * 216 * 216 = ~ 140K – trashgod