TextureManager.cpp

Go to the documentation of this file.
00001 // -*- c-basic-offset: 4 -*-
00002 
00023 #include <math.h>
00024 #include <iostream>
00025 
00026 #include <config.h>
00027 
00028 #ifdef __APPLE__
00029 #include "panoinc.h"
00030 #endif
00031 
00032 
00033 #include "ViewState.h"
00034 #include "TextureManager.h"
00035 #include "huginApp.h"
00036 #include "GLPreviewFrame.h"
00037 
00038 #include "vigra/stdimage.hxx"
00039 #include "vigra/resizeimage.hxx"
00040 #ifdef HAVE_CXX11
00041 #include <functional>  // std::bind
00042 #else
00043 #include <boost/bind.hpp>
00044 #endif
00045 #include "base_wx/wxImageCache.h"
00046 #include "photometric/ResponseTransform.h"
00047 #include "panodata/Mask.h"
00048 #include <lcms2.h>
00049 
00050 // The OpenGL Extension wrangler libray will find extensions and the latest
00051 // supported OpenGL version on all platforms.
00052 #include <GL/glew.h>
00053 #include <wx/platform.h>
00054 
00055 #ifdef __WXMAC__
00056 #include <OpenGL/gl.h>
00057 #include <OpenGL/glu.h>
00058 #else
00059 #include <GL/gl.h>
00060 #include <GL/glu.h>
00061 #endif
00062 
00063 // for loading preview images
00064 #include "wx/mstream.h"
00065 #include "exiv2/exiv2.hpp"
00066 #include "exiv2/preview.hpp"
00067 
00068 TextureManager::TextureManager(HuginBase::Panorama *pano, ViewState *view_state_in)
00069 {
00070     m_pano = pano;
00071     photometric_correct = false;
00072     view_state = view_state_in;
00073 }
00074 
00075 TextureManager::~TextureManager()
00076 {
00077     // free up the textures
00078     textures.clear();
00079 }
00080 
00081 void TextureManager::DrawImage(unsigned int image_number,
00082                                unsigned int display_list)
00083 {
00084     // bind the texture that represents the given image number.
00085     TexturesMap::iterator it;
00086     HuginBase::SrcPanoImage *img_p = view_state->GetSrcImage(image_number);
00087     TextureKey key(img_p, &photometric_correct);
00088     it = textures.find(key);
00089     DEBUG_ASSERT(it != textures.end());
00090     it->second->Bind();
00091     glColor4f(1.0,1.0,1.0,1.0);
00092     if (it->second->GetUseAlpha() || it->second->GetHasActiveMasks())
00093     {
00094         // use an alpha blend if there is a alpha channel or a mask for this image.
00095         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
00096         glEnable(GL_BLEND);
00097     }
00098     if (!photometric_correct)
00099     {
00100         // When using real time photometric correction, we multiply the colour
00101         // components to get the white balance and exposure correct.
00102         HuginBase::SrcPanoImage *img = view_state->GetSrcImage(image_number);    
00103         // we adjust the intensity by using a darker colour
00104         float es = static_cast<float>(viewer_exposure / img->getExposure());
00105         float scale[4] = {static_cast<float>(es / img->getWhiteBalanceRed()),
00106                           es,
00107                           static_cast<float>(es /img->getWhiteBalanceBlue()),
00108                           1.0};
00109         glColor3fv(scale);
00110         glCallList(display_list);
00111         // Since the intensity was clamped to 0.0 - 1.0, we might overdraw a
00112         // few times to make it brighter.
00113         // FIXME If the image has areas masked out, these will also be
00114         // brightened. It might be better to do using the texture, but this
00115         // way we can only add the texture to the frame buffer, (we can't double
00116         // the intensity multiple times) and there is a cost in processing the
00117         // texture. It also won't work properly on partially transparent places.
00118         if (scale[0] > 1.0 || scale[1] > 1.0 || scale[2] >  1.0)
00119         {
00120             view_state->GetTextureManager()->DisableTexture();
00121             glEnable(GL_BLEND);
00122             glBlendFunc(GL_DST_COLOR, GL_ONE);
00123             glColor4f(1.0, 1.0, 1.0, 1.0);
00124             // double the brightness for colour components until it is almost
00125             // right, however limit it incase it is really bright.
00126             bool r, g, b;
00127             unsigned short int count = 0;
00128             while ((   (r = (scale[0] > 2.0))
00129                    || (g = (scale[1] > 2.0))
00130                    || (b = (scale[2] > 2.0)))
00131                    && count < 9)
00132             {
00133                 glColor4f(r ? 1.0 : 0.0, g ? 1.0 : 0.0, b ? 1.0 : 0.0, 1.0);
00134                 glCallList(display_list);
00135                 if (r) scale[0] /= 2.0;
00136                 if (g) scale[1] /= 2.0;
00137                 if (b) scale[2] /= 2.0;
00138                 count++;
00139             }
00140             // now add on anything remaining.
00141             if (scale[0] > 1.0 || scale[1] > 1.0 || scale[2] >  1.0)
00142             {
00143                 // clamped to 0.0-1.0, so it won't get darker.
00144                 scale[0] -= 1.0; scale[1] -= 1.0; scale[2] -= 1.0;
00145                 glColor3fv(scale);
00146                 glCallList(display_list);
00147             }
00148             glEnable(GL_TEXTURE_2D);
00149             glDisable(GL_BLEND);
00150             glColor3f(1.0, 1.0, 1.0);
00151         }
00152     } else {
00153         // we've already corrected all the photometrics, just draw once normally
00154         glCallList(display_list);
00155         if (it->second->GetUseAlpha() || it->second->GetHasActiveMasks())
00156         {
00157             glDisable(GL_BLEND);
00158         }
00159     }
00160 }
00161 
00162 unsigned int TextureManager::GetTextureName(unsigned int image_number)
00163 {
00164     // bind the texture that represents the given image number.
00165     TexturesMap::iterator it;
00166     HuginBase::SrcPanoImage *img_p = view_state->GetSrcImage(image_number);
00167     TextureKey key(img_p, &photometric_correct);
00168     it = textures.find(key);
00169     DEBUG_ASSERT(it != textures.end());
00170     return it->second->GetNumber();
00171 }
00172 
00173 void TextureManager::BindTexture(unsigned int image_number)
00174 {
00175     // bind the texture that represents the given image number.
00176     TexturesMap::iterator it;
00177     HuginBase::SrcPanoImage *img_p = view_state->GetSrcImage(image_number);
00178     TextureKey key(img_p, &photometric_correct);
00179     it = textures.find(key);
00180     DEBUG_ASSERT(it != textures.end());
00181     it->second->Bind();
00182 }
00183 
00184 void TextureManager::DisableTexture(bool maskOnly)
00185 {
00186     if(view_state->GetSupportMultiTexture())
00187     {
00188         glActiveTexture(GL_TEXTURE1);
00189         glDisable(GL_TEXTURE_2D);
00190         glActiveTexture(GL_TEXTURE0);
00191         if(!maskOnly)
00192             glDisable(GL_TEXTURE_2D);
00193     }
00194     else
00195     {
00196         if(!maskOnly)
00197             glDisable(GL_TEXTURE_2D);
00198     };
00199 };
00200 
00201 void TextureManager::Begin()
00202 {
00203     if (!photometric_correct)
00204     {
00205         // find the exposure factor to scale by.
00206         viewer_exposure = 1.0 / pow(2.0,
00207                                     m_pano->getOptions().outputExposureValue);
00208     };
00209 };
00210 
00211 void TextureManager::End()
00212 {
00213 }
00214 
00215 void TextureManager::CheckUpdate()
00216 {
00217     // The images or their lenses have changed.
00218     // Find what size we should have the textures.
00219     // Note that one image changing does affect the rest, if an image suddenly
00220     // takes up more space, the others should take up less.
00221     unsigned int num_images = m_pano->getNrOfImages();
00222     if (num_images == 0)
00223     {
00224         textures.clear();
00225         return;
00226     }
00227     // if we are doing photometric correction, and someone changed the output
00228     // exposure, all of our images are at the wrong exposure.
00229     if (photometric_correct && view_state->RequireRecalculatePhotometric())
00230     {
00231         textures.clear();
00232     }
00233     HuginBase::PanoramaOptions *dest_img = view_state->GetOptions();
00234     // Recalculuate the ideal image density if required
00235     // TODO tidy up once it works.
00236     DEBUG_INFO("Updating texture sizes.");
00237     // find the total of fields of view of the images, in degrees squared
00238     // we assume each image has the same density across all it's pixels
00239     double total_fov = 0.0;
00240     for (unsigned int image_index = 0; image_index < num_images; image_index++)
00241     {
00242         HuginBase::SrcPanoImage *src = view_state->GetSrcImage(image_index);
00243         double aspect = double(src->getSize().height())
00244                                                / double(src->getSize().width());
00245         total_fov += src->getHFOV() * aspect;
00246     };
00247     // now find the ideal density
00248     texel_density = double(GetMaxTotalTexels()) / total_fov;
00249 
00250     // now recalculate the best image sizes
00251     // The actual texture size is the biggest one possible withouth scaling the
00252     // image up in any direction. We only specifiy mipmap levels we can fit in
00253     // a given amount of texture memory, while respecting the image's FOV.
00254     int texels_used = 0;
00255     double ideal_texels_used = 0.0;
00256     for (unsigned int image_index = 0; image_index < num_images; image_index++)
00257     {    
00258         // find this texture
00259         // if it has not been created before, it will be created now.
00260         TexturesMap::iterator it;
00261         HuginBase::SrcPanoImage *img_p = view_state->GetSrcImage(image_index);
00262         TextureKey key(img_p, &photometric_correct);
00263         it = textures.find(key);
00264         /* This section would allow us to reuse textures generated when we want
00265          * to change the size. It is not used as it causes segmentation faults
00266          * under Ubuntu 8.04's "ati" graphics driver.
00267          */
00268       #if 0
00269         TextureInfo *texinfo;
00270         if (it == textures.end())
00271         {
00272             // We haven't seen this image before.
00273             // Find a size for it and make its texture.
00274             // store the power that 2 is raised to, not the actual size
00275             unsigned int max_tex_width_p = int(log2(img_p->getSize().width())),
00276                         max_tex_height_p = int(log2(img_p->getSize().height()));
00277             // check this is hardware supported.
00278             {
00279               unsigned int biggest = GetMaxTextureSizePower();
00280               if (biggest < max_tex_width_p) max_tex_width_p = biggest;
00281               if (biggest < max_tex_height_p) max_tex_height_p = biggest;
00282             }
00283             std::cout << "Texture size for image " << image_index << " is "
00284                       << (1 << max_tex_width_p) << " by "
00285                       << (1 << max_tex_height_p) << "\n";
00286             // create a new texinfo and store the texture details.
00287             std::cout << "About to create new TextureInfo for "
00288                       << img_p->getFilename()
00289                       << ".\n";
00290             std::pair<std::map<TextureKey, TextureInfo>::iterator, bool> ins;
00291             ins = textures.insert(std::pair<TextureKey, TextureInfo>
00292                                  (TextureKey(img_p, &photometric_correct),
00293                 // the key is used to identify the image with (or without)
00294                 // photometric correction parameters.
00295                               TextureInfo(max_tex_width_p, max_tex_height_p)
00296                             ));
00297             texinfo = &((ins.first)->second);
00298         }
00299         else
00300         {
00301             texinfo = &(it->second);
00302         }
00303                 
00304         // find the highest mipmap we want to use.
00305         double hfov = img_p->getHFOV(),
00306                aspect = double (texinfo->height) / double (texinfo->width),
00307                ideal_texels = texel_density * hfov * aspect,
00308                // we would like a mipmap with this size:
00309                ideal_tex_width = sqrt(ideal_texels / aspect),
00310                ideal_tex_height = aspect * ideal_tex_width;
00311         // Ideally this mipmap would bring us up to this many texels
00312         ideal_texels_used += ideal_texels;
00313         std::cout << "Ideal mip size: " << ideal_tex_width << " by "
00314                   << ideal_tex_height << "\n";
00315         // Find the smallest mipmap level that is at least this size.
00316         int max_mip_level = (texinfo->width_p > texinfo->height_p)
00317                             ? texinfo->width_p : texinfo->height_p;
00318         int mip_level = max_mip_level - ceil((ideal_tex_width > ideal_tex_height)
00319                         ? log2(ideal_tex_width) : log2(ideal_tex_height));
00320         // move to the next mipmap level if we are over budget.
00321         if ((texels_used + (1 << (texinfo->width_p + texinfo->height_p
00322                                   - mip_level * 2)))
00323             > ideal_texels_used)
00324         {
00325             // scale down
00326             mip_level ++;
00327         }
00328         // don't allow any mipmaps smaller than the 1 by 1 pixel one.
00329         if (mip_level > max_mip_level) mip_level = max_mip_level;
00330         // don't allow any mipmaps with a negative level of detail (scales up)
00331         if (mip_level < 0) mip_level = 0;
00332         // find the size of this level
00333         int mip_width_p = texinfo->width_p - mip_level,
00334             mip_height_p = texinfo->height_p - mip_level;
00335         // check if we have scaled down to a single line, and make sure we
00336         // limit the line's width to 1 pixel.
00337         if (mip_width_p < 0) mip_width_p = 0;
00338         if (mip_height_p < 0) mip_height_p = 0;
00339         
00340         // now count these texels as used- we are ignoring the smaller mip
00341         //   levels, they add 1/3 on to the size.
00342         texels_used += 1 << (mip_width_p + mip_height_p);
00343         std::cout << "biggest mipmap of image " << image_index << " is "
00344                   << (1 << mip_width_p) << " by " << (1 << mip_height_p)
00345                   << " (level " << mip_level <<").\n";
00346         std::cout << "Ideal texels used " << int(ideal_texels_used)
00347                   << ", actually used " << texels_used << ".\n\n";
00348         if (texinfo->min_lod != mip_level)
00349         {
00350             // maximum level required changed.
00351             if (texinfo->min_lod > mip_level)
00352             {
00353                 // generate more levels
00354                 texinfo->DefineLevels(mip_level,
00355                                       (texinfo->min_lod > max_mip_level) ?
00356                                       max_mip_level : texinfo->min_lod - 1,
00357                                       photometric_correct, dest_img,
00358                                       view_state->GetSrcImage(image_index));
00359             }
00360             texinfo->SetMaxLevel(mip_level);
00361             texinfo->min_lod = mip_level;
00362         }
00363     }
00364     #endif
00365     /* Instead of the above section, replace the whole texture when appropriate:
00366         */
00367         // Find a size for it
00368         double hfov = img_p->getHFOV(),
00369            aspect = double (img_p->getSize().height())
00370                                             / double (img_p->getSize().width()),
00371            ideal_texels = texel_density * hfov * aspect,
00372            // we would like a texture this size:
00373            ideal_tex_width = sqrt(ideal_texels / aspect),
00374            ideal_tex_height = aspect * ideal_tex_width;
00375         // shrink if bigger than the original, avoids scaling up excessively.
00376         if (ideal_tex_width > img_p->getSize().width())
00377                 ideal_tex_width = img_p->getSize().width();
00378         if (ideal_tex_height > img_p->getSize().height())
00379                 ideal_tex_height = img_p->getSize().height();
00380         // we will need to round up/down to a power of two
00381         // round up first, then shrink if over budget.
00382         // store the power that 2 is raised to, not the actual size
00383         unsigned int tex_width_p = int(log2(ideal_tex_width)) + 1,
00384                     tex_height_p = int(log2(ideal_tex_height)) + 1;
00385         // check this is hardware supported.
00386         {
00387           unsigned int biggest = GetMaxTextureSizePower();
00388           if (biggest < tex_width_p) tex_width_p = biggest;
00389           if (biggest < tex_height_p) tex_height_p = biggest;
00390         }
00391         
00392         // check if this is over budget.
00393         ideal_texels_used += ideal_texels; 
00394         // while the texture is over budget, shrink it
00395         while (  (texels_used + (1 << (tex_width_p + tex_height_p)))
00396             > ideal_texels_used)
00397         {
00398             // smaller aspect means the texture is wider.
00399             if ((double) (1 << tex_height_p) / (double) (1 << tex_width_p)
00400                < aspect)
00401             {
00402                 tex_width_p--;
00403             } else {
00404                 tex_height_p--;
00405             }
00406         }
00407         // we have a nice size
00408         texels_used += 1 << (tex_width_p + tex_height_p);
00409         if (   it == textures.end()
00410             || (it->second)->width_p != tex_width_p
00411             || (it->second)->height_p != tex_height_p)
00412         {
00413             // Either: 1. We haven't seen this image before
00414             //     or: 2. Our texture for this is image is the wrong size
00415             // ...therefore we make a new one the right size:
00416             //
00417             // remove duplicate key if exists
00418             TextureKey checkKey (img_p, &photometric_correct);
00419             textures.erase(checkKey);
00420 
00421             std::pair<TexturesMap::iterator, bool> ins;
00422             ins = textures.insert(std::pair<TextureKey, sharedPtrNamespace::shared_ptr<TextureInfo> >
00423                                  (TextureKey(img_p, &photometric_correct),
00424                 // the key is used to identify the image with (or without)
00425                 // photometric correction parameters.
00426                               sharedPtrNamespace::make_shared<TextureInfo>(view_state, tex_width_p, tex_height_p)
00427                             ));
00428            // create and upload the texture image
00429            TextureInfo* texinfo = (ins.first)->second.get();
00430            texinfo->DefineLevels(0, // minimum mip level
00431                                  // maximum mip level
00432                         tex_width_p > tex_height_p ? tex_width_p : tex_height_p,
00433                                 photometric_correct,
00434                                 *dest_img,
00435                                 *view_state->GetSrcImage(image_index));
00436            texinfo->DefineMaskTexture(*view_state->GetSrcImage(image_index));
00437         }
00438         else
00439         {
00440             if(view_state->RequireRecalculateMasks(image_index))
00441             {
00442                 //mask for this image has changed, also update only mask
00443                 it->second->UpdateMask(*view_state->GetSrcImage(image_index));
00444             };
00445         }
00446     }
00447     // We should remove any images' texture when it is no longer in the panorama
00448     // with the ati bug work around, we might make unneassry textures whenever 
00449     //if (photometric_correct || view_state->ImagesRemoved())
00450     {
00451         CleanTextures();
00452     }
00453 //    std::map<TextureKey, TextureInfo>::iterator it;
00454 //    for (it = textures.begin() ; it != textures.end() ; it++) {
00455 //        DEBUG_DEBUG("textures num " << it->second.GetNumber());
00456 //    }
00457 }
00458 
00459 void TextureManager::SetPhotometricCorrect(bool state)
00460 {
00461     // change the photometric correction state.
00462     if (state != photometric_correct)
00463     {
00464         photometric_correct = state;
00465         // We will need to recalculate all the images.
00466         /* TODO It may be possible to keep textures that have some identity
00467          * photometric transformation.
00468          * Be warned that when turning off photometric correction, two images
00469          * with the same filename will suddenly have the same key, which will
00470          * break the textures map, hence clearing now                         */
00471         textures.clear();
00472     }
00473 }
00474 
00475 unsigned int TextureManager::GetMaxTotalTexels()
00476 {
00477     // TODO: cut off at a sensible value for available hardware, otherwise set
00478     // to something like 4 times the size of the screen.
00479     // The value is guestimated as good for 1024*512 view where each point is
00480     // covered by 4 images.
00481     return 2097152;
00482     // Note: since we use mipmaps, the amount of actual maximum of pixels stored
00483     // will be 4/3 of this value. It should use a maximum of 8MB of video memory
00484     // for 8 bits per channel rgb images, 12MB if we include a mask.
00485     // Video memory is also used for two copies of the screen and any auxilary
00486     // buffers, and the meshes, so we should do fine with ~24MB of video memory.
00487 }
00488 
00489 unsigned int TextureManager::GetMaxTextureSizePower()
00490 {
00491     // get the maximum texture size supported by the hardware
00492     // note the value can be too small, it is for a square texture with borders.
00493     // we don't use borders, and the textures aren't always square.
00494     static unsigned int max_size_p = 0;
00495     if (max_size_p) return max_size_p; // don't ask openGL again.
00496     
00497     GLint max_size;
00498     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_size);
00499     if (glGetError())
00500     {
00501       DEBUG_ERROR("Cannot find maximum texture size!");
00502       // opengl docs say 64 pixels square is the minimum size guranteed to be supported.
00503       return 6;
00504     }
00505     max_size_p = int(log2(max_size));
00506     DEBUG_INFO("Max texture size supported is " << max_size <<
00507                " (2^" << max_size_p << ")");
00508     return max_size_p;
00509 }
00510 
00511 void TextureManager::CleanTextures()
00512 {
00513     // clean up all the textures from removed images.
00514     // TODO can this be more efficient?
00515     unsigned int num_images = m_pano->getNrOfImages();
00516     bool retry = true;
00517     TexturesMap::iterator tex;
00518     while (retry)
00519     {
00520       retry = false;
00521       for (tex = textures.begin(); tex != textures.end(); ++tex)
00522       {
00523           bool found = false;
00524           
00525           // try and find an image with this key
00526           for (unsigned int img = 0; img < num_images; img++)
00527           {
00528               TextureKey ik(view_state->GetSrcImage(img), &photometric_correct);
00529               if (ik == tex->first)
00530               {
00531                   found = true;
00532                   break;
00533               }
00534           }
00535           // remove it if it was not found
00536           if (!found)
00537           {
00538               DEBUG_INFO("Removing old texture for " << tex->first.filename << ".");
00539               retry = true;
00540               textures.erase(tex);
00541               break;
00542           }
00543       }
00544     }
00545 }
00546 
00547 // helper class for the image cache
00548 // this procedure is called when a image was sucessfull loaded
00549 // we check if we still need the image and if so prepare the texture with DefineLevels
00550 void TextureManager::LoadingImageFinished(int min, int max,
00551     bool texture_photometric_correct,
00552     const HuginBase::PanoramaOptions &dest_img,
00553     const HuginBase::SrcPanoImage &state)
00554 {
00555     TexturesMap::iterator it = textures.find(TextureKey(&state, &texture_photometric_correct));
00556     // check if image is still there
00557     if (it != textures.end())
00558     {
00559         // new shared pointer to keep class alive
00560         sharedPtrNamespace::shared_ptr<TextureInfo> tex(it->second);
00561         tex->DefineLevels(min, max, texture_photometric_correct, dest_img, state);
00562     };
00563 };
00564 
00565 TextureManager::TextureInfo::TextureInfo(ViewState *new_view_state)
00566 {
00567     // we shouldn't be using this. It exists only to make std::map happy.
00568     DEBUG_ASSERT(0);
00569     m_viewState=new_view_state;
00570     has_active_masks=false;
00571     CreateTexture();
00572 }
00573 
00574 TextureManager::TextureInfo::TextureInfo(ViewState *new_view_state, unsigned int width_p_in,
00575                                          unsigned int height_p_in)
00576 {
00577     m_viewState=new_view_state;
00578     has_active_masks=false;
00579     width_p = width_p_in;
00580     height_p = height_p_in;
00581     width = 1 << width_p;
00582     height = 1 << height_p;
00583     CreateTexture();
00584 }
00585 
00586 void TextureManager::TextureInfo::CreateTexture()
00587 {
00588     // Get an number for an OpenGL texture
00589     glGenTextures(1, (GLuint*) &num);
00590     DEBUG_DEBUG("textures num created " << num);
00591     glGenTextures(1, (GLuint*) &numMask);
00592     // we want to generate all levels of detail, they are all undefined.
00593     min_lod = 1000;
00594     SetParameters();
00595 }
00596 
00597 void TextureManager::TextureInfo::SetParameters()
00598 {
00599     BindImageTexture();
00600     glEnable(GL_TEXTURE_2D);
00601     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
00602                     GL_LINEAR_MIPMAP_LINEAR);
00603     // we don't want the edges to repeat the other side of the texture
00604     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
00605     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
00606     // use anistropic filtering if supported. This is good because we are
00607     // sretching and distorting the textures rather a lot in places and still
00608     // want good image quality.
00609     static bool checked_anisotropic = false;
00610     static bool has_anisotropic;
00611     static float anisotropy;
00612     if (!checked_anisotropic)
00613     {
00614         // check if it is supported
00615         if (GLEW_EXT_texture_filter_anisotropic)
00616         {
00617             has_anisotropic = true;
00618             glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropy);
00619             DEBUG_INFO("Using anisotropic filtering at maximum value "
00620                       << anisotropy);
00621         } else {
00622             has_anisotropic = false;
00623             DEBUG_INFO("Anisotropic filtering is not available.");
00624         }
00625         checked_anisotropic = true;
00626     }
00627     if (has_anisotropic)
00628     {
00629         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
00630                         anisotropy);
00631     }
00632     if(m_viewState->GetSupportMultiTexture())
00633     {
00634         BindMaskTexture();
00635         glEnable(GL_TEXTURE_2D);
00636         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
00637         // we don't want the edges to repeat the other side of the texture
00638         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
00639         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
00640         if(has_anisotropic)
00641             glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,anisotropy);
00642     };
00643     GLenum error = glGetError();
00644     if (error != GL_NO_ERROR)
00645     {
00646         DEBUG_ERROR("GL Error when setting texture parameters: "
00647                     << gluErrorString(error) << ".");
00648     }
00649 }
00650 
00651 TextureManager::TextureInfo::~TextureInfo()
00652 {
00653     // free up the graphics system's memory for this texture
00654     DEBUG_DEBUG("textures num deleting " <<  num);
00655     glDeleteTextures(1, (GLuint*) &num);
00656     glDeleteTextures(1, (GLuint*) &numMask);
00657 }
00658 
00659 void TextureManager::TextureInfo::Bind()
00660 {
00661     BindImageTexture();
00662     BindMaskTexture();
00663     if(m_viewState->GetSupportMultiTexture())
00664     {
00665         if(has_active_masks)
00666             glEnable(GL_TEXTURE_2D);
00667         else
00668             glDisable(GL_TEXTURE_2D);
00669         glActiveTexture(GL_TEXTURE0);
00670     };
00671 }
00672 
00673 void TextureManager::TextureInfo::BindImageTexture()
00674 {
00675     if(m_viewState->GetSupportMultiTexture())
00676     {
00677         glActiveTexture(GL_TEXTURE0);
00678         glBindTexture(GL_TEXTURE_2D, num);
00679     }
00680     else
00681         glBindTexture(GL_TEXTURE_2D, num);
00682 };
00683 void TextureManager::TextureInfo::BindMaskTexture()
00684 {
00685     if(m_viewState->GetSupportMultiTexture())
00686     {
00687         glActiveTexture(GL_TEXTURE1);
00688         glEnable(GL_TEXTURE_2D);
00689         glBindTexture(GL_TEXTURE_2D, numMask);
00690     }
00691 };
00692 
00693 // Note min and max refer to the mipmap levels, not the sizes of them. min has
00694 // the biggest size.
00695 void TextureManager::TextureInfo::DefineLevels(int min,
00696                                                int max,
00697                                                bool photometric_correct,
00698                                      const HuginBase::PanoramaOptions &dest_img,
00699                                          const HuginBase::SrcPanoImage &src_img)
00700 {
00701     // This might take a while, so show a busy cursor.
00702     //FIXME: busy cursor creates weird problem with calling checkupdate function again and messing up the textures
00703 //    wxBusyCursor busy_cursor;
00704     // activate the texture so we can change it.
00705     BindImageTexture();
00706     // find the highest allowable mip level
00707     int max_mip_level = (width_p > height_p) ? width_p : height_p;
00708     if (max > max_mip_level) max = max_mip_level;
00709     
00710     // add more detail textures. We need to get the biggest one first.
00711     // find the original image to scale down.
00712     // TODO cache full texture to disk after scaling?
00713     // TODO use small image if don't need bigger?
00714     // It is also possible to use HDR textures, but I can't see the point using
00715     // them as the only difference on an LDR display would be spending extra 
00716     // time reading the texture and converting the numbers. (float and uint16)
00717     // remove some cache items if we are using lots of memory:
00718     ImageCache::getInstance().softFlush();
00719     DEBUG_INFO("Loading image");
00720     const std::string img_name = src_img.getFilename();
00721     ImageCache::EntryPtr entry = ImageCache::getInstance().getImageIfAvailable(img_name);
00722     if (!entry.get())
00723     {
00724         // Image isn't loaded yet. Request it for later.
00725         m_imageRequest = ImageCache::getInstance().requestAsyncImage(img_name);
00726         // call this function with the same parameters after the image loads
00727         // it would be easier to call DefineLevels directly
00728         // but this fails if the TextureInfo object is destroyed during loading of the image
00729         // this can happen if a new project is opened during the loading cycling
00730         // so we go about LoadingImageFinished to check if the texture is still needed
00731         m_imageRequest->ready.push_back(
00732 #ifdef HAVE_CXX11
00733             std::bind(&TextureManager::LoadingImageFinished, m_viewState->GetTextureManager(),
00734                       min, max, photometric_correct, dest_img, src_img)
00735 #else
00736             boost::bind(&TextureManager::LoadingImageFinished, m_viewState->GetTextureManager(),
00737                         min, max, photometric_correct, dest_img, src_img)
00738 #endif
00739         );
00740         // After that, redraw the preview.
00741         m_imageRequest->ready.push_back(
00742 #ifdef HAVE_CXX11
00743             std::bind(&GLPreviewFrame::redrawPreview,
00744                       huginApp::getMainFrame()->getGLPreview())
00745 #else
00746             boost::bind(&GLPreviewFrame::redrawPreview,
00747                         huginApp::getMainFrame()->getGLPreview())
00748 #endif
00749         );
00750         
00751         // make a temporary placeholder image.
00752         GLubyte* placeholder_image;
00753         size_t placeholderWidth = 64;
00754         size_t placeholderHeight = 64;
00755         Exiv2::Image::AutoPtr image;
00756         bool hasPreview = false;
00757         try
00758         {
00759             image = Exiv2::ImageFactory::open(img_name.c_str());
00760             hasPreview = true;
00761         }
00762         catch (...)
00763         {
00764             std::cerr << __FILE__ << " " << __LINE__ << " Error opening file" << std::endl;
00765         }
00766         if (hasPreview)
00767         {
00768             image->readMetadata();
00769             // read all thumbnails
00770             Exiv2::PreviewManager previews(*image);
00771             Exiv2::PreviewPropertiesList lists = previews.getPreviewProperties();
00772             if (lists.empty())
00773             {
00774                 // no preview found
00775                 hasPreview = false;
00776             }
00777             else
00778             {
00779                 // select a preview with matching size
00780                 int previewIndex = 0;
00781                 while (previewIndex < lists.size() - 1 && lists[previewIndex].width_ < 200 && lists[previewIndex].height_ < 200)
00782                 {
00783                     ++previewIndex;
00784                 };
00785                 // load preview image to wxImage
00786                 wxImage rawImage;
00787                 Exiv2::PreviewImage previewImage = previews.getPreviewImage(lists[previewIndex]);
00788                 wxMemoryInputStream stream(previewImage.pData(), previewImage.size());
00789                 rawImage.LoadFile(stream, wxString(previewImage.mimeType().c_str(), wxConvLocal), -1);
00790                 placeholderWidth = rawImage.GetWidth();
00791                 placeholderHeight = rawImage.GetHeight();
00792                 placeholder_image = new GLubyte[placeholderWidth * placeholderHeight * 4];
00793                 size_t index = 0;
00794                 for (size_t y = 0; y < placeholderHeight; ++y)
00795                 {
00796                     for (size_t x = 0; x < placeholderWidth; ++x)
00797                     {
00798                         placeholder_image[index++] = rawImage.GetRed(x, y);
00799                         placeholder_image[index++] = rawImage.GetGreen(x, y);
00800                         placeholder_image[index++] = rawImage.GetBlue(x, y);
00801                         placeholder_image[index++] = 63;
00802                     };
00803                 };
00804             };
00805         };
00806         if (!hasPreview)
00807         {
00808             // no preview, create checker board
00809             placeholder_image = new GLubyte[placeholderWidth * placeholderHeight * 4];
00810             size_t index = 0;
00811             for (int i = 0; i < placeholderHeight; i++)
00812             {
00813                 for (int j = 0; j < placeholderWidth; j++)
00814                 {
00815                     // checkboard pattern
00816                     GLubyte c = ((i / 8 + j / 8) % 2) ? 63 : 191;
00817                     placeholder_image[index++] = c;
00818                     placeholder_image[index++] = c;
00819                     placeholder_image[index++] = c;
00820                     // alpha is low, so the placeholder is mostly transparent.
00821                     placeholder_image[index++] = 63;
00822                 }
00823             }
00824         };
00825         gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA8, placeholderWidth, placeholderHeight,
00826                                GL_RGBA, GL_UNSIGNED_BYTE,
00827                                placeholder_image);
00828         SetParameters();
00829         delete[] placeholder_image;
00830         return;
00831     }
00832     // forget the request if we made one before.
00833     m_imageRequest = ImageCache::RequestPtr();
00834     DEBUG_INFO("Converting to 8 bits");
00835     sharedPtrNamespace::shared_ptr<vigra::BRGBImage> img = entry->get8BitImage();
00836     sharedPtrNamespace::shared_ptr<vigra::BImage> mask = entry->mask;
00837     // first make the biggest mip level.
00838     int wo = 1 << (width_p - min), ho = 1 << (height_p - min);
00839     if (wo < 1) wo = 1; if (ho < 1) ho = 1;
00840     // use Vigra to resize image
00841     DEBUG_INFO("Scaling image");
00842     vigra::BRGBImage out_img(wo, ho);
00843     // also read in the mask. OpenGL requires that the mask is in the same array
00844     // as the colour data, but the ImageCache doesn't work in this way.
00845     has_mask = mask->width()  && mask->height();
00846     vigra::UInt8Image *out_alpha;
00847     if (has_mask) out_alpha = new vigra::UInt8Image(wo, ho);
00848     if (wo < 2 || ho < 2)
00849     {
00850         // too small for vigra to scale
00851         // we still need to define some mipmap levels though, so use only (0, 0)
00852         for (int h = 0; h < ho; h++)
00853         {
00854             for (int w = 0; w < wo; w++)
00855             {
00856                 out_img[h][w] = (*img)[0][0];
00857                 if (has_mask) (*out_alpha)[h][w] = (*mask)[0][0];
00858             }
00859         }
00860     } else {
00861         // I think this takes to long, although it should be prettier.
00862         /*vigra::resizeImageLinearInterpolation(srcImageRange(*img),
00863                                                destImageRange(out_img));
00864         if (has_mask)
00865         {
00866             vigra::resizeImageLinearInterpolation(srcImageRange(*(entry->mask)),
00867                                           destImageRange(out_alpha));
00868         }*/
00869         
00870         // much faster. It shouldn't be so bad after it
00871         vigra::resizeImageNoInterpolation(srcImageRange(*img),
00872                                           destImageRange(out_img));
00873         if (has_mask)
00874         {
00875             vigra::resizeImageNoInterpolation(srcImageRange(*(mask)),
00876                                               destImageRange(*out_alpha));
00877         }
00878         // prepare color management
00879         cmsHPROFILE inputICC = NULL;
00880         if (!entry->iccProfile->empty())
00881         {
00882             inputICC = cmsOpenProfileFromMem(entry->iccProfile->data(), entry->iccProfile->size());
00883         };
00884         cmsHTRANSFORM transform = NULL;
00885         // do color correction only if input image has icc profile or if we found a monitor profile
00886         if (inputICC != NULL || huginApp::Get()->HasMonitorProfile())
00887         {
00888             // check input profile
00889             if (inputICC != NULL)
00890             {
00891                 if (cmsGetColorSpace(inputICC) != cmsSigRgbData)
00892                 {
00893                     cmsCloseProfile(inputICC);
00894                     inputICC = NULL;
00895                 };
00896             };
00897             // if there is no icc profile in file fall back to sRGB
00898             if (inputICC == NULL)
00899             {
00900                 inputICC = cmsCreate_sRGBProfile();
00901             };
00902             // now build transform
00903             transform = cmsCreateTransform(inputICC, TYPE_RGB_8,
00904                 huginApp::Get()->GetMonitorProfile(), TYPE_RGB_8,
00905                 INTENT_PERCEPTUAL, cmsFLAGS_BLACKPOINTCOMPENSATION);
00906         };
00907         // now perform photometric correction
00908         if (photometric_correct)
00909         {
00910             DEBUG_INFO("Performing photometric correction");
00911             // setup photometric transform for this image type
00912             // this corrects for response curve, white balance, exposure and
00913             // radial vignetting
00914             HuginBase::Photometric::InvResponseTransform < unsigned char, double >
00915                 invResponse(src_img);
00916             // Assume LDR for now.
00917             // if (m_destImg.outputMode == PanoramaOptions::OUTPUT_LDR) {
00918             // select exposure and response curve for LDR output
00919             std::vector<double> outLut;
00920             // @TODO better handling of output EMoR parameters
00921             // Hugin's stitcher is currently using the EMoR parameters of the first image
00922             // as so called output EMoR parameter, so enforce this also for the fast
00923             // preview window
00924             // vigra_ext::EMoR::createEMoRLUT(dest_img.outputEMoRParams, outLut);
00925             vigra_ext::EMoR::createEMoRLUT(m_viewState->GetSrcImage(0)->getEMoRParams(), outLut);
00926             vigra_ext::enforceMonotonicity(outLut);
00927             invResponse.setOutput(1.0 / pow(2.0, dest_img.outputExposureValue),
00928                 outLut, 255.0);
00929             /*} else {
00930                // HDR output. not sure how that would be handled by the opengl
00931                // preview, though. It might be possible to apply a logarithmic
00932                // lookup table here, and average the overlapping pixels
00933                // in the OpenGL renderer?
00934                // TODO
00935                invResponse.setHDROutput();
00936                }*/
00937             // now perform the corrections
00938             double scale_x = (double)src_img.getSize().width() / (double)wo,
00939                 scale_y = (double)src_img.getSize().height() / (double)ho;
00940 #pragma omp parallel for
00941             for (int y = 0; y < ho; y++)
00942             {
00943                 for (int x = 0; x < wo; x++)
00944                 {
00945                     double sx = (double)x * scale_x,
00946                         sy = (double)y * scale_y;
00947                     out_img[y][x] = invResponse(out_img[y][x],
00948                         hugin_utils::FDiff2D(sx, sy));
00949                 }
00950                 // now take color profiles in file and of monitor into account
00951                 if (transform != NULL)
00952                 {
00953                     cmsDoTransform(transform, out_img[y], out_img[y], out_img.width());
00954                 };
00955             }
00956         }
00957         else
00958         {
00959             // no photometric correction
00960             if (transform != NULL)
00961             {
00962 #pragma omp parallel for
00963                 for (int y = 0; y < ho; y++)
00964                 {
00965                     cmsDoTransform(transform, out_img[y], out_img[y], out_img.width());
00966                 };
00967             };
00968         };
00969         if (transform != NULL)
00970         {
00971             cmsDeleteTransform(transform);
00972         };
00973         if (inputICC != NULL)
00974         {
00975             cmsCloseProfile(inputICC);
00976         };
00977     }
00978     
00979     //  make all of the smaller ones until we are done.
00980     // this will use a box filter.
00981     // dependent on OpenGL 1.3. Might need an alternative for 1.2.
00982     // TODO use texture compresion?
00983     DEBUG_INFO("Defining mipmap levels " <<  min << " to " << max
00984           << " of texture " << num << ", starting with a size of "
00985           << wo << " by " << ho << ".");
00986     GLint error;
00987     if (has_mask)
00988     {
00989         // combine the alpha bitmap with the red green and blue one.
00990         unsigned char *image = new unsigned char[ho * wo * 4];
00991         unsigned char *pix_start = image;
00992         for (int h = 0; h < ho; h++)
00993         {
00994             for (int w = 0; w < wo; w++)
00995             {
00996                 pix_start[0] = out_img[h][w].red();
00997                 pix_start[1] = out_img[h][w].green();
00998                 pix_start[2] = out_img[h][w].blue();
00999                 pix_start[3] = (*out_alpha)[h][w];
01000                 pix_start += 4;
01001             }
01002         }
01003         // We don't need to worry about levels with the ATI bug work around,
01004         // and Windows doesn't like it as gluBuild2DMipmapLevels is in OpenGL
01005         // version 1.3 and above only (Microsoft's SDK only uses 1.1)
01006         error = gluBuild2DMipmaps/*Levels*/(GL_TEXTURE_2D, GL_RGBA8, wo, ho,
01007                                GL_RGBA, GL_UNSIGNED_BYTE, /*min, min, max,*/
01008                                image);
01009         delete [] image;
01010         delete out_alpha;
01011     } else {
01012         // we don't need to rearange the data in memory if there is no mask.
01013         error = gluBuild2DMipmaps/*Levels*/(GL_TEXTURE_2D, GL_RGB8, wo, ho,
01014                                GL_RGB, GL_UNSIGNED_BYTE, /*min, min, max,*/
01015                                (unsigned char *) out_img.data());
01016     }
01017     if (error)
01018     {
01019         DEBUG_ERROR("GLU Error when building mipmap levels: "
01020                   << gluErrorString(error) << ".");
01021     }
01022     error = glGetError();
01023     if (error != GL_NO_ERROR)
01024     {
01025         DEBUG_ERROR("GL Error when bulding mipmap levels: "
01026                   << gluErrorString(error) << ".");
01027     }
01028     SetParameters();
01029     DEBUG_INFO("Finsihed loading texture.");
01030 
01031 
01032 }
01033 
01034 void TextureManager::TextureInfo::DefineMaskTexture(const HuginBase::SrcPanoImage &srcImg)
01035 {
01036     has_active_masks=srcImg.hasActiveMasks();
01037     HuginBase::MaskPolygonVector masks=srcImg.getActiveMasks();
01038     if(has_active_masks)
01039     {
01040         unsigned int maskSize=(width>height) ? width : height;
01041         if(maskSize>64)
01042             maskSize/=2;
01043         BindMaskTexture();
01044         for(unsigned int i=0;i<masks.size();i++)
01045             masks[i].scale((double)maskSize/srcImg.getWidth(),(double)maskSize/srcImg.getHeight());
01046         vigra::UInt8Image mask(maskSize,maskSize,255);
01047         //we don't draw mask if the size is smaller than 4 pixel
01048         if(maskSize>4)
01049             vigra_ext::applyMask(vigra::destImageRange(mask), masks);
01050 #ifdef __APPLE__
01051         // see comment to PreviewLayoutLinesTool::PreviewLayoutLinesTool
01052         // on MacOS a single alpha channel seems not to work, so this workaround
01053         unsigned char *image = new unsigned char[maskSize * maskSize * 2];
01054         unsigned char *pix_start = image;
01055         for (int h = 0; h < maskSize; h++)
01056         {
01057             for (int w = 0; w < maskSize; w++)
01058             {
01059                 pix_start[0] = 255;
01060                 pix_start[1] = mask[h][w];
01061                 pix_start += 2;
01062             }
01063         }
01064         gluBuild2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, maskSize, maskSize,
01065             GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, image);
01066         delete [] image;
01067 #else
01068         gluBuild2DMipmaps(GL_TEXTURE_2D, GL_ALPHA, maskSize,maskSize,GL_ALPHA, GL_UNSIGNED_BYTE,(unsigned char *) mask.data());
01069 #endif
01070     };
01071 };
01072 
01073 void TextureManager::TextureInfo::UpdateMask(const HuginBase::SrcPanoImage &srcImg)
01074 {
01075     if(m_viewState->GetSupportMultiTexture())
01076     {
01077         //delete old mask
01078         glDeleteTextures(1, (GLuint*) &numMask);
01079         //new create new mask
01080         glGenTextures(1, (GLuint*) &numMask);
01081         SetParameters();
01082         DefineMaskTexture(srcImg);
01083     };
01084 };
01085 
01086 void TextureManager::TextureInfo::SetMaxLevel(int level)
01087 {
01088     // we want to tell openGL the highest defined mip level of our texture.
01089     BindImageTexture();
01090     // FIXME the ati graphics driver on Ubuntu is known to crash due to this
01091     // practice. ati users should disable direct renderering if using the
01092     // #if 0'ed code above.
01093     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, level);
01094     if(m_viewState->GetSupportMultiTexture())
01095     {
01096         // now for the mask texture
01097         BindMaskTexture();
01098         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, level);
01099     }
01100     // we don't set min_lod so we can 'DefineLevels' using the old value.
01101     GLenum error = glGetError();
01102     if (error != GL_NO_ERROR)
01103     {
01104         DEBUG_ERROR("Error when setting the base mipmap level: "
01105                   << gluErrorString(error) << ".");
01106     }
01107 }
01108 
01109 TextureManager::TextureKey::TextureKey(const HuginBase::SrcPanoImage *source,
01110                                        bool *photometric_correct_ptr)
01111 {
01112     SetOptions(source);
01113     photometric_correct = photometric_correct_ptr;
01114 }
01115 
01116 // This is only used by clean textures
01117 const bool TextureManager::TextureKey::operator==(const TextureKey& comp) const
01118 {
01119     return !(*this < comp || comp < *this);
01120 }
01121 
01122 const bool TextureManager::TextureKey::operator<(const TextureKey& comp) const
01123 {
01124     // compare two keys for ordering.
01125     // first try the filename.
01126     if (filename < comp.filename) return true;
01127     if (filename > comp.filename) return false;
01128     // Are there different masks?
01129     if (masks < comp.masks) return true;
01130     if (masks > comp.masks) return false;
01131     // if we are not using photometric correction, the textures are equivalent.
01132     if (!(*photometric_correct)) return false;
01133     // now try the photometric properties
01134     if (exposure < comp.exposure) return true;
01135     if (exposure > comp.exposure) return false;
01136     if (white_balance_red < comp.white_balance_red) return true;
01137     if (white_balance_red > comp.white_balance_red) return false;
01138     if (white_balance_blue < comp.white_balance_blue) return true;
01139     if (white_balance_blue > comp.white_balance_blue) return false;
01140     if (EMoR_params < comp.EMoR_params) return true;
01141     if (EMoR_params > comp.EMoR_params) return false;
01142     if (radial_vig_corr_coeff < comp.radial_vig_corr_coeff) return true;
01143     if (radial_vig_corr_coeff > comp.radial_vig_corr_coeff) return false;
01144     if (vig_corr_mode < comp.vig_corr_mode) return true;
01145     if (vig_corr_mode > comp.vig_corr_mode) return false;
01146     if (response_type < comp.response_type) return true;
01147     if (response_type > comp.response_type) return false;
01148     if (gamma < comp.gamma) return true;
01149     if (gamma > comp.gamma) return false;
01150     if (radial_distortion_red < comp.radial_distortion_red) return true;
01151     if (radial_distortion_red > comp.radial_distortion_red) return false;
01152     if (radial_distortion_blue < comp.radial_distortion_blue) return true;
01153     if (radial_distortion_blue > comp.radial_distortion_blue) return false;
01154     // If we've reached here it should be exactly the same:
01155     return false;
01156 }
01157 
01158 void TextureManager::TextureKey::SetOptions(const HuginBase::SrcPanoImage *source)
01159 {
01160     filename = source->getFilename();
01161     // Record the masks. Images with different masks require different
01162     // textures since the mask is stored with them.
01163     std::stringstream mask_ss;
01164     source->printMaskLines(mask_ss, 0);
01165     masks = mask_ss.str();
01166     
01167     exposure = source->getExposure();
01168     white_balance_red = source->getWhiteBalanceRed();
01169     white_balance_blue = source->getWhiteBalanceBlue();
01170     EMoR_params = source->getEMoRParams();
01171     radial_vig_corr_coeff = source->getRadialVigCorrCoeff();
01172     vig_corr_mode = source->getVigCorrMode();
01173     response_type = source->getResponseType();
01174     gamma = source->getGamma();
01175     radial_distortion_red = source->getRadialDistortionRed();
01176     radial_distortion_blue = source->getRadialDistortionBlue();
01177 }
01178 

Generated on 9 Feb 2016 for Hugintrunk by  doxygen 1.4.7