Gå til innhold

En liten fraktalgenerator


Anbefalte innlegg

Har de siste par dagene benyttet ruskeværet til å lage en fraktalgenerator, som jeg vil dele med dere. Det er nok mye som kan gjøres bedre, ihvertfall med tanke på koden som lagrer resultatet til fil.

 

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.MemoryImageSource;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

/**
*
* @author en ferieklar konsulent
*/
public class Mandelbrot {

 private int dimension;
 private int maxIter;
 private double xc;
 private double yc;
 private double zoom;

 private int[] bitmap;
 private Color[] palette;

 /**
  *
  */
 public Mandelbrot() {
 }

 /*
  * This method returns the pixel at the given position
  */
 private int getPixelValueAt(int x, int y) {
// Check to see if pixel value is already calculated
if (bitmap[x + y * this.dimension] == -1) {
  double _x = (x - this.dimension / 2.0) / (this.zoom * this.dimension / 4.0) + xc;
  double _y = (y - this.dimension / 2.0) / (this.zoom * this.dimension / 4.0) + yc;

  double r = _x;
  double i = _y;

  double rTemp;

  int n = 0;

  while ((r * r + i * i < 4) && (n < this.maxIter)) {
	rTemp = r * r - i * i + _x;
	i = 2 * r * i + _y;
	r = rTemp;
	n = n + 1;
  }

  return n;
} else {
  // The pixel value was already calculated, return old value
  return bitmap[x + y * this.dimension];
}
 }

 /*
  * This method recursively calculates the fractal
  */
 private void calculate(int x1, int y1, int x2, int y2) {
boolean isUniform = true;

int pixelValue = -2;
int last_PixelValue = -2;

for (int x = x1; x < x2; x++) {
  if (last_PixelValue == -2) {
	pixelValue = getPixelValueAt(x, y1);
  }
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x, y1);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x + y1 * this.dimension] = pixelValue;
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x, y2 - 1);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x + (y2 - 1) * this.dimension] = pixelValue;
}

for (int y = y1; y < y2; y++) {
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x2 - 1, y);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[(x2 - 1) + y * this.dimension] = pixelValue;
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x1, y);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x1 + y * this.dimension] = pixelValue;
}

if (((x2 - x1) > 2) && ((y2 - y1) > 2)) {
  if (isUniform) {
	for (int y = y1; y < y2; y++) {
	  for (int x = x1; x < x2; x++) {
		bitmap[x + y * this.dimension] = pixelValue;
	  }
	}
  } else {
	calculate(x1, y1, x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2); // Q1
	calculate(x1 + (x2 - x1) / 2, y1, x2, y1 + (y2 - y1) / 2); // Q2
	calculate(x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2, x2, y2); // Q3
	calculate(x1, y1 + (y2 - y1) / 2, x1 + (x2 - x1) / 2, y2); // Q4
  }
}
 }

 /*
  * This method creates an Image object out of the bitmap data
  */
 private Image getImage() {
int[] data = new int[this.dimension * this.dimension];
int index = 0;
for (int y = 0; y < this.dimension; y++) {
  for (int x = 0; x < this.dimension; x++) {
	int p = this.bitmap[x + y * this.dimension];

	data[index++] = this.palette[(p == -1 ? 0 : p) * 8 % 1024].getRGB();
  }
}

MemoryImageSource imageSource = new MemoryImageSource(this.dimension, this.dimension, data, 0, this.dimension);
return Toolkit.getDefaultToolkit().createImage(imageSource);
 }

 /*
  * This method converts an Image object to a BufferedImage object
  */
 private BufferedImage toBufferedImage(Image image) {
image = new ImageIcon(image).getImage();

BufferedImage bImg = null;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
try {
  GraphicsDevice gd = ge.getDefaultScreenDevice();
  GraphicsConfiguration gc = gd.getDefaultConfiguration();
  bImg = gc.createCompatibleImage(
		  image.getWidth(null), image.getHeight(null), Transparency.OPAQUE);
} catch (HeadlessException e) {}

if (bImg == null) {
  // Create a buffered image using the default color model
  int type = BufferedImage.TYPE_INT_RGB;
  bImg = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
}

Graphics2D g = bImg.createGraphics();

g.drawImage(image, 0, 0, null);
g.dispose();

return bImg;
 }

 /*
  * This method smoothly returns a resized and smoothed BufferedImage
  */
 private BufferedImage resizedImage(BufferedImage image, int width, int height) {
Image temp1 = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage resizedImage = new BufferedImage(width, height,
		BufferedImage.TYPE_INT_ARGB);
resizedImage.getGraphics().drawImage(temp1, 0, 0, null);

return resizedImage;
 }

 /**
  * Renders the fractal to file
  *
  * @param fileName the filename
  * @param dimension width of image
  * @param maxIter maximum iterations
  * @param xc center x coordinate
  * @param yc center y coordinate
  * @param zoom zoom factor
  */
 public void renderToFile(String fileName, int dimension, int maxIter, double xc, double yc, double zoom) {
this.dimension = dimension * 2;
this.maxIter = maxIter;
this.xc = xc;
this.yc = -yc;
this.zoom = zoom;

// Initialize bitmap buffer
bitmap = new int[this.dimension * this.dimension];
for (int y = 0; y < this.dimension; y++) {
  for (int x = 0; x < this.dimension; x++) {
	bitmap[x + y * this.dimension] = -1;
  }
}

// Initialize and create palette
palette = new Color[1024];
for (int n = 0; n < 1024; n++) {
  int r = -(int) (Math.cos(n / (512.0 / 3.141592654)) * 127.0) + 127;
  int g = -(int) (Math.cos(n / (512.0 / 3.141592654) * 1.5) * 127.0) + 127;
  int b = -(int) (Math.cos(n / (512.0 / 3.141592654) * 2.0) * 127.0) + 127;
  palette[n] = new Color(r, g, b);
}

long startTime = System.currentTimeMillis();
this.calculate(0, 0, this.dimension, this.dimension);

System.out.println("Processing took " + (System.currentTimeMillis() * 1.0 - startTime * 1.0) / 1000 + "seconds");

try {
  // Save as PNG
  File file = new File(fileName);

  ImageIO.write(this.resizedImage(this.toBufferedImage(this.getImage()), this.dimension / 2, this.dimension / 2), "png", file);
} catch (IOException e) {
  e.printStackTrace();
}
 }

 /**
  * @param args the command line arguments
  */
 public static void main(String[] args) {
Mandelbrot m = new Mandelbrot();
m.renderToFile("c:\\test.png", 1024, 1024, 0.0, 0.0, 1.0);
 }
}

 

