1 /**************************************************************************/
2 /* Copyright 2012 Tim Day */
3 /* */
4 /* This file is part of Evolvotron */
5 /* */
6 /* Evolvotron is free software: you can redistribute it and/or modify */
7 /* it under the terms of the GNU General Public License as published by */
8 /* the Free Software Foundation, either version 3 of the License, or */
9 /* (at your option) any later version. */
10 /* */
11 /* Evolvotron is distributed in the hope that it will be useful, */
12 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 /* GNU General Public License for more details. */
15 /* */
16 /* You should have received a copy of the GNU General Public License */
17 /* along with Evolvotron. If not, see <http://www.gnu.org/licenses/>. */
18 /**************************************************************************/
19
20 /*! \file
21 \brief Implementation of class MutatableImageDisplay.
22 */
23
24 #include "mutatable_image_display.h"
25
26 #include "mutatable_image_display_big.h"
27 #include "evolvotron_main.h"
28 #include "mutatable_image_computer_task.h"
29 #include "transform_factory.h"
30 #include "function_pre_transform.h"
31 #include "function_top.h"
32
33 /*! The constructor is passed:
34 - the owning widget (probably either a QGrid or null if top-level),
35 - the EvolvotronMain providing spawn and farm services,
36 - a flag to specify whether this is a fully functional display or a restricted one (e.g no spawning for a single top level display),
37 - a flag to specify whether the offscreen buffer has fixed size (in which case scrollbars may be used),
38 - and the size of the offscreen buffer (only used if fixed_size is true).
39 Note that we use Qt's WDestructiveCode flag to ensure the destructor is called on close
40 */
MutatableImageDisplay(EvolvotronMain * mn,bool full_functionality,bool fixed_size,const QSize & sz,uint f,uint fr)41 MutatableImageDisplay::MutatableImageDisplay(EvolvotronMain* mn,bool full_functionality,bool fixed_size,const QSize& sz,uint f,uint fr)
42 :_main(mn)
43 ,_full_functionality(full_functionality)
44 ,_fixed_size(fixed_size)
45 ,_image_size(sz)
46 ,_frames(f)
47 ,_framerate(fr)
48 ,_current_frame(0)
49 ,_animate_reverse(false)
50 ,_timer(0)
51 ,_resize_in_progress(false)
52 ,_current_display_level(0)
53 ,_current_display_multisample_grid(0)
54 ,_icon_serial(0LL)
55 ,_properties(0)
56 ,_menu(0)
57 ,_menu_big(0)
58 ,_menu_item_action_lock(0)
59 ,_serial(0LL)
60 {
61 setAttribute(Qt::WA_DeleteOnClose,true);
62
63 setFocusPolicy(Qt::StrongFocus);
64
65 _properties=new DialogMutatableImageDisplay(this);
66
67 _menu=new QMenu(this);
68
69 // Most items on the context menu aren't appropriate for a window displaying a single big image
70 if (_full_functionality)
71 {
72 _menu->addAction("Respawn",this,SLOT(menupick_respawn()));
73
74 _menu->addSeparator();
75
76 _menu->addAction("Spawn",this,SLOT(menupick_spawn()));
77 _menu->addAction("Spawn recoloured",this,SLOT(menupick_spawn_recoloured()));
78
79 _menu_warped=_menu->addMenu("Spawn warped");
80 _menu_warped->addAction("Random Mix",this,SLOT(menupick_spawn_warped_random()) );
81 _menu_warped->addAction("Zoom In" ,this,SLOT(menupick_spawn_warped_zoom_in()) );
82 _menu_warped->addAction("Zoom Out" ,this,SLOT(menupick_spawn_warped_zoom_out()));
83 _menu_warped->addAction("Rotate" ,this,SLOT(menupick_spawn_warped_rotate()) );
84 _menu_warped->addAction("Pan XY" ,this,SLOT(menupick_spawn_warped_pan_xy()) );
85 _menu_warped->addAction("Pan X" ,this,SLOT(menupick_spawn_warped_pan_x()) );
86 _menu_warped->addAction("Pan Y" ,this,SLOT(menupick_spawn_warped_pan_y()) );
87 _menu_warped->addAction("Pan Z" ,this,SLOT(menupick_spawn_warped_pan_z()) );
88
89 _menu->addSeparator();
90
91 _menu_item_action_lock=_menu->addAction("Lock",this,SLOT(menupick_lock()));
92 _menu_item_action_lock->setCheckable(true);
93
94 _menu->addSeparator();
95 }
96
97 _menu_big=_menu->addMenu("Enlarge");
98 _menu_big->addAction("Resizable",this,SLOT(menupick_big_resizable()));
99 _menu_big->addSeparator();
100 _menu_big->addAction("256x256" ,this,SLOT(menupick_big_256x256()));
101 _menu_big->addAction("512x512" ,this,SLOT(menupick_big_512x512()) );
102 _menu_big->addAction("768x768" ,this,SLOT(menupick_big_768x768()) );
103 _menu_big->addAction("1024x1024",this,SLOT(menupick_big_1024x1024()));
104 _menu_big->addAction("2048x2048",this,SLOT(menupick_big_2048x2048()));
105 _menu_big->addAction("4096x4096",this,SLOT(menupick_big_4096x4096()));
106 _menu_big->addSeparator();
107 _menu_big->addAction("640x480" ,this,SLOT(menupick_big_640x480()) );
108 _menu_big->addAction("1024x768" ,this,SLOT(menupick_big_1024x768()) );
109 _menu_big->addAction("1280x960" ,this,SLOT(menupick_big_1280x960()) );
110 _menu_big->addAction("1600x1200",this,SLOT(menupick_big_1600x1200()));
111
112 _menu->addSeparator();
113
114 _menu->addAction("Save image",this,SLOT(menupick_save_image()));
115 _menu->addAction("Save function",this,SLOT(menupick_save_function()));
116
117 if (_full_functionality)
118 {
119 _menu->addAction("Load function",this,SLOT(menupick_load_function()));
120 }
121
122 _menu->addSeparator();
123 _menu->addAction("Simplify function",this,SLOT(menupick_simplify()));
124 _menu->addAction("Properties...",this,SLOT(menupick_properties()));
125
126 main().hello(this);
127
128 if (_fixed_size)
129 {
130 setGeometry(0,0,image_size().width(),image_size().height());
131 }
132
133 for (uint f=0;f<_frames;f++)
134 {
135 _offscreen_pixmaps.push_back(QPixmap());
136 }
137
138 _timer=new QTimer(this);
139 connect
140 (
141 _timer,SIGNAL(timeout()),
142 this,SLOT(frame_advance())
143 );
144 if (_frames>1) _timer->start(1000/_framerate);
145 }
146
147 /*! Destructor signs off from EvolvotronMain to prevent further attempts at completed task delivery.
148 */
~MutatableImageDisplay()149 MutatableImageDisplay::~MutatableImageDisplay()
150 {
151 assert(!_image_function.get() || _image_function->ok());
152
153 // During app shutdown EvolvotronMain might have already been destroyed so only call it if it's there.
154 // Don't use main() because it asserts non-null.
155 if (_main)
156 {
157 farm().abort_for(this);
158 main().goodbye(this);
159 }
160
161 _image_function.reset();
162 _offscreen_pixmaps.clear();
163
164 _offscreen_images.clear();
165 }
166
simplify_constants(bool single_action)167 uint MutatableImageDisplay::simplify_constants(bool single_action)
168 {
169 if (single_action) main().history().begin_action("simplify");
170
171 uint old_nodes;
172 uint old_parameters;
173 uint old_depth;
174 uint old_width;
175 real old_const;
176 _image_function->get_stats(old_nodes,old_parameters,old_depth,old_width,old_const);
177
178 main().history().replacing(this);
179
180 image_function(_image_function->simplified(),!single_action);
181
182 uint new_nodes;
183 uint new_parameters;
184 uint new_depth;
185 uint new_width;
186 real new_const;
187 _image_function->get_stats(new_nodes,new_parameters,new_depth,new_width,new_const);
188
189 const uint nodes_eliminated=old_nodes-new_nodes;
190
191 if (single_action) main().history().end_action();
192
193 if (single_action)
194 {
195 if (_icon.get()) _main->setWindowIcon(*_icon);
196
197 std::stringstream msg;
198 msg << "Eliminated " << nodes_eliminated << " redundant function nodes\n";
199 QMessageBox::information(this,"Evolvotron",msg.str().c_str(),QMessageBox::Ok);
200 }
201 return nodes_eliminated;
202 }
203
frame_advance()204 void MutatableImageDisplay::frame_advance()
205 {
206 assert(!(_current_frame==0 && _animate_reverse==true));
207 assert(!(_current_frame==_frames-1 && _animate_reverse==false));
208
209 if (_animate_reverse)
210 {
211 _current_frame--;
212 if (_current_frame==0)
213 {
214 _animate_reverse=false;
215 }
216 }
217 else
218 {
219 _current_frame++;
220 if (_current_frame==_frames-1)
221 {
222 _animate_reverse=true;
223 }
224 }
225 repaint(); // Use repaint rather than update because we really do want this to happen immediately.
226 }
227
image_function(const boost::shared_ptr<const MutatableImage> & i,bool one_of_many)228 void MutatableImageDisplay::image_function(const boost::shared_ptr<const MutatableImage>& i,bool one_of_many)
229 {
230 assert(_image_function.get()==0 || _image_function->ok());
231 assert(i.get()==0 || i->ok());
232
233 // New image, so increment serial number so any old incoming stuff which somehow avoids abort is ignored.
234 _serial++;
235
236 // This might have already been done (e.g by resizeEvent), but it can't hurt to be sure.
237 farm().abort_for(this);
238
239 // Careful: we could be passed our own existing (and already owned) image
240 // (a trick used by resize to trigger recompute & redisplay)
241 if (i.get()==0 || _image_function.get()==0 || i->serial()!=_image_function->serial())
242 {
243 _image_function=i;
244
245 // If we're part of a fullscale change then better to display back rather than something misleading
246 // but for one-offs (e.g middle mouse drag) is better not to clear.
247 if (one_of_many)
248 {
249 // Clear any existing image data - stops old animations continuing to play
250 for (uint f=0;f<_offscreen_pixmaps.size();f++)
251 _offscreen_pixmaps[f].fill(QColor(0,0,0));
252
253 // Queue a redraw
254 update();
255 }
256 }
257
258 // If we start recomputing again we need to accept any delivered images.
259 _current_display_level=static_cast<uint>(-1);
260 _current_display_multisample_grid=static_cast<uint>(-1);
261
262 // Clear up staging area... its contents are now useless
263 _offscreen_images_inbox.clear();
264
265 // Update lock status displayed in menu
266 if (_menu_item_action_lock)
267 _menu_item_action_lock->setChecked(_image_function.get() ? _image_function->locked() : false);
268
269 if (_image_function.get())
270 {
271 // Allow for displays up to 4096 pixels high or wide
272 for (int level=12;level>=0;level--)
273 {
274 const int s=(1<<level);
275 const QSize render_size(image_size()/s);
276
277 // Don't bother rendering anything less than 4x4 unless that's all there is
278 if ((render_size.width()>=4 && render_size.height()>=4) || level==0)
279 {
280 const int fragments
281 =(
282 one_of_many
283 ?
284 1
285 :
286 std::min
287 (
288 2*farm().num_threads(),
289 static_cast<uint>(render_size.height())
290 )
291 );
292
293 std::vector<uint> multisample_grid;
294 multisample_grid.push_back(1);
295 if (level==0)
296 {
297 // Only the final full resolution level gets an additional multisampling task.
298 // For 4x4 sampling, do an initial 2x2 too.
299 if (main().render_parameters().multisample_grid()==4) multisample_grid.push_back(2);
300 if (main().render_parameters().multisample_grid()>1) multisample_grid.push_back(main().render_parameters().multisample_grid());
301 }
302
303 for (std::vector<uint>::const_iterator multisample_it=multisample_grid.begin();multisample_it!=multisample_grid.end();multisample_it++)
304 {
305 //! \todo Should computed animation frames be constant or reduced c.f spatial resolution ? (Do full z resolution for now)
306 const boost::shared_ptr<const MutatableImage> task_image(_image_function);
307 assert(task_image->ok());
308
309 // Use number of samples in unfragmented image as priority
310 const uint task_priority=render_size.width()*render_size.height()*(*multisample_it)*(*multisample_it);
311
312 int fragment_start_row=0;
313 for (int f=0;f<fragments;f++)
314 {
315 const int fragment_end_row=(render_size.height()*(f+1))/fragments;
316 const boost::shared_ptr<MutatableImageComputerTask> task
317 (
318 new MutatableImageComputerTask
319 (
320 this,
321 task_image,
322 task_priority,
323 QSize(0,fragment_start_row),
324 QSize(render_size.width(),fragment_end_row-fragment_start_row),
325 render_size,
326 _frames,
327 level,
328 f,
329 fragments,
330 main().render_parameters().jittered_samples(),
331 (*multisample_it),
332 _serial
333 )
334 );
335 farm().push_todo(task);
336 fragment_start_row=fragment_end_row;
337 }
338 }
339 }
340 }
341 }
342 }
343
deliver(const boost::shared_ptr<const MutatableImageComputerTask> & task)344 void MutatableImageDisplay::deliver(const boost::shared_ptr<const MutatableImageComputerTask>& task)
345 {
346 // Ignore tasks which were aborted or which have somehow got out of order
347 // (entirely possible with multiple compute threads).
348 if (
349 task->aborted()
350 || task->serial()!=_serial
351 || task->level()>_current_display_level
352 || (task->level()==_current_display_level && task->multisample_grid()<=_current_display_multisample_grid)
353 )
354 return;
355
356 // Record the fragment in the inbox
357 const OffscreenImageInbox::key_type inbox_key(task->level(),task->multisample_grid());
358 OffscreenImageInbox::mapped_type& inbox_level=_offscreen_images_inbox[inbox_key];
359 assert(inbox_level.find(task->fragment())==inbox_level.end());
360 inbox_level[task->fragment()]=task;
361
362 if (inbox_level.size()!=task->number_of_fragments())
363 return;
364
365 // If the level is complete, we can proceed to displaying it
366
367 // Note that obsolete levels will never complete once a better one is displayed.
368 // So clear up previous levels; they're now irrelevant
369 _offscreen_images_inbox.erase
370 (
371 _offscreen_images_inbox.upper_bound(inbox_key), // upper_bound is the NEXT key from the one we're about to display
372 _offscreen_images_inbox.end()
373 );
374
375 const QSize render_size(task->whole_image_size());
376
377 if (task->number_of_fragments()==1)
378 {
379 // If there's only one fragment in the task, just use it
380 _offscreen_images=task->images();
381 }
382 else
383 {
384 // Otherwise we need to assemble the fragments together
385 _offscreen_images.resize(0);
386 for (uint f=0;f<_frames;f++)
387 {
388 _offscreen_images.push_back(QImage(render_size,QImage::Format_RGB32));
389
390 for (OffscreenImageInbox::mapped_type::const_iterator it=inbox_level.begin();it!=inbox_level.end();++it)
391 {
392 QPainter painter(&_offscreen_images.back());
393 painter.drawImage
394 (
395 QPoint((*it).second->fragment_origin().width(),(*it).second->fragment_origin().height()),
396 (*it).second->images()[f]
397 );
398 }
399 }
400 }
401
402 for (uint f=0;f<_frames;f++)
403 {
404 //! \todo Pick a scaling mode: Qt::SmoothTransformation vs Qt::FastTransformation (default) (and put it under GUI control).
405 //! \todo Expose dither mode control: Qt::DiffuseDither vs Qt::ThresholdDither
406 _offscreen_pixmaps[f]=QPixmap::fromImage(_offscreen_images[f].scaled(image_size()),(Qt::ColorOnly|Qt::ThresholdDither));
407 }
408
409 //! Note the resolution we've displayed so out-of-order low resolution images are dropped
410 _current_display_level=task->level();
411 _current_display_multisample_grid=task->multisample_grid();
412
413 // For an icon, take the first image big enough to (hopefully) be filtered down nicely.
414 // The (Qt3) converter seems to auto-create an alpha mask sometimes (images with const-color areas), which is quite cool.
415 const QSize icon_size(32,32);
416 if (task->serial()!=_icon_serial && (task->level()==0 || (render_size.width()>=2*icon_size.width() && render_size.height()>=2*icon_size.height())))
417 {
418 const QImage icon_image(_offscreen_images[_offscreen_images.size()/2].scaled(icon_size));
419
420 if (!_icon.get()) _icon=std::unique_ptr<QPixmap>(new QPixmap(icon_size));
421 (*_icon)=QPixmap::fromImage(icon_image,Qt::ColorOnly);
422
423 _icon_serial=task->serial();
424 }
425
426 // Update what's on the screen.
427 update();
428 }
429
lock(bool l,bool record_in_history)430 void MutatableImageDisplay::lock(bool l,bool record_in_history)
431 {
432 // This might be called (with l=false) with null _image during start-up reset.
433 if (_image_function && _image_function->locked()!=l)
434 {
435 if (record_in_history)
436 {
437 main().history().begin_action(l ? "lock" : "unlock");
438 main().history().replacing(this);
439 }
440 const boost::shared_ptr<const MutatableImage> new_image_function(_image_function->deepclone(l));
441 image_function(new_image_function,false);
442 if (record_in_history)
443 {
444 main().history().end_action();
445 }
446 }
447
448 _menu_item_action_lock->setChecked(l);
449 }
450
451 /*! Enlargements are implied by a non-full-functionality displays.
452 */
farm() const453 MutatableImageComputerFarm& MutatableImageDisplay::farm() const
454 {
455 return main().farm(!_full_functionality);
456 }
457
paintEvent(QPaintEvent *)458 void MutatableImageDisplay::paintEvent(QPaintEvent*)
459 {
460 // Repaint the screen from the offscreen pixmaps
461 QPainter painter(this);
462 painter.drawPixmap(0,0,_offscreen_pixmaps[_current_frame]);
463
464 // If this is the first paint event after a resize we can start computing images for the new size.
465 if (_resize_in_progress)
466 {
467 image_function(_image_function,false); // A resize should really be considered one-of-many, but because the image doesn't change we seem to be able to get away with it
468 _resize_in_progress=false;
469 }
470 }
471
472 /*! In the resize event we just focus on shutting down existing compute threads
473 because they'll all have to be restarted.
474 NB Multiple resize events can be received before a repaint occurs.
475 NB There's nothing to be done for fixed size images (not even setting _resize_in_progress).
476 */
resizeEvent(QResizeEvent * event)477 void MutatableImageDisplay::resizeEvent(QResizeEvent* event)
478 {
479 // Fixed size images don't need anything doing here. We don't even set _resize_in_progress.
480 if (!_fixed_size)
481 {
482 _image_size=event->size();
483
484 // Abort all current tasks because they'll be the wrong size.
485 farm().abort_for(this);
486
487 // Resize and reset our offscreen pixmap (something to do while we wait)
488 for (uint f=0;f<_offscreen_pixmaps.size();f++)
489 {
490 _offscreen_pixmaps[f]=QPixmap(image_size());
491 _offscreen_pixmaps[f].fill(QColor(0,0,0));
492 }
493
494 // Flag for the next paintEvent to tell it a recompute can be started now.
495 _resize_in_progress=true;
496 }
497 }
498
mousePressEvent(QMouseEvent * event)499 void MutatableImageDisplay::mousePressEvent(QMouseEvent* event)
500 {
501 if (event->button()==Qt::RightButton)
502 {
503 _menu->exec(QCursor::pos());
504 }
505 else if (event->button()==Qt::MidButton)
506 {
507 // Take a snapshot to undo back to.
508 main().history().begin_action("middle-button drag");
509 main().history().replacing(this);
510 main().history().end_action();
511
512 _mid_button_adjust_start_pos=event->pos();
513 _mid_button_adjust_last_pos=event->pos();
514 }
515 else if (_full_functionality && event->button()==Qt::LeftButton)
516 {
517 if (_icon.get()) _main->setWindowIcon(*_icon);
518
519 menupick_spawn();
520 }
521 }
522
mouseMoveEvent(QMouseEvent * event)523 void MutatableImageDisplay::mouseMoveEvent(QMouseEvent* event)
524 {
525 if (event->buttons()&Qt::MidButton)
526 {
527 if (locked())
528 {
529 QMessageBox::warning(this,"Evolvotron","Cannot middle-mouse adjust a locked image.\nUnlock and try again.");
530 }
531 else
532 {
533 const QPoint pixel_delta(event->pos()-_mid_button_adjust_last_pos);
534
535 // Build the transform caused by the adjustment
536 Transform transform=TransformIdentity();
537
538 // Shift button (no ctrl) is various zooms
539 if (event->modifiers()&Qt::ShiftModifier && !(event->modifiers()&Qt::ControlModifier))
540 {
541 // Alt-Shift is anisotropic
542 if (event->modifiers()&Qt::AltModifier)
543 {
544 // Only scale in non-degenerate cases
545 if (
546 event->pos().x()!=image_size().width()/2
547 && event->pos().y()!=image_size().height()/2
548 && _mid_button_adjust_last_pos.x()!=image_size().width()/2
549 && _mid_button_adjust_last_pos.y()!=image_size().height()/2
550 )
551 {
552 XYZ scale(
553 (event->pos().x()-image_size().width() /2) / static_cast<real>(_mid_button_adjust_last_pos.x()-image_size().width() /2),
554 (event->pos().y()-image_size().height()/2) / static_cast<real>(_mid_button_adjust_last_pos.y()-image_size().height()/2),
555 0.0
556 );
557
558 transform.basis_x(XYZ(1.0/scale.x(), 0.0,0.0));
559 transform.basis_y(XYZ( 0.0,1.0/scale.y(),0.0));
560
561 std::clog << "[Anisotropic scale]";
562 }
563 }
564 // Shift button alone is isotropic zoom
565 else
566 {
567 const real cx=image_size().width()/2.0;
568 const real cy=image_size().width()/2.0;
569
570 const real dx=event->pos().x()-cx;
571 const real dy=event->pos().y()-cy;
572
573 const real last_dx=_mid_button_adjust_last_pos.x()-cx;
574 const real last_dy=_mid_button_adjust_last_pos.y()-cy;
575
576 const real radius=sqrt(dx*dx+dy*dy);
577 const real last_radius=sqrt(last_dx*last_dx+last_dy*last_dy);
578
579 // Only scale in non-degenerate cases
580 if (radius!=0.0 && last_radius!=0.0)
581 {
582 const real scale=radius/last_radius;
583 transform.basis_x(XYZ(1.0/scale, 0.0,0.0));
584 transform.basis_y(XYZ( 0.0,1.0/scale,0.0));
585
586 std::clog << "[Isotropic scale]";
587 }
588 }
589 }
590 else if (event->modifiers()&Qt::ControlModifier)
591 {
592 // Control-alt is shear
593 if (event->modifiers()&Qt::AltModifier)
594 {
595 const real cx=image_size().width()/2.0;
596 const real cy=image_size().width()/2.0;
597
598 const real dx=(event->pos().x()-_mid_button_adjust_last_pos.x())/cx;
599 const real dy=(event->pos().y()-_mid_button_adjust_last_pos.y())/cy;
600
601 transform.basis_x(XYZ(1.0, -dy,0.0));
602 transform.basis_y(XYZ( dx,1.0,0.0));
603
604 std::clog << "[Shear]";
605 }
606 // Control button only is rotate
607 else
608 {
609 const real cx=image_size().width()/2.0;
610 const real cy=image_size().width()/2.0;
611
612 const real dx=event->pos().x()-cx;
613 const real dy=event->pos().y()-cy;
614
615 const real last_dx=_mid_button_adjust_last_pos.x()-cx;
616 const real last_dy=_mid_button_adjust_last_pos.y()-cy;
617
618 const real a=atan2(dy,dx);
619 const real last_a=atan2(last_dy,last_dx);
620
621 const real rot=a-last_a;
622
623 const real sr=sin(rot);
624 const real cr=cos(rot);
625
626 transform.basis_x(XYZ( cr,sr,0.0));
627 transform.basis_y(XYZ(-sr,cr,0.0));
628
629 std::clog << "[Rotate]";
630 }
631 }
632 // Default (no interesting modifier keys detected) is panning around
633 else
634 {
635 XYZ translate(
636 (-2.0*pixel_delta.x())/image_size().width(),
637 ( 2.0*pixel_delta.y())/image_size().height(),
638 0.0
639 );
640 transform.translate(translate);
641
642 std::clog << "[Pan]";
643 }
644
645 std::unique_ptr<FunctionTop> new_root(image_function()->top().typed_deepclone());
646 new_root->concatenate_pretransform_on_right(transform);
647
648 // Install new image (triggers recompute).
649 const boost::shared_ptr<const MutatableImage> new_image_function(new MutatableImage(new_root,image_function()->sinusoidal_z(),image_function()->spheremap(),false));
650 image_function(new_image_function,false);
651
652 // Finally, record position of this event as last event
653 _mid_button_adjust_last_pos=event->pos();
654 }
655 }
656 }
657
658 /*! This slot is called by selecting the "Respawn" context menu item
659 */
menupick_respawn()660 void MutatableImageDisplay::menupick_respawn()
661 {
662 main().respawn(this);
663 }
664
665 /*! This slot is called by selecting the "Spawn" context menu item, or by clicking the image
666 */
menupick_spawn()667 void MutatableImageDisplay::menupick_spawn()
668 {
669 main().spawn_normal(this);
670 }
671
672 /*! This slot is called by selecting the "Spawn Recoloured" context menu item
673 */
menupick_spawn_recoloured()674 void MutatableImageDisplay::menupick_spawn_recoloured()
675 {
676 main().spawn_recoloured(this);
677 }
678
679 /*! This slot is called by selecting the "Spawn Warped/Random" context menu item
680 */
menupick_spawn_warped_random()681 void MutatableImageDisplay::menupick_spawn_warped_random()
682 {
683 TransformFactoryRandomWarpXY transform_factory;
684
685 main().spawn_warped(this,transform_factory);
686 }
687
menupick_spawn_warped_zoom_in()688 void MutatableImageDisplay::menupick_spawn_warped_zoom_in()
689 {
690 TransformFactoryRandomScaleXY transform_factory(-2.0,0.0);
691
692 main().spawn_warped(this,transform_factory);
693 }
694
menupick_spawn_warped_zoom_out()695 void MutatableImageDisplay::menupick_spawn_warped_zoom_out()
696 {
697 TransformFactoryRandomScaleXY transform_factory(0.0,2.0);
698
699 main().spawn_warped(this,transform_factory);
700 }
701
menupick_spawn_warped_rotate()702 void MutatableImageDisplay::menupick_spawn_warped_rotate()
703 {
704 TransformFactoryRandomRotateZ transform_factory;
705
706 main().spawn_warped(this,transform_factory);
707 }
708
menupick_spawn_warped_pan_xy()709 void MutatableImageDisplay::menupick_spawn_warped_pan_xy()
710 {
711 TransformFactoryRandomTranslateXYZ transform_factory(XYZ(0.0,0.0,0.0),XYZ(1.0,1.0,0.0));
712
713 main().spawn_warped(this,transform_factory);
714 }
715
menupick_spawn_warped_pan_x()716 void MutatableImageDisplay::menupick_spawn_warped_pan_x()
717 {
718 TransformFactoryRandomTranslateXYZ transform_factory(XYZ(0.0,0.0,0.0),XYZ(1.0,0.0,0.0));
719
720 main().spawn_warped(this,transform_factory);
721 }
722
menupick_spawn_warped_pan_y()723 void MutatableImageDisplay::menupick_spawn_warped_pan_y()
724 {
725 TransformFactoryRandomTranslateXYZ transform_factory(XYZ(0.0,0.0,0.0),XYZ(0.0,1.0,0.0));
726
727 main().spawn_warped(this,transform_factory);
728 }
729
menupick_spawn_warped_pan_z()730 void MutatableImageDisplay::menupick_spawn_warped_pan_z()
731 {
732 TransformFactoryRandomTranslateXYZ transform_factory(XYZ(0.0,0.0,0.0),XYZ(0.0,0.0,1.0));
733
734 main().spawn_warped(this,transform_factory);
735 }
736
737 /*! This slot is called by selecting the "Lock" context menu item.
738 It stops the image from being overwritten by a new image.
739 */
menupick_lock()740 void MutatableImageDisplay::menupick_lock()
741 {
742 lock(!_image_function->locked(),true);
743 }
744
menupick_simplify()745 void MutatableImageDisplay::menupick_simplify()
746 {
747 simplify_constants(true);
748 }
749
750
751 /*! Saves image (unless the image is not full resolution yet, in which case an informative dialog is generated.
752 */
menupick_save_image()753 void MutatableImageDisplay::menupick_save_image()
754 {
755 if (_icon.get()) _main->setWindowIcon(*_icon);
756
757 std::clog << "Save requested...\n";
758
759 if (_current_display_level!=0 || _current_display_multisample_grid!=main().render_parameters().multisample_grid())
760 {
761 QMessageBox::information(this,"Evolvotron","The selected image has not yet been generated at maximum resolution.\nPlease try again later.");
762 }
763 else
764 {
765 const QString save_filename=QFileDialog::getSaveFileName
766 (
767 this,
768 "Save image to a PNG or PPM file",
769 ".",
770 "Images (*.png *.ppm)"
771 );
772
773 if (!save_filename.isEmpty())
774 {
775 QString save_format="PNG";
776 if (save_filename.toUpper().endsWith(".PPM"))
777 {
778 save_format="PPM";
779 }
780 else if (save_filename.toUpper().endsWith(".PNG"))
781 {
782 save_format="PNG";
783 }
784 else
785 {
786 QMessageBox::warning
787 (
788 this,
789 "Evolvotron",
790 QString("Unrecognised file suffix.\nFile will be written in ")+save_format+QString(" format.")
791 );
792 }
793
794 for (uint f=0;f<_offscreen_images.size();f++)
795 {
796 QString actual_save_filename(save_filename);
797
798 if (_offscreen_images.size()>1)
799 {
800 QString frame_component;
801 frame_component.sprintf(".f%06d",f);
802 int insert_point=save_filename.lastIndexOf(QString("."));
803 if (insert_point==-1)
804 {
805 actual_save_filename.append(frame_component);
806 }
807 else
808 {
809 actual_save_filename.insert(insert_point,frame_component);
810 }
811 }
812
813 if (!_offscreen_images[f].save(actual_save_filename,save_format.toLocal8Bit()))
814 {
815 QMessageBox::critical(this,"Evolvotron","Failed to write file "+actual_save_filename);
816 if (f<_offscreen_images.size()-1)
817 {
818 QMessageBox::critical(this,"Evolvotron","Not attempting to save remaining images in animation");
819 }
820 break;
821 }
822 }
823 }
824 }
825 std::clog << "...save done\n";
826 }
827
menupick_save_function()828 void MutatableImageDisplay::menupick_save_function()
829 {
830 if (_icon.get()) _main->setWindowIcon(*_icon);
831
832 const QString save_filename=QFileDialog::getSaveFileName
833 (
834 this,
835 "Save image function to an XML file",
836 ".",
837 "Functions (*.xml)"
838 );
839
840 if (!save_filename.isEmpty())
841 {
842 std::ofstream file(save_filename.toLocal8Bit());
843 _image_function->save_function(file);
844 file.flush();
845 if (!file)
846 {
847 QMessageBox::critical(this,"Evolvotron","File write failed");
848 }
849 }
850 }
851
load_function_file(const QString & load_filename)852 void MutatableImageDisplay::load_function_file(const QString& load_filename)
853 {
854 const std::string filename(load_filename.toLocal8Bit());
855 std::ifstream file(filename.c_str());
856
857 if (!file)
858 {
859 QMessageBox::critical(
860 this,
861 "Evolvotron",
862 ("Filename '"+filename+"' could not be opened\n").c_str()
863 );
864 }
865 else
866 {
867 std::string report;
868 boost::shared_ptr<const MutatableImage> new_image_function(MutatableImage::load_function(_main->mutation_parameters().function_registry(),file,report));
869
870 if (new_image_function.get()==0)
871 {
872 QMessageBox::critical(
873 this,
874 "Evolvotron",
875 ("Function in filename '"+filename+"' not loaded due to errors:\n"+report).c_str()
876 );
877 }
878 else
879 {
880 if (!report.empty())
881 {
882 QMessageBox::warning(
883 this,
884 "Evolvotron",
885 ("Function in filename '"+filename+"' +loaded with warnings:\n"+report).c_str(),
886 QMessageBox::Ok,
887 QMessageBox::NoButton
888 );
889 }
890
891 main().history().begin_action("load");
892 main().history().replacing(this);
893 main().history().end_action();
894 image_function(new_image_function,false);
895 }
896 }
897 }
898
menupick_load_function()899 void MutatableImageDisplay::menupick_load_function()
900 {
901 const QString load_filename=QFileDialog::getOpenFileName
902 (
903 this,
904 "Load image function from an XML file",
905 ".",
906 "Functions (*.xml)"
907 );
908
909 if (!load_filename.isEmpty())
910 {
911 load_function_file(load_filename);
912 }
913 }
914
menupick_big_resizable()915 void MutatableImageDisplay::menupick_big_resizable()
916 {
917 spawn_big(false,QSize(0,0));
918 }
919
menupick_big_640x480()920 void MutatableImageDisplay::menupick_big_640x480()
921 {
922 spawn_big(true,QSize(640,480));
923 }
924
menupick_big_1024x768()925 void MutatableImageDisplay::menupick_big_1024x768()
926 {
927 spawn_big(true,QSize(1024,768));
928 }
929
menupick_big_1280x960()930 void MutatableImageDisplay::menupick_big_1280x960()
931 {
932 spawn_big(true,QSize(1280,960));
933 }
934
menupick_big_1600x1200()935 void MutatableImageDisplay::menupick_big_1600x1200()
936 {
937 spawn_big(true,QSize(1600,1200));
938 }
939
menupick_big_256x256()940 void MutatableImageDisplay::menupick_big_256x256()
941 {
942 spawn_big(true,QSize(256,256));
943 }
944
menupick_big_512x512()945 void MutatableImageDisplay::menupick_big_512x512()
946 {
947 spawn_big(true,QSize(512,512));
948 }
949
menupick_big_768x768()950 void MutatableImageDisplay::menupick_big_768x768()
951 {
952 spawn_big(true,QSize(768,768));
953 }
954
menupick_big_1024x1024()955 void MutatableImageDisplay::menupick_big_1024x1024()
956 {
957 spawn_big(true,QSize(1024,1024));
958 }
959
menupick_big_2048x2048()960 void MutatableImageDisplay::menupick_big_2048x2048()
961 {
962 spawn_big(true,QSize(2048,2048));
963 }
964
menupick_big_4096x4096()965 void MutatableImageDisplay::menupick_big_4096x4096()
966 {
967 spawn_big(true,QSize(4096,4096));
968 }
969
menupick_properties()970 void MutatableImageDisplay::menupick_properties()
971 {
972 uint total_nodes;
973 uint total_parameters;
974 uint depth;
975 uint width;
976 real proportion_constant;
977
978 image_function()->get_stats(total_nodes,total_parameters,depth,width,proportion_constant);
979
980 std::stringstream msg;
981 msg << " " << total_nodes << "\t function nodes\n";
982 msg << " " << total_parameters << "\t parameters\n";
983 msg << " " << depth << "\t maximum depth\n";
984 msg << " " << width << "\t width\n";
985 msg << " " << std::setprecision(3) << 100.0*proportion_constant << "%\t constant\n";
986
987 std::stringstream xml;
988 image_function()->save_function(xml);
989
990 _properties->set_content(msg.str(),xml.str());
991 if (_icon.get()) _properties->setWindowIcon(*_icon);
992 _properties->exec();
993 }
994
995 /*! Create an image display with no parent: becomes a top level window
996 Disable full menu functionality because there's less we can do with a single image (e.g no spawn_target)
997 */
spawn_big(bool scrollable,const QSize & sz)998 void MutatableImageDisplay::spawn_big(bool scrollable,const QSize& sz)
999 {
1000 MutatableImageDisplayBig*const top_level_widget=new MutatableImageDisplayBig(&main());
1001 top_level_widget->setLayout(new QVBoxLayout);
1002 if (_icon.get()) top_level_widget->setWindowIcon(*_icon);
1003
1004 MutatableImageDisplay* display=0;
1005
1006 if (scrollable)
1007 {
1008 QScrollArea*const scrollview=new QScrollArea;
1009 top_level_widget->layout()->addWidget(scrollview);
1010 display=new MutatableImageDisplay(&main(),false,true,sz,_frames,_framerate);
1011 scrollview->setWidget(display);
1012 }
1013 else
1014 {
1015 display=new MutatableImageDisplay(&main(),false,false,QSize(0,0),_frames,_framerate);
1016 top_level_widget->layout()->addWidget(display);
1017 }
1018
1019 top_level_widget->show();
1020
1021 //Propagate full screen mode
1022 if (main().isFullScreen()) top_level_widget->showFullScreen();
1023
1024 // Fire up image calculation
1025 display->image_function(_image_function,false);
1026 }
1027