This project has retired. For details please refer to its Attic page.
FractalGenerator xref

1   ////////////////////////////////////////////////////////////////////////////////
2   /*
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   * http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   */
20  
21  /*
22   * Original code inspired by work from David Lebernight 
23   * see: http://www.gui.net/fractal.html
24   * email as requested in original source has been sent,
25   * to david@leberknight.com, but address is invalid (2012-02-07)
26   */
27  
28  package org.apache.chemistry.opencmis.util.content.fractal;
29  
30  import java.awt.Color;
31  import java.awt.Rectangle;
32  import java.awt.image.BufferedImage;
33  import java.io.ByteArrayOutputStream;
34  import java.io.IOException;
35  import java.util.HashMap;
36  import java.util.Iterator;
37  import java.util.Locale;
38  import java.util.Map;
39  import java.util.Random;
40  
41  import javax.imageio.IIOImage;
42  import javax.imageio.ImageIO;
43  import javax.imageio.ImageWriteParam;
44  import javax.imageio.ImageWriter;
45  import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
46  import javax.imageio.stream.ImageOutputStream;
47  
48  import org.apache.commons.logging.Log;
49  import org.apache.commons.logging.LogFactory;
50  
51  
52  public class FractalGenerator {
53      private static final Log LOG = LogFactory.getLog(FractalGenerator.class);
54  
55      private final static int ZOOM_STEPS_PER_BATCH = 10;
56      private final static int DEFAULT_MAX_ITERATIONS = 33;
57      private final static ComplexRectangle INITIAL_RECT = new ComplexRectangle(-2.1, 1.1, -1.3, 1.3);
58      private final static ComplexRectangle INITIAL_JULIA_RECT = new ComplexRectangle(-2.0, 2.0, -2.0, 2.0);
59      private final static int INITIAL_ITERATIONS = 33;
60  
61      // Color:
62      private Map<String, int[]> colorTable;
63      private final String COLORS_BLACK_AND_WHITE = "black & white";
64      private final String COLORS_BLUE_ICE = "blue ice";
65      private final String COLORS_FUNKY = "funky";
66      private final String COLORS_PASTEL = "pastel";
67      private final String COLORS_PSYCHEDELIC = "psychedelic";
68      private final String COLORS_PURPLE_HAZE = "purple haze";
69      private final String COLORS_RADICAL = "radical";
70      private final String COLORS_RAINBOW = "rainbow";
71      private final String COLORS_RAINBOWS = "rainbows";
72      private final String COLORS_SCINTILLATION = "scintillation";
73      private final String COLORS_WARPED = "warped";
74      private final String COLORS_WILD = "wild";
75      private final String COLORS_ZEBRA = "zebra";
76      private final String[] colorSchemes = {COLORS_BLACK_AND_WHITE, COLORS_BLUE_ICE, COLORS_FUNKY, COLORS_PASTEL,
77          COLORS_PSYCHEDELIC, COLORS_PURPLE_HAZE, COLORS_RADICAL, COLORS_RAINBOW, COLORS_RAINBOWS,
78          COLORS_SCINTILLATION, COLORS_WARPED, COLORS_WILD, COLORS_ZEBRA};
79      private final int imageHeight = 512; // default
80      private final int imageWidth = 512; // default
81      private final int numColors = 512; // colors per colormap
82      private FractalCalculator calculator;
83      private int previousIterations = 1;
84      private int maxIterations;
85      String color;
86      int counter = 0;
87      int newRowTile, newColTile;
88      int parts = 16;
89      private int stepInBatch = 0;
90      ComplexRectangle rect;
91      ComplexPoint juliaPoint;
92      
93      public FractalGenerator() {
94          reset();
95      }
96      
97      private void reset() {
98          rect = new ComplexRectangle(-1.6, -1.2, -0.1, 0.1);
99          juliaPoint = null; // new ComplexPoint();
100         maxIterations = DEFAULT_MAX_ITERATIONS;
101        
102         Random ran = new Random();
103         color = colorSchemes[ran.nextInt(colorSchemes.length)];
104         parts = ran.nextInt(13)+3;
105         LOG.debug("Parts: " + parts);
106         maxIterations = DEFAULT_MAX_ITERATIONS;
107         LOG.debug("Original rect " + ": (" + rect.getRMin() + "r," + rect.getRMax() +
108                 "r, " + rect.getIMin() + "i, " + rect.getIMax() + "i)");
109         randomizeRect(rect);
110     }
111     
112     public ByteArrayOutputStream generateFractal() throws IOException {
113         ByteArrayOutputStream bos = null;
114         
115         if (stepInBatch == ZOOM_STEPS_PER_BATCH) {
116             stepInBatch = 0;
117             reset();
118         }
119 
120         ++stepInBatch;
121         LOG.debug("Generating rect no " + stepInBatch + ": (" + rect.getRMin() + "r," + 
122                 rect.getRMax() +  "r, " + rect.getIMin() + "i, " + rect.getIMax() + "i)");
123         LOG.debug("   width: " + rect.getWidth() + " height: " + rect.getHeight());
124         bos = genFractal(rect, juliaPoint);
125 
126         double r1New = rect.getWidth() * newColTile / parts +  rect.getRMin();
127         double r2New = rect.getWidth() * (newColTile+1) / parts +  rect.getRMin();
128         double i1New =  rect.getIMax() - (rect.getHeight() * newRowTile / parts);
129         double i2New =  rect.getIMax() - (rect.getHeight() * (newRowTile+1) / parts);
130         rect.set(r1New, r2New, i1New, i2New);
131         randomizeRect(rect);
132         LOG.debug("Done generating fractals.");
133         
134         return bos;
135     }
136 
137     private void randomizeRect( ComplexRectangle rect) {
138         double jitterFactor = 0.15; // +/- 15%
139         double ran = Math.random() * jitterFactor +  (1.0 - jitterFactor);
140         double width = rect.getWidth() * ran;
141         ran = Math.random() * jitterFactor +  (1.0 - jitterFactor);
142         double height = rect.getHeight() * ran;
143         ran = Math.random() * jitterFactor +  (1.0 - jitterFactor);
144         double r1 = (rect.getWidth() - width) * ran + rect.getRMin();
145         ran = Math.random() * jitterFactor +  (1.0 - jitterFactor);
146         double i1 = (rect.getHeight() - height) * ran + rect.getIMin();
147         rect.set(r1, r1+width, i1, i1+height);
148     }
149 
150     /**
151      * Create a fractal image as JPEG in memory and return it  
152      * @param rect
153      *      rectangle of mandelbrot or julia set
154      * @param juliaPoint
155      *      point in Julia set or null
156      * @return
157      *      byte array with JPEG stream
158      * @throws IOException 
159      */
160     public ByteArrayOutputStream genFractal(ComplexRectangle rect, ComplexPoint juliaPoint) throws IOException {
161 
162         boolean isJulia = null != juliaPoint;
163         expandRectToFitImage(rect);
164         initializeColors();
165 
166         maxIterations = maybeGuessMaxIterations(maxIterations, rect, isJulia);
167         LOG.debug("using " + maxIterations + " iterations.");
168         detectDeepZoom(rect);
169 
170         calculator = new FractalCalculator(rect, maxIterations, imageWidth, imageHeight, getCurrentColorMap(),
171                 juliaPoint);
172         int[][] iterations = calculator.calcFractal();
173         BufferedImage image = calculator.mapItersToColors(iterations);
174         findNewRect(image, iterations);
175 
176         // fast method to write to a file with default options
177         // ImageIO.write((BufferedImage)(image), "jpg", new File("fractal-" + counter++ + ".jpg"));
178 
179         // create image in memory
180         ByteArrayOutputStream bos = new ByteArrayOutputStream(200*1024);
181         ImageOutputStream ios = ImageIO.createImageOutputStream(bos);
182         Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName( "jpg" );
183         ImageWriter imageWriter = writers.next();
184 
185         JPEGImageWriteParam params = new JPEGImageWriteParam( Locale.getDefault() );
186         params.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
187         params.setCompressionQuality( 0.9f );
188 
189         imageWriter.setOutput( ios );
190         imageWriter.write( null, new IIOImage( image, null, null ), params );
191         ios.close();
192 
193         // write memory block to a file
194         // String fileName = String.format(pattern, counter++);
195         // FileOutputStream outputStream = new FileOutputStream (fileName);
196         // bos.writeTo(outputStream);
197         // bos.close();
198         // outputStream.close();
199 
200         return bos;
201     }
202 
203     protected int[] getCurrentColorMap() {
204         return colorTable.get(getColor());
205     }
206 
207     protected String getColor() {
208         return color;
209     }
210 
211     protected void expandRectToFitImage(ComplexRectangle complexRect) {
212         // The complex rectangle must be scaled to fit the pixel image view.
213         // Method: compare the width/height ratios of the two rectangles.
214         double imageWHRatio = 1.0;
215         double complexWHRatio = 1.0;
216         double iMin = complexRect.getIMin();
217         double iMax = complexRect.getIMax();
218         double rMin = complexRect.getRMin();
219         double rMax = complexRect.getRMax();
220         double complexWidth = rMax - rMin;
221         double complexHeight = iMax - iMin;
222 
223         if ((imageWidth != 0) && (imageHeight != 0)) {
224             imageWHRatio = ((double) imageWidth / (double) imageHeight);
225         } else
226             return;
227 
228         if ((complexWidth != 0) && (complexHeight != 0)) {
229             complexWHRatio = complexWidth / complexHeight;
230         } else
231             return;
232 
233         if (imageWHRatio == complexWHRatio)
234             return;
235 
236         if (imageWHRatio < complexWHRatio) {
237             // Expand vertically
238             double newHeight = complexWidth / imageWHRatio;
239             double heightDifference = Math.abs(newHeight - complexHeight);
240             iMin = iMin - heightDifference / 2;
241             iMax = iMax + heightDifference / 2;
242         } else {
243             // Expand horizontally
244             double newWidth = complexHeight * imageWHRatio;
245             double widthDifference = Math.abs(newWidth - complexWidth);
246             rMin = rMin - widthDifference / 2;
247             rMax = rMax + widthDifference / 2;
248         }
249         complexRect.set(rMin, rMax, iMin, iMax);
250     }
251 
252     private int guessNewMaxIterations(ComplexRectangle cr, boolean isJulia) {
253         // The higher the zoom factor, the more iterations that are needed to
254         // see
255         // the detail. Guess at a number to produce a cool looking fractal:
256         double zoom = INITIAL_RECT.getWidth() / cr.getWidth();
257         if (zoom < 1.0) {
258             zoom = 1.0; // forces logZoom >= 0
259         }
260         double logZoom = Math.log(zoom);
261         double magnitude = (logZoom / 2.3) - 2.0; // just a guess.
262         if (magnitude < 1.0) {
263             magnitude = 1.0;
264         }
265         double iterations = INITIAL_ITERATIONS * (magnitude * logZoom + 1.0);
266         if (isJulia)
267             iterations *= 2.0; // Julia sets tend to need more iterations.
268         return (int) iterations;
269     }
270 
271     private int maybeGuessMaxIterations(int maxIterations, ComplexRectangle cr, boolean isJulia) {
272         // If the user did not change the number of iterations, make a guess...
273         if (previousIterations == maxIterations) {
274             maxIterations = guessNewMaxIterations(cr, isJulia);
275         }
276         previousIterations = maxIterations;
277         return maxIterations;
278     }
279 
280     private boolean detectDeepZoom(ComplexRectangle cr) {
281         // "Deep Zoom" occurs when the precision provided by the Java type
282         // double
283         // runs out of resolution. The use of BigDecimal is required to fix
284         // this.
285         double deltaDiv2 = cr.getWidth() / ((imageWidth) * 2.0);
286         String min = "" + (cr.getRMin());
287         String minPlus = "" + (cr.getRMin() + deltaDiv2);
288 
289         if (Double.valueOf(min).doubleValue() == Double.valueOf(minPlus).doubleValue()) {
290             LOG.warn("Deep Zoom...  Drawing resolution will be degraded ;-(");
291             return true;
292         }
293         return false;
294     }
295 
296     private void initializeColors() {
297         colorTable = new HashMap<String, int[]>();
298 
299         int red = 255;
300         int green = 255;
301         int blue = 255;
302 
303         float hue = (float) 1.0;
304         float saturation = (float) 1.0;
305         float brightness = (float) 1.0;
306 
307         // COLORS_BLACK_AND_WHITE:
308         int[] colorMap = new int[numColors];
309         for (int colorNum = numColors - 1; colorNum >= 0; colorNum--) {
310             colorMap[colorNum] = Color.white.getRGB();
311         }
312         colorTable.put(COLORS_BLACK_AND_WHITE, colorMap);
313 
314         // COLORS_BLUE_ICE:
315         blue = 255;
316         colorMap = new int[numColors];
317         for (int colorNum = numColors - 1; colorNum >= 0; colorNum--) {
318             red = (int) ((255 * (float) colorNum / numColors)) % 255;
319             green = (int) ((255 * (float) colorNum / numColors)) % 255;
320             colorMap[colorNum] = new Color(red, green, blue).getRGB();
321         }
322         colorTable.put(COLORS_BLUE_ICE, colorMap);
323 
324         // COLORS_FUNKY:
325         colorMap = new int[numColors];
326         for (int colorNum = numColors - 1; colorNum >= 0; colorNum--) {
327             red = (int) ((1024 * (float) colorNum / numColors)) % 255;
328             green = (int) ((512 * (float) colorNum / numColors)) % 255;
329             blue = (int) ((256 * (float) colorNum / numColors)) % 255;
330             colorMap[numColors - colorNum - 1] = new Color(red, green, blue).getRGB();
331         }
332         colorTable.put(COLORS_FUNKY, colorMap);
333 
334         // COLORS_PASTEL
335         brightness = (float) 1.0;
336         colorMap = new int[numColors];
337         for (int colorNum = 0; colorNum < numColors; colorNum++) {
338             hue = ((float) (colorNum * 4) / (float) numColors) % numColors;
339             saturation = ((float) (colorNum * 2) / (float) numColors) % numColors;
340             colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
341         }
342         colorTable.put(COLORS_PASTEL, colorMap);
343 
344         // COLORS_PSYCHEDELIC:
345         saturation = (float) 1.0;
346         colorMap = new int[numColors];
347         for (int colorNum = 0; colorNum < numColors; colorNum++) {
348             hue = ((float) (colorNum * 5) / (float) numColors) % numColors;
349             brightness = ((float) (colorNum * 20) / (float) numColors) % numColors;
350             colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
351         }
352         colorTable.put(COLORS_PSYCHEDELIC, colorMap);
353 
354         // COLORS_PURPLE_HAZE:
355         red = 255;
356         blue = 255;
357         colorMap = new int[numColors];
358         for (int colorNum = numColors - 1; colorNum >= 0; colorNum--) {
359             green = (int) ((255 * (float) colorNum / numColors)) % 255;
360             colorMap[numColors - colorNum - 1] = new Color(red, green, blue).getRGB();
361         }
362         colorTable.put(COLORS_PURPLE_HAZE, colorMap);
363 
364         // COLORS_RADICAL:
365         saturation = (float) 1.0;
366         colorMap = new int[numColors];
367         for (int colorNum = 0; colorNum < numColors; colorNum++) {
368             hue = ((float) (colorNum * 7) / (float) numColors) % numColors;
369             brightness = ((float) (colorNum * 49) / (float) numColors) % numColors;
370             colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
371         }
372         colorTable.put(COLORS_RADICAL, colorMap);
373 
374         // COLORS_RAINBOW:
375         saturation = (float) 1.0;
376         brightness = (float) 1.0;
377         colorMap = new int[numColors];
378         for (int colorNum = 0; colorNum < numColors; colorNum++) {
379             hue = (float) colorNum / (float) numColors;
380             colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
381         }
382         colorTable.put(COLORS_RAINBOW, colorMap);
383 
384         // COLORS_RAINBOWS:
385         saturation = (float) 1.0;
386         brightness = (float) 1.0;
387         colorMap = new int[numColors];
388         for (int colorNum = 0; colorNum < numColors; colorNum++) {
389             hue = ((float) (colorNum * 5) / (float) numColors) % numColors;
390             colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
391         }
392         colorTable.put(COLORS_RAINBOWS, colorMap);
393 
394         // COLORS_SCINTILLATION
395         brightness = (float) 1.0;
396         saturation = (float) 1.0;
397         colorMap = new int[numColors];
398         for (int colorNum = 0; colorNum < numColors; colorNum++) {
399             hue = ((float) (colorNum * 2) / (float) numColors) % numColors;
400             brightness = ((float) (colorNum * 5) / (float) numColors) % numColors;
401             colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
402         }
403         colorTable.put(COLORS_SCINTILLATION, colorMap);
404 
405         // COLORS_WARPED:
406         colorMap = new int[numColors];
407         for (int colorNum = numColors - 1; colorNum >= 0; colorNum--) {
408             red = (int) ((1024 * (float) colorNum / numColors)) % 255;
409             green = (int) ((256 * (float) colorNum / numColors)) % 255;
410             blue = (int) ((512 * (float) colorNum / numColors)) % 255;
411             colorMap[numColors - colorNum - 1] = new Color(red, green, blue).getRGB();
412         }
413         colorTable.put(COLORS_WARPED, colorMap);
414 
415         // COLORS_WILD:
416         colorMap = new int[numColors];
417         for (int colorNum = 0; colorNum < numColors; colorNum++) {
418             hue = ((float) (colorNum * 1) / (float) numColors) % numColors;
419             saturation = ((float) (colorNum * 2) / (float) numColors) % numColors;
420             brightness = ((float) (colorNum * 4) / (float) numColors) % numColors;
421             colorMap[colorNum] = Color.HSBtoRGB(hue, saturation, brightness);
422         }
423         colorTable.put(COLORS_WILD, colorMap);
424 
425         // COLORS_ZEBRA:
426         colorMap = new int[numColors];
427         for (int colorNum = 0; colorNum < numColors; colorNum++) {
428             if (colorNum % 2 == 0) {
429                 colorMap[colorNum] = Color.white.getRGB();
430                 ;
431             } else {
432                 colorMap[colorNum] = Color.black.getRGB();
433                 ;
434             }
435         }
436         colorTable.put(COLORS_ZEBRA, colorMap);
437     }
438 
439 
440     private void findNewRect(BufferedImage image, int[][] iterations) {
441 
442         int newWidth = image.getWidth() / parts;
443         int newHeight = image.getHeight() / parts;
444         int i=0, j=0;
445         int noTiles = (image.getWidth() / newWidth) * (image.getHeight() / newHeight); // equals parts but be aware of rounding errors!;
446         double[] stdDev = new double [noTiles];
447 
448         for (int y = 0; y+newHeight <= image.getHeight(); y+=newHeight) {
449             for (int x = 0; x+newWidth <= image.getWidth(); x+=newWidth) {
450                 Rectangle subRect = new Rectangle(x, y, newWidth, newHeight);
451                 stdDev[i*parts+j] = calcStdDev(iterations, subRect);
452                 ++j;
453             }
454             ++i;
455             j=0;
456         }
457 
458         // find tile with greatest std deviation:
459         double max = 0;
460         int index = 0;
461         for (i=0; i<noTiles; i++) {
462             if (stdDev[i] > max) {
463                 index = i;
464                 max = stdDev[i];
465             }
466         }
467         newRowTile = index / parts;
468         newColTile = index % parts;
469     }
470 
471     private double calcStdDev(int[][] iterations, Rectangle rect) {
472 
473         int sum=0;
474         long sumSquare=0;
475 
476         for (int x = rect.x; x < rect.x+rect.width; x+=1) {
477             for (int y = rect.y; y < rect.y+rect.height; y+=1) {
478                 int iters = iterations[x][y];
479                 sum +=iters;
480                 sumSquare +=iters*iters;
481             }
482         }
483         int count = rect.width * rect.height;
484         double mean = 0.0;
485 
486         mean = sum / count;
487         return Math.sqrt(sumSquare/count - (mean * mean));
488     }
489 
490 }