/**************************************************************************/ /* Copyright 2012 Tim Day */ /* */ /* This file is part of Evolvotron */ /* */ /* Evolvotron is free software: you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ /* the Free Software Foundation, either version 3 of the License, or */ /* (at your option) any later version. */ /* */ /* Evolvotron is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with Evolvotron. If not, see . */ /**************************************************************************/ /*! \file \brief Implementation of class MutatableImageDisplay. */ #include "mutatable_image_display.h" #include "mutatable_image_display_big.h" #include "evolvotron_main.h" #include "mutatable_image_computer_task.h" #include "transform_factory.h" #include "function_pre_transform.h" #include "function_top.h" /*! The constructor is passed: - the owning widget (probably either a QGrid or null if top-level), - the EvolvotronMain providing spawn and farm services, - 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), - a flag to specify whether the offscreen buffer has fixed size (in which case scrollbars may be used), - and the size of the offscreen buffer (only used if fixed_size is true). Note that we use Qt's WDestructiveCode flag to ensure the destructor is called on close */ MutatableImageDisplay::MutatableImageDisplay(EvolvotronMain* mn,bool full_functionality,bool fixed_size,const QSize& sz,uint f,uint fr) :_main(mn) ,_full_functionality(full_functionality) ,_fixed_size(fixed_size) ,_image_size(sz) ,_frames(f) ,_framerate(fr) ,_current_frame(0) ,_animate_reverse(false) ,_timer(0) ,_resize_in_progress(false) ,_current_display_level(0) ,_current_display_multisample_grid(0) ,_icon_serial(0LL) ,_properties(0) ,_menu(0) ,_menu_big(0) ,_menu_item_action_lock(0) ,_serial(0LL) { setAttribute(Qt::WA_DeleteOnClose,true); setFocusPolicy(Qt::StrongFocus); _properties=new DialogMutatableImageDisplay(this); _menu=new QMenu(this); // Most items on the context menu aren't appropriate for a window displaying a single big image if (_full_functionality) { _menu->addAction("Respawn",this,SLOT(menupick_respawn())); _menu->addSeparator(); _menu->addAction("Spawn",this,SLOT(menupick_spawn())); _menu->addAction("Spawn recoloured",this,SLOT(menupick_spawn_recoloured())); _menu_warped=_menu->addMenu("Spawn warped"); _menu_warped->addAction("Random Mix",this,SLOT(menupick_spawn_warped_random()) ); _menu_warped->addAction("Zoom In" ,this,SLOT(menupick_spawn_warped_zoom_in()) ); _menu_warped->addAction("Zoom Out" ,this,SLOT(menupick_spawn_warped_zoom_out())); _menu_warped->addAction("Rotate" ,this,SLOT(menupick_spawn_warped_rotate()) ); _menu_warped->addAction("Pan XY" ,this,SLOT(menupick_spawn_warped_pan_xy()) ); _menu_warped->addAction("Pan X" ,this,SLOT(menupick_spawn_warped_pan_x()) ); _menu_warped->addAction("Pan Y" ,this,SLOT(menupick_spawn_warped_pan_y()) ); _menu_warped->addAction("Pan Z" ,this,SLOT(menupick_spawn_warped_pan_z()) ); _menu->addSeparator(); _menu_item_action_lock=_menu->addAction("Lock",this,SLOT(menupick_lock())); _menu_item_action_lock->setCheckable(true); _menu->addSeparator(); } _menu_big=_menu->addMenu("Enlarge"); _menu_big->addAction("Resizable",this,SLOT(menupick_big_resizable())); _menu_big->addSeparator(); _menu_big->addAction("256x256" ,this,SLOT(menupick_big_256x256())); _menu_big->addAction("512x512" ,this,SLOT(menupick_big_512x512()) ); _menu_big->addAction("768x768" ,this,SLOT(menupick_big_768x768()) ); _menu_big->addAction("1024x1024",this,SLOT(menupick_big_1024x1024())); _menu_big->addAction("2048x2048",this,SLOT(menupick_big_2048x2048())); _menu_big->addAction("4096x4096",this,SLOT(menupick_big_4096x4096())); _menu_big->addSeparator(); _menu_big->addAction("640x480" ,this,SLOT(menupick_big_640x480()) ); _menu_big->addAction("1024x768" ,this,SLOT(menupick_big_1024x768()) ); _menu_big->addAction("1280x960" ,this,SLOT(menupick_big_1280x960()) ); _menu_big->addAction("1600x1200",this,SLOT(menupick_big_1600x1200())); _menu->addSeparator(); _menu->addAction("Save image",this,SLOT(menupick_save_image())); _menu->addAction("Save function",this,SLOT(menupick_save_function())); if (_full_functionality) { _menu->addAction("Load function",this,SLOT(menupick_load_function())); } _menu->addSeparator(); _menu->addAction("Simplify function",this,SLOT(menupick_simplify())); _menu->addAction("Properties...",this,SLOT(menupick_properties())); main().hello(this); if (_fixed_size) { setGeometry(0,0,image_size().width(),image_size().height()); } for (uint f=0;f<_frames;f++) { _offscreen_pixmaps.push_back(QPixmap()); } _timer=new QTimer(this); connect ( _timer,SIGNAL(timeout()), this,SLOT(frame_advance()) ); if (_frames>1) _timer->start(1000/_framerate); } /*! Destructor signs off from EvolvotronMain to prevent further attempts at completed task delivery. */ MutatableImageDisplay::~MutatableImageDisplay() { assert(!_image_function.get() || _image_function->ok()); // During app shutdown EvolvotronMain might have already been destroyed so only call it if it's there. // Don't use main() because it asserts non-null. if (_main) { farm().abort_for(this); main().goodbye(this); } _image_function.reset(); _offscreen_pixmaps.clear(); _offscreen_images.clear(); } uint MutatableImageDisplay::simplify_constants(bool single_action) { if (single_action) main().history().begin_action("simplify"); uint old_nodes; uint old_parameters; uint old_depth; uint old_width; real old_const; _image_function->get_stats(old_nodes,old_parameters,old_depth,old_width,old_const); main().history().replacing(this); image_function(_image_function->simplified(),!single_action); uint new_nodes; uint new_parameters; uint new_depth; uint new_width; real new_const; _image_function->get_stats(new_nodes,new_parameters,new_depth,new_width,new_const); const uint nodes_eliminated=old_nodes-new_nodes; if (single_action) main().history().end_action(); if (single_action) { if (_icon.get()) _main->setWindowIcon(*_icon); std::stringstream msg; msg << "Eliminated " << nodes_eliminated << " redundant function nodes\n"; QMessageBox::information(this,"Evolvotron",msg.str().c_str(),QMessageBox::Ok); } return nodes_eliminated; } void MutatableImageDisplay::frame_advance() { assert(!(_current_frame==0 && _animate_reverse==true)); assert(!(_current_frame==_frames-1 && _animate_reverse==false)); if (_animate_reverse) { _current_frame--; if (_current_frame==0) { _animate_reverse=false; } } else { _current_frame++; if (_current_frame==_frames-1) { _animate_reverse=true; } } repaint(); // Use repaint rather than update because we really do want this to happen immediately. } void MutatableImageDisplay::image_function(const boost::shared_ptr& i,bool one_of_many) { assert(_image_function.get()==0 || _image_function->ok()); assert(i.get()==0 || i->ok()); // New image, so increment serial number so any old incoming stuff which somehow avoids abort is ignored. _serial++; // This might have already been done (e.g by resizeEvent), but it can't hurt to be sure. farm().abort_for(this); // Careful: we could be passed our own existing (and already owned) image // (a trick used by resize to trigger recompute & redisplay) if (i.get()==0 || _image_function.get()==0 || i->serial()!=_image_function->serial()) { _image_function=i; // If we're part of a fullscale change then better to display back rather than something misleading // but for one-offs (e.g middle mouse drag) is better not to clear. if (one_of_many) { // Clear any existing image data - stops old animations continuing to play for (uint f=0;f<_offscreen_pixmaps.size();f++) _offscreen_pixmaps[f].fill(QColor(0,0,0)); // Queue a redraw update(); } } // If we start recomputing again we need to accept any delivered images. _current_display_level=static_cast(-1); _current_display_multisample_grid=static_cast(-1); // Clear up staging area... its contents are now useless _offscreen_images_inbox.clear(); // Update lock status displayed in menu if (_menu_item_action_lock) _menu_item_action_lock->setChecked(_image_function.get() ? _image_function->locked() : false); if (_image_function.get()) { // Allow for displays up to 4096 pixels high or wide for (int level=12;level>=0;level--) { const int s=(1<=4 && render_size.height()>=4) || level==0) { const int fragments =( one_of_many ? 1 : std::min ( 2*farm().num_threads(), static_cast(render_size.height()) ) ); std::vector multisample_grid; multisample_grid.push_back(1); if (level==0) { // Only the final full resolution level gets an additional multisampling task. // For 4x4 sampling, do an initial 2x2 too. if (main().render_parameters().multisample_grid()==4) multisample_grid.push_back(2); if (main().render_parameters().multisample_grid()>1) multisample_grid.push_back(main().render_parameters().multisample_grid()); } for (std::vector::const_iterator multisample_it=multisample_grid.begin();multisample_it!=multisample_grid.end();multisample_it++) { //! \todo Should computed animation frames be constant or reduced c.f spatial resolution ? (Do full z resolution for now) const boost::shared_ptr task_image(_image_function); assert(task_image->ok()); // Use number of samples in unfragmented image as priority const uint task_priority=render_size.width()*render_size.height()*(*multisample_it)*(*multisample_it); int fragment_start_row=0; for (int f=0;f task ( new MutatableImageComputerTask ( this, task_image, task_priority, QSize(0,fragment_start_row), QSize(render_size.width(),fragment_end_row-fragment_start_row), render_size, _frames, level, f, fragments, main().render_parameters().jittered_samples(), (*multisample_it), _serial ) ); farm().push_todo(task); fragment_start_row=fragment_end_row; } } } } } } void MutatableImageDisplay::deliver(const boost::shared_ptr& task) { // Ignore tasks which were aborted or which have somehow got out of order // (entirely possible with multiple compute threads). if ( task->aborted() || task->serial()!=_serial || task->level()>_current_display_level || (task->level()==_current_display_level && task->multisample_grid()<=_current_display_multisample_grid) ) return; // Record the fragment in the inbox const OffscreenImageInbox::key_type inbox_key(task->level(),task->multisample_grid()); OffscreenImageInbox::mapped_type& inbox_level=_offscreen_images_inbox[inbox_key]; assert(inbox_level.find(task->fragment())==inbox_level.end()); inbox_level[task->fragment()]=task; if (inbox_level.size()!=task->number_of_fragments()) return; // If the level is complete, we can proceed to displaying it // Note that obsolete levels will never complete once a better one is displayed. // So clear up previous levels; they're now irrelevant _offscreen_images_inbox.erase ( _offscreen_images_inbox.upper_bound(inbox_key), // upper_bound is the NEXT key from the one we're about to display _offscreen_images_inbox.end() ); const QSize render_size(task->whole_image_size()); if (task->number_of_fragments()==1) { // If there's only one fragment in the task, just use it _offscreen_images=task->images(); } else { // Otherwise we need to assemble the fragments together _offscreen_images.resize(0); for (uint f=0;f<_frames;f++) { _offscreen_images.push_back(QImage(render_size,QImage::Format_RGB32)); for (OffscreenImageInbox::mapped_type::const_iterator it=inbox_level.begin();it!=inbox_level.end();++it) { QPainter painter(&_offscreen_images.back()); painter.drawImage ( QPoint((*it).second->fragment_origin().width(),(*it).second->fragment_origin().height()), (*it).second->images()[f] ); } } } for (uint f=0;f<_frames;f++) { //! \todo Pick a scaling mode: Qt::SmoothTransformation vs Qt::FastTransformation (default) (and put it under GUI control). //! \todo Expose dither mode control: Qt::DiffuseDither vs Qt::ThresholdDither _offscreen_pixmaps[f]=QPixmap::fromImage(_offscreen_images[f].scaled(image_size()),(Qt::ColorOnly|Qt::ThresholdDither)); } //! Note the resolution we've displayed so out-of-order low resolution images are dropped _current_display_level=task->level(); _current_display_multisample_grid=task->multisample_grid(); // For an icon, take the first image big enough to (hopefully) be filtered down nicely. // The (Qt3) converter seems to auto-create an alpha mask sometimes (images with const-color areas), which is quite cool. const QSize icon_size(32,32); if (task->serial()!=_icon_serial && (task->level()==0 || (render_size.width()>=2*icon_size.width() && render_size.height()>=2*icon_size.height()))) { const QImage icon_image(_offscreen_images[_offscreen_images.size()/2].scaled(icon_size)); if (!_icon.get()) _icon=std::unique_ptr(new QPixmap(icon_size)); (*_icon)=QPixmap::fromImage(icon_image,Qt::ColorOnly); _icon_serial=task->serial(); } // Update what's on the screen. update(); } void MutatableImageDisplay::lock(bool l,bool record_in_history) { // This might be called (with l=false) with null _image during start-up reset. if (_image_function && _image_function->locked()!=l) { if (record_in_history) { main().history().begin_action(l ? "lock" : "unlock"); main().history().replacing(this); } const boost::shared_ptr new_image_function(_image_function->deepclone(l)); image_function(new_image_function,false); if (record_in_history) { main().history().end_action(); } } _menu_item_action_lock->setChecked(l); } /*! Enlargements are implied by a non-full-functionality displays. */ MutatableImageComputerFarm& MutatableImageDisplay::farm() const { return main().farm(!_full_functionality); } void MutatableImageDisplay::paintEvent(QPaintEvent*) { // Repaint the screen from the offscreen pixmaps QPainter painter(this); painter.drawPixmap(0,0,_offscreen_pixmaps[_current_frame]); // If this is the first paint event after a resize we can start computing images for the new size. if (_resize_in_progress) { 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 _resize_in_progress=false; } } /*! In the resize event we just focus on shutting down existing compute threads because they'll all have to be restarted. NB Multiple resize events can be received before a repaint occurs. NB There's nothing to be done for fixed size images (not even setting _resize_in_progress). */ void MutatableImageDisplay::resizeEvent(QResizeEvent* event) { // Fixed size images don't need anything doing here. We don't even set _resize_in_progress. if (!_fixed_size) { _image_size=event->size(); // Abort all current tasks because they'll be the wrong size. farm().abort_for(this); // Resize and reset our offscreen pixmap (something to do while we wait) for (uint f=0;f<_offscreen_pixmaps.size();f++) { _offscreen_pixmaps[f]=QPixmap(image_size()); _offscreen_pixmaps[f].fill(QColor(0,0,0)); } // Flag for the next paintEvent to tell it a recompute can be started now. _resize_in_progress=true; } } void MutatableImageDisplay::mousePressEvent(QMouseEvent* event) { if (event->button()==Qt::RightButton) { _menu->exec(QCursor::pos()); } else if (event->button()==Qt::MidButton) { // Take a snapshot to undo back to. main().history().begin_action("middle-button drag"); main().history().replacing(this); main().history().end_action(); _mid_button_adjust_start_pos=event->pos(); _mid_button_adjust_last_pos=event->pos(); } else if (_full_functionality && event->button()==Qt::LeftButton) { if (_icon.get()) _main->setWindowIcon(*_icon); menupick_spawn(); } } void MutatableImageDisplay::mouseMoveEvent(QMouseEvent* event) { if (event->buttons()&Qt::MidButton) { if (locked()) { QMessageBox::warning(this,"Evolvotron","Cannot middle-mouse adjust a locked image.\nUnlock and try again."); } else { const QPoint pixel_delta(event->pos()-_mid_button_adjust_last_pos); // Build the transform caused by the adjustment Transform transform=TransformIdentity(); // Shift button (no ctrl) is various zooms if (event->modifiers()&Qt::ShiftModifier && !(event->modifiers()&Qt::ControlModifier)) { // Alt-Shift is anisotropic if (event->modifiers()&Qt::AltModifier) { // Only scale in non-degenerate cases if ( event->pos().x()!=image_size().width()/2 && event->pos().y()!=image_size().height()/2 && _mid_button_adjust_last_pos.x()!=image_size().width()/2 && _mid_button_adjust_last_pos.y()!=image_size().height()/2 ) { XYZ scale( (event->pos().x()-image_size().width() /2) / static_cast(_mid_button_adjust_last_pos.x()-image_size().width() /2), (event->pos().y()-image_size().height()/2) / static_cast(_mid_button_adjust_last_pos.y()-image_size().height()/2), 0.0 ); transform.basis_x(XYZ(1.0/scale.x(), 0.0,0.0)); transform.basis_y(XYZ( 0.0,1.0/scale.y(),0.0)); std::clog << "[Anisotropic scale]"; } } // Shift button alone is isotropic zoom else { const real cx=image_size().width()/2.0; const real cy=image_size().width()/2.0; const real dx=event->pos().x()-cx; const real dy=event->pos().y()-cy; const real last_dx=_mid_button_adjust_last_pos.x()-cx; const real last_dy=_mid_button_adjust_last_pos.y()-cy; const real radius=sqrt(dx*dx+dy*dy); const real last_radius=sqrt(last_dx*last_dx+last_dy*last_dy); // Only scale in non-degenerate cases if (radius!=0.0 && last_radius!=0.0) { const real scale=radius/last_radius; transform.basis_x(XYZ(1.0/scale, 0.0,0.0)); transform.basis_y(XYZ( 0.0,1.0/scale,0.0)); std::clog << "[Isotropic scale]"; } } } else if (event->modifiers()&Qt::ControlModifier) { // Control-alt is shear if (event->modifiers()&Qt::AltModifier) { const real cx=image_size().width()/2.0; const real cy=image_size().width()/2.0; const real dx=(event->pos().x()-_mid_button_adjust_last_pos.x())/cx; const real dy=(event->pos().y()-_mid_button_adjust_last_pos.y())/cy; transform.basis_x(XYZ(1.0, -dy,0.0)); transform.basis_y(XYZ( dx,1.0,0.0)); std::clog << "[Shear]"; } // Control button only is rotate else { const real cx=image_size().width()/2.0; const real cy=image_size().width()/2.0; const real dx=event->pos().x()-cx; const real dy=event->pos().y()-cy; const real last_dx=_mid_button_adjust_last_pos.x()-cx; const real last_dy=_mid_button_adjust_last_pos.y()-cy; const real a=atan2(dy,dx); const real last_a=atan2(last_dy,last_dx); const real rot=a-last_a; const real sr=sin(rot); const real cr=cos(rot); transform.basis_x(XYZ( cr,sr,0.0)); transform.basis_y(XYZ(-sr,cr,0.0)); std::clog << "[Rotate]"; } } // Default (no interesting modifier keys detected) is panning around else { XYZ translate( (-2.0*pixel_delta.x())/image_size().width(), ( 2.0*pixel_delta.y())/image_size().height(), 0.0 ); transform.translate(translate); std::clog << "[Pan]"; } std::unique_ptr new_root(image_function()->top().typed_deepclone()); new_root->concatenate_pretransform_on_right(transform); // Install new image (triggers recompute). const boost::shared_ptr new_image_function(new MutatableImage(new_root,image_function()->sinusoidal_z(),image_function()->spheremap(),false)); image_function(new_image_function,false); // Finally, record position of this event as last event _mid_button_adjust_last_pos=event->pos(); } } } /*! This slot is called by selecting the "Respawn" context menu item */ void MutatableImageDisplay::menupick_respawn() { main().respawn(this); } /*! This slot is called by selecting the "Spawn" context menu item, or by clicking the image */ void MutatableImageDisplay::menupick_spawn() { main().spawn_normal(this); } /*! This slot is called by selecting the "Spawn Recoloured" context menu item */ void MutatableImageDisplay::menupick_spawn_recoloured() { main().spawn_recoloured(this); } /*! This slot is called by selecting the "Spawn Warped/Random" context menu item */ void MutatableImageDisplay::menupick_spawn_warped_random() { TransformFactoryRandomWarpXY transform_factory; main().spawn_warped(this,transform_factory); } void MutatableImageDisplay::menupick_spawn_warped_zoom_in() { TransformFactoryRandomScaleXY transform_factory(-2.0,0.0); main().spawn_warped(this,transform_factory); } void MutatableImageDisplay::menupick_spawn_warped_zoom_out() { TransformFactoryRandomScaleXY transform_factory(0.0,2.0); main().spawn_warped(this,transform_factory); } void MutatableImageDisplay::menupick_spawn_warped_rotate() { TransformFactoryRandomRotateZ transform_factory; main().spawn_warped(this,transform_factory); } void MutatableImageDisplay::menupick_spawn_warped_pan_xy() { TransformFactoryRandomTranslateXYZ transform_factory(XYZ(0.0,0.0,0.0),XYZ(1.0,1.0,0.0)); main().spawn_warped(this,transform_factory); } void MutatableImageDisplay::menupick_spawn_warped_pan_x() { TransformFactoryRandomTranslateXYZ transform_factory(XYZ(0.0,0.0,0.0),XYZ(1.0,0.0,0.0)); main().spawn_warped(this,transform_factory); } void MutatableImageDisplay::menupick_spawn_warped_pan_y() { TransformFactoryRandomTranslateXYZ transform_factory(XYZ(0.0,0.0,0.0),XYZ(0.0,1.0,0.0)); main().spawn_warped(this,transform_factory); } void MutatableImageDisplay::menupick_spawn_warped_pan_z() { TransformFactoryRandomTranslateXYZ transform_factory(XYZ(0.0,0.0,0.0),XYZ(0.0,0.0,1.0)); main().spawn_warped(this,transform_factory); } /*! This slot is called by selecting the "Lock" context menu item. It stops the image from being overwritten by a new image. */ void MutatableImageDisplay::menupick_lock() { lock(!_image_function->locked(),true); } void MutatableImageDisplay::menupick_simplify() { simplify_constants(true); } /*! Saves image (unless the image is not full resolution yet, in which case an informative dialog is generated. */ void MutatableImageDisplay::menupick_save_image() { if (_icon.get()) _main->setWindowIcon(*_icon); std::clog << "Save requested...\n"; if (_current_display_level!=0 || _current_display_multisample_grid!=main().render_parameters().multisample_grid()) { QMessageBox::information(this,"Evolvotron","The selected image has not yet been generated at maximum resolution.\nPlease try again later."); } else { const QString save_filename=QFileDialog::getSaveFileName ( this, "Save image to a PNG or PPM file", ".", "Images (*.png *.ppm)" ); if (!save_filename.isEmpty()) { QString save_format="PNG"; if (save_filename.toUpper().endsWith(".PPM")) { save_format="PPM"; } else if (save_filename.toUpper().endsWith(".PNG")) { save_format="PNG"; } else { QMessageBox::warning ( this, "Evolvotron", QString("Unrecognised file suffix.\nFile will be written in ")+save_format+QString(" format.") ); } for (uint f=0;f<_offscreen_images.size();f++) { QString actual_save_filename(save_filename); if (_offscreen_images.size()>1) { QString frame_component; frame_component.sprintf(".f%06d",f); int insert_point=save_filename.lastIndexOf(QString(".")); if (insert_point==-1) { actual_save_filename.append(frame_component); } else { actual_save_filename.insert(insert_point,frame_component); } } if (!_offscreen_images[f].save(actual_save_filename,save_format.toLocal8Bit())) { QMessageBox::critical(this,"Evolvotron","Failed to write file "+actual_save_filename); if (f<_offscreen_images.size()-1) { QMessageBox::critical(this,"Evolvotron","Not attempting to save remaining images in animation"); } break; } } } } std::clog << "...save done\n"; } void MutatableImageDisplay::menupick_save_function() { if (_icon.get()) _main->setWindowIcon(*_icon); const QString save_filename=QFileDialog::getSaveFileName ( this, "Save image function to an XML file", ".", "Functions (*.xml)" ); if (!save_filename.isEmpty()) { std::ofstream file(save_filename.toLocal8Bit()); _image_function->save_function(file); file.flush(); if (!file) { QMessageBox::critical(this,"Evolvotron","File write failed"); } } } void MutatableImageDisplay::load_function_file(const QString& load_filename) { const std::string filename(load_filename.toLocal8Bit()); std::ifstream file(filename.c_str()); if (!file) { QMessageBox::critical( this, "Evolvotron", ("Filename '"+filename+"' could not be opened\n").c_str() ); } else { std::string report; boost::shared_ptr new_image_function(MutatableImage::load_function(_main->mutation_parameters().function_registry(),file,report)); if (new_image_function.get()==0) { QMessageBox::critical( this, "Evolvotron", ("Function in filename '"+filename+"' not loaded due to errors:\n"+report).c_str() ); } else { if (!report.empty()) { QMessageBox::warning( this, "Evolvotron", ("Function in filename '"+filename+"' +loaded with warnings:\n"+report).c_str(), QMessageBox::Ok, QMessageBox::NoButton ); } main().history().begin_action("load"); main().history().replacing(this); main().history().end_action(); image_function(new_image_function,false); } } } void MutatableImageDisplay::menupick_load_function() { const QString load_filename=QFileDialog::getOpenFileName ( this, "Load image function from an XML file", ".", "Functions (*.xml)" ); if (!load_filename.isEmpty()) { load_function_file(load_filename); } } void MutatableImageDisplay::menupick_big_resizable() { spawn_big(false,QSize(0,0)); } void MutatableImageDisplay::menupick_big_640x480() { spawn_big(true,QSize(640,480)); } void MutatableImageDisplay::menupick_big_1024x768() { spawn_big(true,QSize(1024,768)); } void MutatableImageDisplay::menupick_big_1280x960() { spawn_big(true,QSize(1280,960)); } void MutatableImageDisplay::menupick_big_1600x1200() { spawn_big(true,QSize(1600,1200)); } void MutatableImageDisplay::menupick_big_256x256() { spawn_big(true,QSize(256,256)); } void MutatableImageDisplay::menupick_big_512x512() { spawn_big(true,QSize(512,512)); } void MutatableImageDisplay::menupick_big_768x768() { spawn_big(true,QSize(768,768)); } void MutatableImageDisplay::menupick_big_1024x1024() { spawn_big(true,QSize(1024,1024)); } void MutatableImageDisplay::menupick_big_2048x2048() { spawn_big(true,QSize(2048,2048)); } void MutatableImageDisplay::menupick_big_4096x4096() { spawn_big(true,QSize(4096,4096)); } void MutatableImageDisplay::menupick_properties() { uint total_nodes; uint total_parameters; uint depth; uint width; real proportion_constant; image_function()->get_stats(total_nodes,total_parameters,depth,width,proportion_constant); std::stringstream msg; msg << " " << total_nodes << "\t function nodes\n"; msg << " " << total_parameters << "\t parameters\n"; msg << " " << depth << "\t maximum depth\n"; msg << " " << width << "\t width\n"; msg << " " << std::setprecision(3) << 100.0*proportion_constant << "%\t constant\n"; std::stringstream xml; image_function()->save_function(xml); _properties->set_content(msg.str(),xml.str()); if (_icon.get()) _properties->setWindowIcon(*_icon); _properties->exec(); } /*! Create an image display with no parent: becomes a top level window Disable full menu functionality because there's less we can do with a single image (e.g no spawn_target) */ void MutatableImageDisplay::spawn_big(bool scrollable,const QSize& sz) { MutatableImageDisplayBig*const top_level_widget=new MutatableImageDisplayBig(&main()); top_level_widget->setLayout(new QVBoxLayout); if (_icon.get()) top_level_widget->setWindowIcon(*_icon); MutatableImageDisplay* display=0; if (scrollable) { QScrollArea*const scrollview=new QScrollArea; top_level_widget->layout()->addWidget(scrollview); display=new MutatableImageDisplay(&main(),false,true,sz,_frames,_framerate); scrollview->setWidget(display); } else { display=new MutatableImageDisplay(&main(),false,false,QSize(0,0),_frames,_framerate); top_level_widget->layout()->addWidget(display); } top_level_widget->show(); //Propagate full screen mode if (main().isFullScreen()) top_level_widget->showFullScreen(); // Fire up image calculation display->image_function(_image_function,false); }