Werner

Endret av wernie
Lenke til kommentar
Videoannonse
Annonse

Wow, werner, dette var stilige saker! Har du fulgt en guide for utregning, eller har du virkelig peilig på detta? :D

 

Tøft, får jeg lov til å skrive videre på denna koden og vise bildet i et vindu hvor man kan zoome og flytte rundt i fraktalen? :D

 

Edit: Jeg må bare oppgradere PCen min litt først tror jeg...

Endret av LostOblivion
Lenke til kommentar
Wow, werner, dette var stilige saker! Har du fulgt en guide for utregning, eller har du virkelig peilig på detta? :D

 

Tøft, får jeg lov til å skrive videre på denna koden og vise bildet i et vindu hvor man kan zoome og flytte rundt i fraktalen? :D

 

Edit: Jeg må bare oppgradere PCen min litt først tror jeg...

 

Jeg har vel laget min skjerv av fraktalgeneratorer i årenes løp. Den første skrev jeg vel på slutten av 80-tallet en gang. Dette er en utfordring jeg får lyst til å bryne meg på, med noen års mellomrom. Er jo litt morsomt å tenke tilbake på den første generatoren jeg laget. Det tok jo en hel dag å generere mandelbrot-settet. Rutina jeg la ut i dag bruker ca 0.1 sekund på min MacBook Pro på å regne ut det samme settet, dvs et bilde på 320x200, med 256 iterasjoner. Men så er jo en MacBook Pro noe kraftigere enn en Commodore 128, og algoritmen jeg bruker nå litt mer effektiv enn i den første generatoren jeg skrev. (Skrev den forøvrig inn den første fra et blad)

 

Du står fritt til å gjøre hva du vil med koden. Jeg har forøvrig fikset noen bugs og gjort noen forbedringer, så her følger ny kode:

 

package javaapplication2;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.MemoryImageSource;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

/**
*
* @author en ferieklar konsulent
*/
public class Mandelbrot {

 private class Palette {

int[] palette;

int numColors;

public Palette(int numColors) {
  this.numColors = numColors;
  this.palette = new int[numColors];

  double paletteRad = (double)this.numColors / 2.0 / Math.PI;

  for (int n=0; n<numColors; n++) {
	int r = -(int) (Math.cos(n / paletteRad * 1.0) * 127.0) + 127;
	int g = -(int) (Math.cos(n / paletteRad * 2.0) * 127.0) + 127;
	int b = -(int) (Math.cos(n / paletteRad * 4.0) * 127.0) + 127;
	this.palette[n] = (new Color(r, g, b)).getRGB();
  }
}

public int getColorAt(int index) {
  return this.palette[index % this.numColors];
}
 }

 private int dimension;
 private long maxIter;
 private double xc;
 private double yc;
 private double zoom;

 private int[] bitmap;
 private Palette palette;

 /**
  *
  */
 public Mandelbrot() {
 }

 /*
  * This method returns the pixel at the given position
  */
 private int getPixelValueAt(int x, int y) {
// Check to see if pixel value is already calculated
if (bitmap[x + y * this.dimension] == -1) {
  double _x = (x - this.dimension / 2.0) / (this.zoom * this.dimension / 4.0) + xc;
  double _y = (y - this.dimension / 2.0) / (this.zoom * this.dimension / 4.0) + yc;

  double r = _x;
  double i = _y;

  double rTemp;

  int n = 0;

  while ((r * r + i * i <= 4) && (n < this.maxIter)) {
	rTemp = r * r - i * i + _x;
	i = 2 * r * i + _y;
	r = rTemp;
	n = n + 1;
  }

  if ((r * r + i * i <= 4)) {
	return 0;
  } else {
	return n;
  }
} else {
  // The pixel value was already calculated, return old value
  return bitmap[x + y * this.dimension];
}
 }

 /*
  * This method recursively calculates the fractal
  */
 private void calculate(int x1, int y1, int x2, int y2) {
boolean isUniform = true;

int pixelValue = -2;
int last_PixelValue = -2;

for (int x = x1; x < x2; x++) {
  if (last_PixelValue == -2) {
	pixelValue = getPixelValueAt(x, y1);
  }
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x, y1);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x + y1 * this.dimension] = pixelValue;
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x, y2 - 1);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x + (y2 - 1) * this.dimension] = pixelValue;
}

for (int y = y1; y < y2; y++) {
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x2 - 1, y);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[(x2 - 1) + y * this.dimension] = pixelValue;
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x1, y);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x1 + y * this.dimension] = pixelValue;
}

if (((x2 - x1) > 2) && ((y2 - y1) > 2)) {
  if (isUniform) {
	for (int y = y1; y < y2; y++) {
	  for (int x = x1; x < x2; x++) {
		bitmap[x + y * this.dimension] = pixelValue;
	  }
	}
  } else {
	calculate(x1, y1, x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2); // Q1
	calculate(x1 + (x2 - x1) / 2, y1, x2, y1 + (y2 - y1) / 2); // Q2
	calculate(x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2, x2, y2); // Q3
	calculate(x1, y1 + (y2 - y1) / 2, x1 + (x2 - x1) / 2, y2); // Q4
  }
}
 }

 /*
  * This method creates an Image object out of the bitmap data
  */
 private Image getImage() {
for (int n = 0; n < this.dimension * this.dimension; n++) {
  this.bitmap[n] = this.palette.getColorAt(((this.bitmap[n] * 4) % 1024));
}

MemoryImageSource imageSource = new MemoryImageSource(this.dimension, this.dimension, this.bitmap, 0, this.dimension);
return Toolkit.getDefaultToolkit().createImage(imageSource);
 }

 /*
  * This method converts an Image object to a BufferedImage object
  */
 private BufferedImage toBufferedImage(Image image) {
image = new ImageIcon(image).getImage();

BufferedImage bImg = null;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
try {
  GraphicsDevice gd = ge.getDefaultScreenDevice();
  GraphicsConfiguration gc = gd.getDefaultConfiguration();
  bImg = gc.createCompatibleImage(
		  image.getWidth(null), image.getHeight(null), Transparency.OPAQUE);
} catch (HeadlessException e) {}

if (bImg == null) {
  // Create a buffered image using the default color model
  int type = BufferedImage.TYPE_INT_RGB;
  bImg = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
}

Graphics2D g = bImg.createGraphics();

g.drawImage(image, 0, 0, null);
g.dispose();

return bImg;
 }

 /*
  * This method smoothly returns a resized and smoothed BufferedImage
  */
 private BufferedImage resizedImage(BufferedImage image, int width, int height) {
Image temp1 = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage resizedImage = new BufferedImage(width, height,
		BufferedImage.TYPE_INT_ARGB);
resizedImage.getGraphics().drawImage(temp1, 0, 0, null);

return resizedImage;
 }

 /**
  * Renders the fractal to file
  *
  * @param fileName the filename
  * @param dimension width of image
  * @param maxIter maximum iterations
  * @param xc center x coordinate
  * @param yc center y coordinate
  * @param zoom zoom factor
  */
 public void renderToFile(String fileName, int dimension, long maxIter, double xc, double yc, double zoom) {
this.dimension = dimension * 2;
this.maxIter = maxIter;
this.xc = xc;
this.yc = yc;
this.zoom = zoom;

// Initialize bitmap buffer
bitmap = new int[this.dimension * this.dimension];
for (int y = 0; y < this.dimension; y++) {
  for (int x = 0; x < this.dimension; x++) {
	bitmap[x + y * this.dimension] = -1;
  }
}

this.palette = new Palette(1024);

long startTime = System.currentTimeMillis();
this.calculate(0, 0, this.dimension, this.dimension);

System.out.println("Processing took " + (System.currentTimeMillis() * 1.0 - startTime * 1.0) / 1000 + "seconds");

try {
  // Save as PNG
  File file = new File(fileName);

  ImageIO.write(this.resizedImage(this.toBufferedImage(this.getImage()), this.dimension / 2, this.dimension / 2), "png", file);
} catch (IOException e) {
  e.printStackTrace();
}
 }

 /**
  * @param args the command line arguments
  */
 public static void main(String[] args) {
Mandelbrot m = new Mandelbrot();
m.renderToFile("c:\\fraktal.png", 1024, 1024, 0.0, 0.0, 1.0);
 }
}

 

Werner.

Endret av wernie
Lenke til kommentar

Lagde en slags enkel navigator i swing til Wernies fraktalgenerator:

import java.io.File;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;

public class FractalViewer extends JFrame implements MouseListener, KeyListener
{
 Mandelbrot mandelbrot;
 JLabel view;

 int dimension;
 int maxIter;
 double xc;
 double yc;
 double zoom;

 FractalViewer(int dimension, int maxIter, double xc, double yc, double zoom)
 {
this.dimension = dimension;
this.maxIter = maxIter;
this.xc = xc;
this.yc = yc;
this.zoom = zoom;

setTitle("FractalViewer");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocation(50, 50);
setResizable(false);
addKeyListener(this);

mandelbrot = new Mandelbrot();
view = new JLabel();
view.addMouseListener(this);
getContentPane().add(view);
refreshView();

pack();
setVisible(true);
 }

 private void refreshView()
 {
BufferedImage image;
Graphics2D g;

image = mandelbrot.renderToImage(this.dimension, this.maxIter, this.xc, this.yc, this.zoom);
g = (Graphics2D)image.getGraphics();
g.drawString("dimension = " + dimension, 10, 10);
g.drawString("max iter = " + maxIter, 10, 20);
g.drawString("xc = " + xc, 10, 30);
g.drawString("yc = " + yc, 10, 40);
g.drawString("zoom = " + zoom, 10, 50);
g.drawString("Press 'P' to save to file", 10, 60);
view.setIcon(new ImageIcon(image));
pack();
 }

 public void mouseReleased(MouseEvent e)
 {
int mx;
int my;

mx = e.getX() - dimension/2;
my = e.getY() - dimension/2;

if (e.getButton() == MouseEvent.BUTTON1)
{
  xc = xc + mx / (zoom * dimension / 4.0);
  yc = yc + my / (zoom * dimension / 4.0);
  zoom = zoom * 2.0;
}
else
{
  zoom = zoom / 2.0;
}
refreshView();
 }

 public void keyReleased(KeyEvent e)
 {
switch (e.getKeyCode())
{
case KeyEvent.VK_P:
  String filepath = new File("fractal.png").getAbsolutePath();

  mandelbrot.renderToFile(filepath, this.dimension, this.maxIter, this.xc, this.yc, this.zoom);
  JOptionPane.showMessageDialog(this, "Image saved to file '" + filepath + "'");
  break;
case KeyEvent.VK_PAGE_DOWN:
  maxIter = maxIter * 2;
  refreshView();
  break;
case KeyEvent.VK_PAGE_UP:
  maxIter = maxIter / 2;
  refreshView();
  break;
case KeyEvent.VK_END:
  dimension = dimension + 256;
  refreshView();
  break;
case KeyEvent.VK_HOME:
  dimension = dimension - 256;
  refreshView();
  break;
}
 }

 public void keyTyped(KeyEvent e)
 {
/* Unused. */
 }

 public void keyPressed(KeyEvent e)
 {
/* Unused. */
 }

 public void mouseClicked(MouseEvent e)
 {
/* Unused. */
 }

 public void mouseEntered(MouseEvent e)
 {
/* Unused. */
 }

 public void mouseExited(MouseEvent e)
 {
/* Unused. */
 }

 public void mousePressed(MouseEvent e)
 {
/* Unused. */
 }

 public static void main(String[] args)
 {
new FractalViewer(1024, 4096, 0.001643721971153, 0.822467633298876, 57555555555.0);
 }
}

Og Wernies kode med tillagt renderToImage-metode:

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.MemoryImageSource;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

/**
*
* @author en ferieklar konsulent
*/
public class Mandelbrot {

 private class Palette {

int[] palette;

int numColors;

public Palette(int numColors) {
  this.numColors = numColors;
  this.palette = new int[numColors];

  double paletteRad = (double)this.numColors / 2.0 / Math.PI;

  for (int n=0; n<numColors; n++) {
	int r = -(int) (Math.cos(n / paletteRad * 1.0) * 127.0) + 127;
	int g = -(int) (Math.cos(n / paletteRad * 2.0) * 127.0) + 127;
	int b = -(int) (Math.cos(n / paletteRad * 4.0) * 127.0) + 127;
	this.palette[n] = (new Color(r, g, b)).getRGB();
  }
}

public int getColorAt(int index) {
  return this.palette[index % this.numColors];
}
 }

 private int dimension;
 private long maxIter;
 private double xc;
 private double yc;
 private double zoom;

 private int[] bitmap;
 private Palette palette;

 /**
  *
  */
 public Mandelbrot() {
 }

 /*
  * This method returns the pixel at the given position
  */
 private int getPixelValueAt(int x, int y) {
// Check to see if pixel value is already calculated
if (bitmap[x + y * this.dimension] == -1) {
  double _x = (x - this.dimension / 2.0) / (this.zoom * this.dimension / 4.0) + xc;
  double _y = (y - this.dimension / 2.0) / (this.zoom * this.dimension / 4.0) + yc;

  double r = _x;
  double i = _y;

  double rTemp;

  int n = 0;

  while ((r * r + i * i <= 4) && (n < this.maxIter)) {
	rTemp = r * r - i * i + _x;
	i = 2 * r * i + _y;
	r = rTemp;
	n = n + 1;
  }

  if ((r * r + i * i <= 4)) {
	return 0;
  } else {
	return n;
  }
} else {
  // The pixel value was already calculated, return old value
  return bitmap[x + y * this.dimension];
}
 }

 /*
  * This method recursively calculates the fractal
  */
 private void calculate(int x1, int y1, int x2, int y2) {
boolean isUniform = true;

int pixelValue = -2;
int last_PixelValue = -2;

for (int x = x1; x < x2; x++) {
  if (last_PixelValue == -2) {
	pixelValue = getPixelValueAt(x, y1);
  }
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x, y1);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x + y1 * this.dimension] = pixelValue;
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x, y2 - 1);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x + (y2 - 1) * this.dimension] = pixelValue;
}

for (int y = y1; y < y2; y++) {
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x2 - 1, y);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[(x2 - 1) + y * this.dimension] = pixelValue;
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x1, y);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x1 + y * this.dimension] = pixelValue;
}

