
prng.image.Fractal Maven / Gradle / Ivy
package prng.image;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.Random;
/** Create a fractal image file using a provided random number generator */
public class Fractal extends BasePainter {
/**
* Number of vertices in the TSP polygon
*/
public static final int VERTICES = 64;
/**
* Create 2D linearly-interpolated Perlin noise
*
* @param rand
* source of randomness
* @return noise values
*/
public static int[][] createNoise(Random rand) {
float[][] r = new float[128][128];
int scale = 64;
int len = 2;
while( len <= r.length ) {
// create seed points for the current scale
float[][] b = new float[len + 1][len + 1];
for(int x = 0;x <= len;x++) {
for(int y = 0;y <= len;y++) {
b[x][y] = rand.nextFloat() * scale;
}
}
// interpolate the seed points to the grid
int v = 128 / len;
for(int x = 0;x < len;x++) {
for(int y = 0;y < len;y++) {
// corners of the seed points in this square
float b00 = b[x][y];
float b01 = b[x][y + 1];
float b10 = b[x + 1][y];
float b11 = b[x + 1][y + 1];
// for each point in the seed square
for(int i = 0;i < v;i++) {
float fi = (float) i / v;
for(int j = 0;j < v;j++) {
float fj = (float) j / v;
// calculate local contribution
r[(x * v) + i][(y * v)
+ j] += (b00 * (1 - fi) * (1 - fj))
+ (b10 * fi * (1 - fj))
+ (b01 * (1 - fi) * fj)
+ (b11 * fi * fj);
}
}
}
}
scale /= 2;
len *= 2;
}
// find max and minimum for rescaling
float max = r[0][0];
float min = r[0][0];
for(int i = 0;i < 128;i++) {
for(int j = 0;j < 128;j++) {
float v = r[i][j];
if( v > max ) {
max = v;
}
if( v < min ) {
min = v;
}
}
}
float size = max - min;
// rescale up to integer grid
int[][] ri = new int[128][128];
for(int i = 0;i < 128;i++) {
for(int j = 0;j < 128;j++) {
ri[i][j] = (int) ((255.0 * (r[i][j] - min)) / size);
}
}
return ri;
}
/**
* Create a route which is a TSP solution for a set of random points in one
* quarter of the field. The route enters and leaves on different boundaries
* of the quarter, so by reflections a complete symmetric polygon can be
* created.
*
* @param rand
* random source
* @return points for one quarter of the polygon
*/
public static Point2D[] createPoly(Random rand) {
// create the vertices
Point2D[] points = new Point2D[VERTICES + 1];
for(int i = 0;i < VERTICES;i++) {
Point2D p;
boolean isUsed;
do {
p = new Point2D.Double(rand.nextInt(256), rand.nextInt(256));
isUsed = false;
for(int j = 0;j < i;j++) {
if( p.equals(points[j]) ) {
isUsed = true;
break;
}
}
} while( isUsed );
points[i] = p;
}
// create the edge vertices at start and end
points[0].setLocation(256, rand.nextInt(256));
points[VERTICES] = new Point2D.Double(rand.nextInt(256), 256);
Point2D s1, e1, s2, e2;
// repeatedly apply the 2-opt rule to improve the TSP solution
boolean isImproved = true;
while( isImproved ) {
isImproved = false;
outer: for(int i = 1;i < (VERTICES - 1);i++) {
s1 = points[i - 1];
e1 = points[i];
double len1 = s1.distance(e1);
for(int j = i + 1;j < VERTICES;j++) {
s2 = points[j];
e2 = points[j + 1];
double origLen = len1 + s2.distance(e2);
double newLen = s1.distance(s2) + e1.distance(e2);
// if new route is shorter, reverse i to j
if( (newLen < origLen) ) {
while( i < j ) {
Point2D t = points[i];
points[i] = points[j];
points[j] = t;
i++;
j--;
}
isImproved = true;
break outer;
}
}
}
}
return points;
}
public Fractal() {
// do nothing
}
public Fractal(Random rand) {
super(rand);
}
@Override
public void create() {
// create image
BufferedImage image = new BufferedImage(512, 512,
BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = (Graphics2D) image.getGraphics();
// draw Perlin noise background
int[][] red = createNoise(rand);
int[][] green = createNoise(rand);
int[][] blue = createNoise(rand);
for(int i = 0;i < 128;i++) {
for(int j = 0;j < 128;j++) {
graphics.setColor(
new Color(red[i][j], green[i][j], blue[i][j]));
graphics.fillRect(i * 4, j * 4, 4, 4);
}
}
// create the polygon
Point2D[] points = createPoly(rand);
Path2D path = new Path2D.Double(Path2D.WIND_NON_ZERO,
(4 * VERTICES) + 2);
// draw 1st quarter
path.moveTo(points[0].getX(), points[0].getY());
for(int i = 1;i < points.length;i++) {
Point2D p = points[i];
path.lineTo(p.getX(), p.getY());
}
// draw 2nd quarter
for(int i = points.length - 2;i > 0;i--) {
Point2D p = points[i];
path.lineTo(p.getX(), 512 - p.getY());
}
// draw 3rd quarter
for(int i = 0;i < points.length;i++) {
Point2D p = points[i];
path.lineTo(512 - p.getX(), 512 - p.getY());
}
// draw final quarter
for(int i = points.length - 2;i > 0;i--) {
Point2D p = points[i];
path.lineTo(512 - p.getX(), p.getY());
}
path.closePath();
// fill the polygon with a translucent black
graphics.setColor(new Color(0, 0, 0, 64));
graphics.fill(path);
// draw the edge of the polygon in solid black
graphics.setStroke(new BasicStroke(4, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
graphics.setColor(Color.BLACK);
graphics.draw(path);
// create a 5x5 Gaussian blur filter
float[] blurMatrix = new float[] { 1, 4, 7, 4, 1, 4, 16, 26, 16, 4, 7,
26, 41, 26, 7, 4, 16, 26, 16, 4, 1, 4, 7, 4, 1 };
for(int i = 0;i < 25;i++) {
blurMatrix[i] /= 273f;
}
BufferedImageOp op = new ConvolveOp(new Kernel(5, 5, blurMatrix),
ConvolveOp.EDGE_NO_OP, null);
// blur the image
myImage = op.filter(image, null);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy