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

Generated on 28 Jul 2015 for Hugintrunk by  doxygen 1.4.7