if (((x2 - x1) > 2) && ((y2 - y1) > 2)) {
  if (isUniform) {
	for (int y = y1; y < y2; y++) {
	  for (int x = x1; x < x2; x++) {
		bitmap[x + y * this.dimension] = pixelValue;
	  }
	}
  } else {
	calculate(x1, y1, x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2); // Q1
	calculate(x1 + (x2 - x1) / 2, y1, x2, y1 + (y2 - y1) / 2); // Q2
	calculate(x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2, x2, y2); // Q3
	calculate(x1, y1 + (y2 - y1) / 2, x1 + (x2 - x1) / 2, y2); // Q4
  }
}
 }

 /*
  * This method creates an Image object out of the bitmap data
  */
 private Image getImage() {
for (int n = 0; n < this.dimension * this.dimension; n++) {
  this.bitmap[n] = this.palette.getColorAt(((this.bitmap[n] * 4) % 1024));
}

MemoryImageSource imageSource = new MemoryImageSource(this.dimension, this.dimension, this.bitmap, 0, this.dimension);
return Toolkit.getDefaultToolkit().createImage(imageSource);
 }

 /*
  * This method converts an Image object to a BufferedImage object
  */
 private BufferedImage toBufferedImage(Image image) {
image = new ImageIcon(image).getImage();

BufferedImage bImg = null;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
try {
  GraphicsDevice gd = ge.getDefaultScreenDevice();
  GraphicsConfiguration gc = gd.getDefaultConfiguration();
  bImg = gc.createCompatibleImage(
		  image.getWidth(null), image.getHeight(null), Transparency.OPAQUE);
} catch (HeadlessException e) {}

if (bImg == null) {
  // Create a buffered image using the default color model
  int type = BufferedImage.TYPE_INT_RGB;
  bImg = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
}

Graphics2D g = bImg.createGraphics();

g.drawImage(image, 0, 0, null);
g.dispose();

return bImg;
 }

 /*
  * This method smoothly returns a resized and smoothed BufferedImage
  */
 private BufferedImage resizedImage(BufferedImage image, int width, int height) {
Image temp1 = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage resizedImage = new BufferedImage(width, height,
		BufferedImage.TYPE_INT_ARGB);
resizedImage.getGraphics().drawImage(temp1, 0, 0, null);

return resizedImage;
 }

 /**
  * Renders the fractal to file
  *
  * @param fileName the filename
  * @param dimension width of image
  * @param maxIter maximum iterations
  * @param xc center x coordinate
  * @param yc center y coordinate
  * @param zoom zoom factor
  */
 public void renderToFile(String fileName, int dimension, long maxIter, double xc, double yc, double zoom) {
this.dimension = dimension * 2;
this.maxIter = maxIter;
this.xc = xc;
this.yc = yc;
this.zoom = zoom;

// Initialize bitmap buffer
bitmap = new int[this.dimension * this.dimension];
for (int y = 0; y < this.dimension; y++) {
  for (int x = 0; x < this.dimension; x++) {
	bitmap[x + y * this.dimension] = -1;
  }
}

this.palette = new Palette(1024);

long startTime = System.currentTimeMillis();
this.calculate(0, 0, this.dimension, this.dimension);

System.out.println("Processing took " + (System.currentTimeMillis() * 1.0 - startTime * 1.0) / 1000 + " seconds");

try {
  // Save as PNG
  File file = new File(fileName);

  ImageIO.write(this.resizedImage(this.toBufferedImage(this.getImage()), this.dimension / 2, this.dimension / 2), "png", file);
} catch (IOException e) {
  e.printStackTrace();
}
 }

 /**
  * Renders the fractal and returns it as a BufferedImage
  *
  * @param dimension width of image
  * @param maxIter maximum iterations
  * @param xc center x coordinate
  * @param yc center y coordinate
  * @param zoom zoom factor
  */
 public BufferedImage renderToImage(int dimension, long maxIter, double xc, double yc, double zoom) {
this.dimension = dimension * 2;
this.maxIter = maxIter;
this.xc = xc;
this.yc = yc;
this.zoom = zoom;

// Initialize bitmap buffer
bitmap = new int[this.dimension * this.dimension];
for (int y = 0; y < this.dimension; y++) {
  for (int x = 0; x < this.dimension; x++) {
	bitmap[x + y * this.dimension] = -1;
  }
}

this.palette = new Palette(1024);

long startTime = System.currentTimeMillis();
this.calculate(0, 0, this.dimension, this.dimension);

System.out.println("Processing took " + (System.currentTimeMillis() * 1.0 - startTime * 1.0) / 1000 + " seconds");

return this.resizedImage(this.toBufferedImage(this.getImage()), this.dimension / 2, this.dimension / 2);
 }

 /**
  * @param args the command line arguments
  */
 public static void main(String[] args) {
Mandelbrot m = new Mandelbrot();
m.renderToFile("fractal.png", 1024, 1024, 0.0, 0.0, 1.0);
 }
}

Jeg hadde mye moro med detta, Wernie! Takk igjen! :D

Endret av LostOblivion
Lenke til kommentar

Jævlig bra! Dette kommer jeg helt klart til å benytte meg av, under den videre utviklingen av generatoren min. Kunne også ønsket meg å øke og minke antall iterasjoner med + og - tastene. (I steppene 256-512-1024-2048-4096-... osv) Men det skal jeg vel klare å knotte inn selv ;)

 

Werner

Endret av wernie
Lenke til kommentar

La til kode for å kontrollere dimension og maxIter variable (bytt ut med gamle keyReleased):

public void keyReleased(KeyEvent e)
 {
switch (e.getKeyCode())
{
case KeyEvent.VK_P:
  String filepath = new File("fractal.png").getAbsolutePath();

  mandelbrot.renderToFile(filepath, this.dimension, this.maxIter, this.xc, this.yc, this.zoom);
  JOptionPane.showMessageDialog(this, "Image saved to file '" + filepath + "'");
  break;
case KeyEvent.VK_PAGE_DOWN:
  maxIter = maxIter * 2;
  refreshView();
  break;
case KeyEvent.VK_PAGE_UP:
  maxIter = maxIter / 2;
  refreshView();
  break;
case KeyEvent.VK_END:
  dimension = dimension * 2;
  refreshView();
  break;
case KeyEvent.VK_HOME:
  dimension = dimension / 2;
  refreshView();
  break;
}
 }

