Source code: exptree/utilities/imageGenerator.java
1 /* Evolvo - Image Generator
2 * Copyright (C) 2000 Andrew Molloy
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
18
19 /*
20 * @(#)imageGenerator.java 0.1 08/19/2000
21 */
22 package exptree.utilities;
23
24 import myComponents.*;
25 import java.awt.*;
26 import java.awt.event.*;
27 import java.awt.image.*;
28 import javax.swing.*;
29 import settings.*;
30 import exptree.*;
31 import java.util.EventListener;
32 import javax.swing.event.*;
33 import java.awt.color.*;
34 /**
35 * Generates an image based on expressionTrees. Currently, this supports images in the
36 * RGB and HSB colorspaces, and therefore takes 3 expressionTrees. The image generation
37 * takes place in a thread, and does not begin until the startIt() method is called.
38 *
39 * @version 1.1 08/19/2000
40 * @author Andy Molloy
41 */
42 public class imageGenerator extends JComponent implements Runnable, Cloneable
43 {
44 /** Stores the generated image. */
45 BufferedImage image;
46 /** Local copy of the settings package. */
47 globalSettings settings;
48 /** The set of variables retrieved from the variablePackage. */
49 variable variables[];
50 variablePackage vp;
51 /** The actual expressionTrees that describe the image. */
52 expressionTree expressions[];
53 /** The image's imageWidth and imageHeight. */
54 int imageWidth, imageHeight;
55
56 Thread theThread;
57 boolean threadKeepRunning = true;
58 ColorModel colorModel;
59 WritableRaster raster;
60 boolean finished = false;
61
62 double magnification;
63 double x_translation;
64 double y_translation;
65
66 transient myProgressMonitor pm;
67
68 protected transient ChangeEvent changeEvent = null;
69 protected EventListenerList listenerList = new EventListenerList();
70
71 protected transient ChangeEvent finishedEvent = null;
72 protected EventListenerList finishedListenerList = new EventListenerList();
73
74 static private final double map_height = Math.pow(0.5, 2);
75 static private final double PI_OVER_TWO = Math.PI / 2.0;
76
77 /** Class constuctor. */
78 public imageGenerator(globalSettings s,
79 imageGeneratorParams params,
80 myProgressMonitor myPM,
81 Dimension D)
82 {
83 super();
84 settings = s;
85 vp = params.getVariablePackage();
86 variables = vp.getVariables();
87 expressions = params.getExpressions();
88 pm = myPM;
89
90 magnification = settings.getDoubleProperty("magnification");
91 x_translation = settings.getDoubleProperty("x_trans");
92 y_translation = settings.getDoubleProperty("y_trans");
93
94 int colorModel;
95 setSize(D);
96 setPreferredSize(D);
97 setImageSize(D.width, D.height);
98
99 image = initImage(D.width, D.height);
100
101 theThread = new Thread(this);
102 }
103
104 public void setProgressMonitor(myProgressMonitor p)
105 {
106 pm = p;
107 }
108
109 /** Sets the component's size, as well as the size of the image to generate. */
110 public void setSize(int w, int h)
111 {
112 super.setSize(w, h);
113 }
114
115 /** Sets the component's size, as well as the size of the image to generate. */
116 public void setSize(Dimension d)
117 {
118 super.setSize(d);
119 }
120
121 /** Sets the component's preferred size, as well as the size of the image to generate. */
122 public void setPreferredSize(Dimension d)
123 {
124 super.setPreferredSize(d);
125 }
126
127 public void setImageSize(int w, int h)
128 {
129 imageWidth = w;
130 imageHeight= h;
131 }
132
133 public void startIt()
134 {
135 synchronized ( image )
136 {
137 image = initImage(imageWidth, imageHeight);
138 }
139
140 theThread.setPriority( Thread.NORM_PRIORITY - 1);
141 theThread.start();
142 }
143
144 /** Thread which actually generates the image. */
145 public void run()
146 {
147 int x, y;
148 double tx, ty;
149 int index = 0;
150 double c1, c2, c3;
151 int ic1, ic2, ic3;
152 int offset;
153 boolean HSBFlag = settings.getStringProperty("render.colormodel").equals("HSB");
154
155 for (int i = 0; i < expressions.length; i++)
156 {
157 expressions[i].buildCache();
158 }
159
160 int data[] = new int[imageHeight * imageWidth];
161
162 for (y = 0; y < imageHeight; y++)
163 {
164 offset = y * imageWidth;
165 index = 0; // reset index for each scanline
166 ty = ((double)y / (double)imageHeight) * 2.0 - 1.0; // translate y to the range of -1.0 to 1.0
167
168 ty = (ty + y_translation) * magnification;
169 for (x = 0; x < imageWidth; x++ )
170 {
171 if (!threadKeepRunning)
172 {
173 return;
174 }
175 tx = ((double)x / (double)imageWidth) * 2.0 - 1.0; // translate x to the range of -1.0 to 1.0
176
177 tx = (tx + x_translation) * magnification;
178
179 variables[0].setVariable(tx); // Set the "x" variable to the translated x
180 variables[1].setVariable(ty); // Set the "y" variable to the translated y
181 variables[2].setVariable( Math.sqrt( (tx * tx) + (ty * ty) ) );
182 variables[3].setVariable( Math.atan2(tx, ty) );
183 // Each expressionTree is evaluated, and the results translated from [-1.0,1.0] to [0.0,1.0]
184 c1 = map(expressions[0].evaluate());
185 c2 = map(expressions[1].evaluate());
186 c3 = map(expressions[2].evaluate());
187
188 // Decide how the colors are being represented. Right now only RGB and HSB are
189 // supported. I may add others in the near future, such as Lab and Luv.
190 if ( HSBFlag )
191 {
192 data[offset + index++] = java.awt.Color.HSBtoRGB((float)c1, (float)c2, (float)c3);
193 // Java has a nice built in routine to
194 // convert from HSB to Java, so we use it.
195 } else {
196 ic1 = (int)(c1 * 255);
197 ic2 = (int)(c2 * 255);
198 ic3 = (int)(c3 * 255);
199 data[offset + index++] = (ic1 << 16) | (ic2 << 8) | ic3;
200 }
201 }
202
203 if (pm != null)
204 {
205 pm.incrementProgress(1);
206 if (pm.isCanceled())
207 {
208 synchronized ( image )
209 {
210 image.setRGB(0, 0, imageWidth, imageHeight, data, 0, imageWidth);
211 }
212
213 repaint();
214 fireStateChanged();
215
216 finished = true;
217 threadKeepRunning = false;
218 fireFinished();
219 }
220 }
221
222 theThread.yield(); // and allow other threads to do their thing...
223 }
224
225 synchronized ( image )
226 {
227 image.setRGB(0, 0, imageWidth, imageHeight, data, 0, imageWidth);
228 }
229
230 repaint();
231 fireStateChanged();
232
233 finished = true;
234 fireFinished();
235 }
236
237 public void stop()
238 {
239 threadKeepRunning = false;
240 }
241
242 public void paint(Graphics g)
243 {
244 super.paint(g);
245
246 if (image != null)
247 {
248 g.drawImage(image, 0, 0, this); // put the image on the graphics context
249 }
250 }
251
252 public void update(Graphics g)
253 {
254 paint(g);
255 }
256
257 public BufferedImage getImage()
258 {
259 synchronized ( image )
260 {
261 return image;
262 }
263 }
264
265 /**
266 * Adds a ChangeListener to the image.
267 */
268 public void addChangeListener(ChangeListener l)
269 {
270 listenerList.add(ChangeListener.class, l);
271 }
272
273 /**
274 * Removes a ChangeListener from the button.
275 */
276 public void removeChangeListener(ChangeListener l)
277 {
278 listenerList.remove(ChangeListener.class, l);
279 }
280
281 /** Notify all listeners that have registered interest for
282 * notification on this event type. The event instance
283 * is lazily created using the parameters passed into
284 * the fire method.
285 */
286 protected void fireStateChanged() {
287 this.repaint();
288 // Guaranteed to return a non-null array
289 Object[] listeners = listenerList.getListenerList();
290 // Process the listeners last to first, notifying
291 // those that are interested in this event
292 for (int i = listeners.length-2; i>=0; i-=2)
293 {
294 if (listeners[i]==ChangeListener.class)
295 {
296 // Lazily create the event:
297 if (changeEvent == null)
298 {
299 changeEvent = new ChangeEvent(this);
300 }
301 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
302 }
303 }
304 }
305
306 /**
307 * Adds a ChangeListener to the image.
308 */
309 public void addFinishedListener(ChangeListener l)
310 {
311 finishedListenerList.add(ChangeListener.class, l);
312 }
313
314 /**
315 * Removes a ChangeListener from the button.
316 */
317 public void removeFinishedListener(ChangeListener l)
318 {
319 finishedListenerList.remove(ChangeListener.class, l);
320 }
321
322 /** Notify all listeners that have registered interest for
323 * notification on this event type. The event instance
324 * is lazily created using the parameters passed into
325 * the fire method.
326 */
327 protected void fireFinished() {
328 this.repaint();
329
330 // Guaranteed to return a non-null array
331 Object[] listeners = finishedListenerList.getListenerList();
332 // Process the listeners last to first, notifying
333 // those that are interested in this event
334 for (int i = listeners.length-2; i>=0; i-=2)
335 {
336 if (listeners[i]==ChangeListener.class)
337 {
338 // Lazily create the event:
339 if (finishedEvent == null)
340 {
341 finishedEvent = new ChangeEvent(this);
342 }
343 ((ChangeListener)listeners[i+1]).stateChanged(finishedEvent);
344 }
345 }
346 }
347
348 protected Object clone()
349 {
350 imageGenerator newImgG = new imageGenerator(
351 settings,
352 new imageGeneratorParams(vp, expressions),
353 pm,
354 new Dimension(imageWidth, imageHeight));
355
356 return (Object)newImgG;
357 }
358
359 public imageGenerator getClone()
360 {
361 return (imageGenerator)clone();
362 }
363
364 private BufferedImage initImage(int width, int height)
365 {
366 return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
367 }
368
369 public boolean isFinished()
370 {
371 return finished;
372 }
373
374 public imageGeneratorParams getImageGeneratorParams()
375 {
376 return new imageGeneratorParams(vp, expressions);
377 }
378
379 public String toString()
380 {
381 StringBuffer sb = new StringBuffer("ImageGenerator: ");
382
383 for (int i = 0; i < expressions.length; i++)
384 {
385 sb.append("Expression " + i + ": [");
386 if (expressions[i] == null)
387 {
388 sb.append("null");
389 }
390 else
391 {
392 sb.append(expressions.toString());
393 }
394 sb.append("] ");
395 }
396
397 return sb.toString();
398 }
399
400 static double map(double a)
401 {
402 double a_squared = Math.pow(a, 2);
403 double distance = Math.sqrt( map_height + a_squared );
404 double angle = Math.asin(a / distance);
405 double translated = (PI_OVER_TWO - Math.abs(angle)) / PI_OVER_TWO;
406
407 return translated;
408 }
409
410 public Dimension getSize(Dimension rv)
411 {
412 if (rv == null)
413 {
414 rv = new Dimension();
415 }
416
417 rv.setSize(imageWidth, imageHeight);
418 return rv;
419 }
420 }