Glad du fikk bruk for det! ;) Hva gjør egentlig maxIter?

Endret av LostOblivion
Lenke til kommentar

Det du spør om er ikke så enkelt å forklare, men jeg skal forsøke.

 

For hvert piksel i bildet vi skal generere, kjører vi metoden getPixelValueAt(). Denne metoden har en while-løkke, som potensielt kunne kverne i det uendelige. Som du ser sjekker vi om r * r + i * i <= 4. I noen tilfeller vil dette uttrykket returnere false, og løkka brytes. Verdien til n vil da inneholde antall ganger løkka kjørte, og vi benytter dette for å beregne fargen på pikselen. I noen andre tilfeller vil dette uttrykket aldri returnere false, og løkka ville bare ha fortsatt til evig tid, hvis vi ikke legger inn en sjekk på n < this.maxIter. Når n har nådd maxIter, skal den returnerte verdien fra getPixelValueAt() være null, noe jeg nå ser ikke er tilfelle i koden min. Dette gir likevel ikke noen direkte feil i bildet som blir generert så vidt jeg kan se.

 

Sånn rent praktisk vil maxIter bestemme hvor mange detaljer det er i det genererte bildet. Defaultverdien i generatoren min, 1024, er grei nok inntil du begynner å zoome ganske dypt. Da vil du se at det blir færre og færre detaljer, og grovkornete konturer. Som et eksperiment kan du jo prøve å sette ned maxIter til f.eks 64, så ser du hva jeg mener.

 

Generering av paletten er ennå ikke optimal. Hvis man justerer maxIter til en høy verdi, f.eks. 4096, blir start-bildet veldig mørkt. Dette fordi hele paletten "konsumeres" for fort, dvs at det bare er en tynn, tynn kant der fargene vises. Ting blir derimot bedre jo lenger man zoomer inn. Motsatt problem blir det hvis start-bildet ser bra ut, og man zoomer veldig langt inn. Da blir fargene ganske blasse.

 

Ting blir vel bedre etterhvert.

 

Werner

Lenke til kommentar

Oppdatert kode:

 

package javaapplication2;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.MemoryImageSource;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

/**
*
* @author en ferieklar konsulent
*/
public class Mandelbrot {

 private class Palette {

private class ColorPoint {

  private double position;
  private int red;
  private int green;
  private int blue;

  public ColorPoint() {
  }

  public ColorPoint(double position, int red, int green, int blue) {
	this.position = position;
	this.red = red;
	this.green = green;
	this.blue = blue;
  }

  public double getPosition() {
	return position;
  }

  public void setPosition(double position) {
	this.position = position;
  }

  public int getRed() {
	return red;
  }

  public void setRed(int red) {
	this.red = red;
  }

  public int getGreen() {
	return green;
  }

  public void setGreen(int green) {
	this.green = green;
  }

  public int getBlue() {
	return blue;
  }

  public void setBlue(int blue) {
	this.blue = blue;
  }
}
private int size;
private Color[] colors;
private List<ColorPoint> colorPoints;

public Palette(int size) {
  this.size = size;
  this.colors = new Color[this.size];
  this.colorPoints = new ArrayList<ColorPoint>();
}

public void addColor(double position, int red, int green, int blue) {
  this.colorPoints.add(new ColorPoint(position, red, green, blue));
}

public void createPalette() {
  int index = 0;
  for (int n = 0; n < this.colorPoints.size() - 1; n++) {
	ColorPoint colorPoint1 = this.colorPoints.get(n);
	ColorPoint colorPoint2 = this.colorPoints.get(n + 1);

	// The start and stop indices in the color array for this transition
	double startIndex = (colorPoint1.position * this.size * 1.0);
	double stopIndex = (colorPoint2.position * this.size * 1.0);

	// The starting color components for this transition
	float red = colorPoint1.red;
	float green = colorPoint1.green;
	float blue = colorPoint1.blue;

	for (double i = startIndex; i < stopIndex; i++) {
	  //System.out.println(i + ", " + Math.round(red) + ", " + Math.round(green) + ", " + Math.round(blue));
	  this.colors[index++] = new Color((int) red, (int) green, (int) blue);
	  red += ((colorPoint2.red - colorPoint1.red) / ((stopIndex - startIndex) * 1.0));
	  green += ((colorPoint2.green - colorPoint1.green) / ((stopIndex - startIndex) * 1.0));
	  blue += ((colorPoint2.blue - colorPoint1.blue) / ((stopIndex - startIndex) * 1.0));
	}
  }
}

public Color getColorAt(int index) {
  return this.colors[index % this.size];
}
 }

 private int dimension;
 private int maxIter;
 private double xc;
 private double yc;
 private double zoom;
 private int[] bitmap;
 private Palette palette;
 private double logEscapeRadius = Math.log(4);
 private int alias = 2;

 /**
  *
  */
 public Mandelbrot() {
 }

 /*
  * This method returns the pixel at the given position
  */
 private int getPixelValueAt(int x, int y) {
// Check to see if pixel value is already calculated
if (bitmap[x + y * this.dimension] == -1) {
  double _x = (x - this.dimension / 2.0) / (this.zoom * this.dimension / 4.0) + xc;
  double _y = (y - this.dimension / 2.0) / (this.zoom * this.dimension / 4.0) + yc;

  double r = _x;
  double i = _y;

  double rTemp;

  int n = 0;

  while ((r * r + i * i <= 4) && (n < this.maxIter)) {
	rTemp = r * r - i * i + _x;
	i = 2 * r * i + _y;
	r = rTemp;
	n = n + 1;
  }

  if (n == this.maxIter) {
	return 0;
  }

  int colorIndex = 0;

  if (n < this.maxIter) {
	rTemp = r * r - i * i + _x;
	i = 2 * r * i + _y;
	r = rTemp;
	n = n + 1;

	rTemp = r * r - i * i + _x;
	i = 2 * r * i + _y;
	r = rTemp;
	n = n + 1;

	double mu = n - (Math.log(Math.log(r * r + i * i))) / this.logEscapeRadius;
	colorIndex = (int) (mu / this.maxIter * 4096.0);
	if (colorIndex >= 4096) {
	  colorIndex = 0;
	}
	if (colorIndex < 0) {
	  colorIndex = 0;
	}

	return colorIndex;
  }

  return 0;
} else {
// The pixel value was already calculated, return old value
  return bitmap[x + y * this.dimension];
}
 }

 /*
  * This method recursively calculates the fractal
  */
 private void calculate(int x1, int y1, int x2, int y2) {
boolean isUniform = true;

int pixelValue = -2;
int last_PixelValue = -2;

for (int x = x1; x < x2; x++) {
  if (last_PixelValue == -2) {
	pixelValue = getPixelValueAt(x, y1);
  }
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x, y1);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x + y1 * this.dimension] = pixelValue;
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x, y2 - 1);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x + (y2 - 1) * this.dimension] = pixelValue;
}

for (int y = y1; y < y2; y++) {
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x2 - 1, y);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[(x2 - 1) + y * this.dimension] = pixelValue;
  last_PixelValue = pixelValue;
  pixelValue = getPixelValueAt(x1, y);
  if (pixelValue != last_PixelValue) {
	isUniform = false;
  }
  bitmap[x1 + y * this.dimension] = pixelValue;
}

if (((x2 - x1) > 2) && ((y2 - y1) > 2)) {
  if (isUniform) {
	for (int y = y1; y < y2; y++) {
	  for (int x = x1; x < x2; x++) {
		bitmap[x + y * this.dimension] = pixelValue;
	  }
	}
  } else {
	calculate(x1, y1, x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2); // Q1
	calculate(x1 + (x2 - x1) / 2, y1, x2, y1 + (y2 - y1) / 2); // Q2
	calculate(x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2, x2, y2); // Q3
	calculate(x1, y1 + (y2 - y1) / 2, x1 + (x2 - x1) / 2, y2); // Q4
  }
}
 }

 /*
  * This method creates an Image object out of the bitmap data
  */
 private Image getImage() {

this.palette = new Palette(4096);

this.palette.addColor(0.0, 0, 0, 0);
this.palette.addColor(0.25, 255, 0, 0);
this.palette.addColor(0.5, 255, 255, 0);
this.palette.addColor(0.75, 255, 0, 0);
this.palette.addColor(1.0, 0, 0, 0);

this.palette.createPalette();

for (int n = 0; n < this.dimension * this.dimension; n++) {
  this.bitmap[n] = this.palette.getColorAt(this.bitmap[n]).getRGB();
}

MemoryImageSource imageSource = new MemoryImageSource(this.dimension, this.dimension, this.bitmap, 0, this.dimension);
return Toolkit.getDefaultToolkit().createImage(imageSource);
 }

 /*
  * This method converts an Image object to a BufferedImage object
  */
 private BufferedImage toBufferedImage(Image image) {
image = new ImageIcon(image).getImage();

BufferedImage bImg = null;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
try {
  GraphicsDevice gd = ge.getDefaultScreenDevice();
  GraphicsConfiguration gc = gd.getDefaultConfiguration();
  bImg = gc.createCompatibleImage(
		  image.getWidth(null), image.getHeight(null), Transparency.OPAQUE);
} catch (HeadlessException e) {
}

if (bImg == null) {
// Create a buffered image using the default color model
  int type = BufferedImage.TYPE_INT_RGB;
  bImg = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
}

Graphics2D g = bImg.createGraphics();

g.drawImage(image, 0, 0, null);
g.dispose();

return bImg;
 }

 /*
  * This method smoothly returns a resized and smoothed BufferedImage
  */
 private BufferedImage resizedImage(BufferedImage image, int width, int height) {
Image temp1 = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage resizedImage = new BufferedImage(width, height,
		BufferedImage.TYPE_INT_ARGB);
resizedImage.getGraphics().drawImage(temp1, 0, 0, null);

return resizedImage;
 }

 /**
  * Renders the fractal to file
  *
  * @param fileName the filename
  * @param dimension width of image
  * @param maxIter maximum iterations
  * @param xc center x coordinate
  * @param yc center y coordinate
  * @param zoom zoom factor
  */
 public void renderToFile(String fileName, int dimension, int maxIter, double xc, double yc, double zoom) {
this.dimension = dimension;
this.maxIter = maxIter;
this.xc = xc;
this.yc = yc;
this.zoom = zoom;

// Initialize bitmap buffer
bitmap = new int[this.dimension * this.dimension];
for (int y = 0; y < this.dimension; y++) {
  for (int x = 0; x < this.dimension; x++) {
	bitmap[x + y * this.dimension] = -1;
  }
}

long startTime = System.currentTimeMillis();
this.calculate(0, 0, this.dimension, this.dimension);

System.out.println("Processing took " + (System.currentTimeMillis() * 1.0 - startTime * 1.0) / 1000 + " seconds");

try {
// Save as PNG
  File file = new File(fileName);

  ImageIO.write(this.resizedImage(this.toBufferedImage(this.getImage()), this.dimension, this.dimension), "png", file);
} catch (IOException e) {
  e.printStackTrace();
}
 }

 /**
  * Renders the fractal and returns it as a BufferedImage
  *
  * @param dimension width of image
  * @param maxIter maximum iterations
  * @param xc center x coordinate
  * @param yc center y coordinate
  * @param zoom zoom factor
  */
 public BufferedImage renderToImage(int dimension, int maxIter, double xc, double yc, double zoom) {
this.dimension = dimension * this.alias;
this.maxIter = maxIter;
this.xc = xc;
this.yc = yc;
this.zoom = zoom;

// Initialize bitmap buffer
bitmap = new int[this.dimension * this.dimension];
for (int y = 0; y < this.dimension; y++) {
  for (int x = 0; x < this.dimension; x++) {
	bitmap[x + y * this.dimension] = -1;
  }
}

long startTime = System.currentTimeMillis();
this.calculate(0, 0, this.dimension, this.dimension);

System.out.println("Processing took " + (System.currentTimeMillis() * 1.0 - startTime * 1.0) / 1000 + " seconds");

return this.resizedImage(this.toBufferedImage(this.getImage()), this.dimension / this.alias, this.dimension / this.alias);
 }
}

 

Werner

Lenke til kommentar
Hmmm, nå ble jo rimelig mye annerledes...og rødt! Og den fraktalen i andre post er ikke like fin. :/

 

Jeg driver og jobber med palett-håndteringen, så ting ser kanskje ikke helt optimaklt ut :-/

 

Werner

Lenke til kommentar

Processing took 2.172seconds
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.awt.image.DataBufferInt.<init>(DataBufferInt.java:41)
at java.awt.image.Raster.createPackedRaster(Raster.java:458)
at java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1015)
at sun.awt.X11GraphicsConfig.createCompatibleImage(X11GraphicsConfig.java:185)
at java.awt.GraphicsConfiguration.createCompatibleImage(GraphicsConfiguration.java:155)
at Mandelbrot.toBufferedImage(Mandelbrot.java:155)
at Mandelbrot.renderToFile(Mandelbrot.java:228)
at Mandelbrot.main(Mandelbrot.java:239)

 

Kompilerte og kjørte koden i første posten, noen som vet hva jeg gjør galt?

Lenke til kommentar
Processing took 2.172seconds
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.awt.image.DataBufferInt.<init>(DataBufferInt.java:41)
at java.awt.image.Raster.createPackedRaster(Raster.java:458)
at java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1015)
at sun.awt.X11GraphicsConfig.createCompatibleImage(X11GraphicsConfig.java:185)
at java.awt.GraphicsConfiguration.createCompatibleImage(GraphicsConfiguration.java:155)
at Mandelbrot.toBufferedImage(Mandelbrot.java:155)
at Mandelbrot.renderToFile(Mandelbrot.java:228)
at Mandelbrot.main(Mandelbrot.java:239)

 

Kompilerte og kjørte koden i første posten, noen som vet hva jeg gjør galt?

 

Du kan prøve å sette VM-opsjonen -Xmx1024m i IDE'en du bruker, eventuelt sette ned verdien etter filnavnet i main-metoden, fra 1024 til f.eks. 512. Husk at dette tallet må være delelig med 2.

 

Werner

Lenke til kommentar

Kan du ikke bytte ut

	  if ((r * r + i * i <= 4)) {
	return 0;
  } else {
	return n;
  }

med

	  if (n == this.maxIter) {
	return 0;
  } else {
	return n;
  }

og spare to gangeoperasjoner og en addisjon for hver gang getPixelValueAt blir kalt? :)

 

Edit: Sparte ~200 ms på en 11 sek render på den der. :D

Endret av LostOblivion
Lenke til kommentar

Ikke alt er like optimalt, nei... ;-)

 

Så til status på generatoren, som jeg fortsatt jobber med.

 

Jeg har sett på forskjellige måter å generere paletten på, uten at jeg har kommet fram til noen konklusjon ennå. Slik det var fra børjan, og slik den er nå, er ikke optimalt. Andre ting jeg har sett på, er hva som bør gjøres når man har zoomet så langt inn at datatypen double ikke lenger gir tilstrekkelig nøyaktighet. Det er bare å zoome 14-15 ganger eller mer så ser du hva jeg mener. Og det er ikke særlig langt inn. Det er altså ikke sjans å zoome så dypt som de gjør her:

 

I Java finnes det ingen native datatype med større nøyaktighet enn double, så man må benytte BigDecimal eller lignende. Det har jeg prøvd, men det er himla tregt. Ca 800 ganger tregere for å være spesifikk.

 

Werner

Lenke til kommentar

Ja, la merke til det. Du kan jo kanskje prøve å skrive den i C eller C++? Der har du jo long double. :) Men det er vel også likedan mht hastighet og også rimelig arkitekturavhengig hvor mange bits long double benytter seg av.

 

Når alt kommer til alt, så er vel dette mer egnet for supermaskiner enn våre PCer desverre. :/

Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
  • Hvem er aktive   0 medlemmer

    • Ingen innloggede medlemmer aktive
×
×
  • Opprett ny...