/********************************************************************
**                Image Component Library (ICL)                    **
**                                                                 **
** Copyright (C) 2006-2012 CITEC, University of Bielefeld          **
**                         Neuroinformatics Group                  **
** Website: www.iclcv.org and                                      **
**          http://opensource.cit-ec.de/projects/icl               **
**                                                                 **
** File   : ICLQt/src/GLImg.cpp                                    **
** Module : ICLQt                                                  **
** Authors: Christof Elbrechter                                    **
**                                                                 **
**                                                                 **
** Commercial License                                              **
** ICL can be used commercially, please refer to our website       **
** www.iclcv.org for more details.                                 **
**                                                                 **
** GNU General Public License Usage                                **
** Alternatively, this file may be used under the terms of the     **
** GNU General Public License version 3.0 as published by the      **
** Free Software Foundation and appearing in the file LICENSE.GPL  **
** included in the packaging of this file.  Please review the      **
** following information to ensure the GNU General Public License  **
** version 3.0 requirements will be met:                           **
** http://www.gnu.org/copyleft/gpl.html.                           **
**                                                                 **
** The development of this software was supported by the           **
** Excellence Cluster EXC 277 Cognitive Interaction Technology.    **
** The Excellence Cluster EXC 277 is a grant of the Deutsche       **
** Forschungsgemeinschaft (DFG) in the context of the German       **
** Excellence Initiative.                                          **
**                                                                 **
*********************************************************************/

#include <ICLCore/Img.h>
#include <ICLQt/GLImg.h>
#include <ICLQt/GLContext.h>
#include <ICLCC/CCFunctions.h>

#ifdef ICL_SYSTEM_APPLE
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif

#include <ICLUtils/Array2D.h>

#include <ICLQuick/Quick.h>
#include <ICLUtils/Rect32f.h>
#include <ICLUtils/Lockable.h>

#ifdef HAVE_QT
#include <QtCore/QObject>
#include <QtCore/QCoreApplication>
#include <QtCore/QMutex>
#include <QtOpenGL/QGLContext>
#endif

namespace icl{
  struct WhiteTexture{
    Img8u image;
    GLImg gli;
    WhiteTexture() : image(Size(1,1),1){
      image(0,0,0) = 255;
      gli.update(&image);
    }
  };
  
  void bindWhiteTexture(){
    static WhiteTexture wt;
    glTexCoord2f(0,0);
    wt.gli.bind();
  }

#ifdef HAVE_QT  
  struct AsynchronousTextureDeleter : public QObject{
    struct Event : public QEvent{
      Event(const std::vector<GLuint> &del):
        QEvent((QEvent::Type)QEvent::registerEventType()),del(del){
        ctx = QGLContext::currentContext();
      }
      std::vector<GLuint> del;
      const QGLContext *ctx;
    };
    virtual bool event(QEvent *eIn){
      Event *e = dynamic_cast<Event*>(eIn);
      if(!e) return false;
      const_cast<QGLContext*>(e->ctx)->makeCurrent();
      glDeleteTextures(e->del.size(), e->del.data());
      return true;
    }
  };
  
  void freeTextures(const std::vector<GLuint> &del){
    static SmartPtr<AsynchronousTextureDeleter> deleter(new AsynchronousTextureDeleter);
    QCoreApplication::postEvent(deleter.get(), new AsynchronousTextureDeleter::Event(del));
  }
#endif

  inline float static winToDraw(float x, float w) { return (2/w) * x -1; }  
  inline float static drawToWin(float x, float w) { return (w/2) * x + (w/2); } 
  
  struct TextureElement{
    TextureElement(const Point &offset, const Size &size, int pixelSize, const Size &imageSize):
      tex(0),offset(offset), size(size), data(size.getDim()*pixelSize){
      
      float maxX  = imageSize.width, maxY = imageSize.height;
      relUL.x = maxX ? float(offset.x)/maxX : 0;
      relUL.y = maxY ? float(offset.y)/maxY : 0;
      relLR.x = maxX ? float(offset.x+size.width)/maxX : 1;
      relLR.y = maxY ? float(offset.y+size.height)/maxY : 1;
    }
    GLuint tex;
    Point offset;
    Size size;
    Point32f relUL, relLR;

    std::vector<icl8u> data;

    template<class T, int C>
    std::vector<Range64f> findMinMax() const{
      const T *p = (const T*) data.data();
      const int dim = size.getDim();
      Range64f rs[C];
      for(int c=0;c<C;++c){
        rs[c] = Range64f::limits();
        std::swap(rs[c].minVal, rs[c].maxVal);
        const T *pc = p+c;
        for(int i=0;i<dim;++i){
          T t = pc[i*C];
          if(t < rs[c].minVal) rs[c].minVal = t;
          if(t > rs[c].maxVal) rs[c].maxVal = t;
        }
      }
      return std::vector<Range64f>(rs,rs+C);
    }
  };
  typedef SmartPtr<TextureElement> TextureElementPtr;

#ifdef HAVE_IPP

  // ipp optimization for 1 channel data
  template<> std::vector<Range64f> TextureElement::findMinMax<icl8u,1>() const{
    icl8u mm[2];
    ippiMinMax_8u_C1R(data.data(), size.width, size, mm, mm+1);
    return std::vector<Range64f>(1,Range64f(mm[0],mm[1]));
  }

  template<> std::vector<Range64f> TextureElement::findMinMax<icl16s,1>() const{
    icl16s mm[2];
    ippiMinMax_16s_C1R(reinterpret_cast<const icl16s*>(data.data()), size.width*sizeof(icl16s), size, mm, mm+1);
    return std::vector<Range64f>(1,Range64f(mm[0],mm[1]));
  }

  template<> std::vector<Range64f> TextureElement::findMinMax<icl32f,1>() const{
    icl32f mm[2];
    ippiMinMax_32f_C1R(reinterpret_cast<const icl32f*>(data.data()), size.width*sizeof(icl32f), size, mm, mm+1);
    return std::vector<Range64f>(1,Range64f(mm[0],mm[1]));
  }

  /// ipp optimization for 3 channel data
  template<> std::vector<Range64f> TextureElement::findMinMax<icl8u,3>() const{
    icl8u mins[3],maxs[3];
    ippiMinMax_8u_C3R(data.data(), size.width, size, mins, maxs);
    std::vector<Range64f> r(3); 
    for(int i=0;i<3;++i) { r[i].minVal = mins[i]; r[i].maxVal = maxs[i]; }
    return r;
  }

  template<> std::vector<Range64f> TextureElement::findMinMax<icl16s,3>() const{
    icl16s mins[3],maxs[3];
    ippiMinMax_16s_C3R(reinterpret_cast<const icl16s*>(data.data()), size.width*sizeof(icl16s), size, mins, maxs);
    std::vector<Range64f> r(3); 
    for(int i=0;i<3;++i) { r[i].minVal = mins[i]; r[i].maxVal = maxs[i]; }
    return r;
  }

  template<> std::vector<Range64f> TextureElement::findMinMax<icl32f,3>() const{
    icl32f mins[3],maxs[3];
    ippiMinMax_32f_C3R(reinterpret_cast<const icl32f*>(data.data()), size.width*sizeof(icl32f), size, mins, maxs);
    std::vector<Range64f> r(3); 
    for(int i=0;i<3;++i) { r[i].minVal = mins[i]; r[i].maxVal = maxs[i]; }
    return r;
  }

  /// ipp optimization for 4 channel data
  template<> std::vector<Range64f> TextureElement::findMinMax<icl8u,4>() const{
    icl8u mins[4],maxs[4];
    ippiMinMax_8u_C4R(data.data(), size.width, size, mins, maxs);
    std::vector<Range64f> r(4); 
    for(int i=0;i<4;++i) { r[i].minVal = mins[i]; r[i].maxVal = maxs[i]; }
    return r;
  }

  template<> std::vector<Range64f> TextureElement::findMinMax<icl16s,4>() const{
    icl16s mins[4],maxs[4];
    ippiMinMax_16s_C4R(reinterpret_cast<const icl16s*>(data.data()), size.width*sizeof(icl16s), size, mins, maxs);
    std::vector<Range64f> r(4); 
    for(int i=0;i<4;++i) { r[i].minVal = mins[i]; r[i].maxVal = maxs[i]; }
    return r;
  }

  template<> std::vector<Range64f> TextureElement::findMinMax<icl32f,4>() const{
    icl32f mins[4],maxs[4];
    ippiMinMax_32f_C4R(reinterpret_cast<const icl32f*>(data.data()), size.width*sizeof(icl32f), size, mins, maxs);
    std::vector<Range64f> r(4); 
    for(int i=0;i<4;++i) { r[i].minVal = mins[i]; r[i].maxVal = maxs[i]; }
    return r;
  }

#endif
  
  template<class T>
  static inline void histo_entry(T v, double m, vector<int> &h, unsigned int n, double r){
    // todo check 1000 times +5 times (3Times done!)
    ++h[ floor( n*(v-m)/(r+1)) ];
  }
  template<class T>
  static void histo_interleaved(const void *vdata,
                                const int dim,
                                const int channels, 
                                const std::vector<Range64f> &ranges,
                                std::vector< std::vector<int> > &histos){
    const T * data = reinterpret_cast<const T*>(vdata);
    double mins[4],ls[4];
    for(unsigned int i=0;i<ranges.size();++i){
      mins[i] = ranges[i].minVal;
      ls[i] = ranges[i].getLength();
    }
    for(int i=0;i<dim;++i){
      for(int c=0;c<channels;++c){
        const T &val = data[channels * i + c];
        histo_entry(val,mins[c],histos[c],256,ls[c]);
      }
    }
  }
  
  template<>
  void histo_interleaved<icl8u>(const void *vdata,
                                const int dim,
                                const int channels, 
                                const std::vector<Range64f> &ranges,
                                std::vector< std::vector<int> > &histos){
    const icl8u *data = reinterpret_cast<const icl8u*>(vdata);
    switch(channels){
      case 1:{
          int *h = histos[0].data();
          for(int i=0;i<dim;++i){
            ++h[ data[i] ];
          }
          break;
      }
      case 2:{
        int *h0 = histos[0].data();
        int *h1 = histos[1].data();
        for(int i=0;i<dim;++i){
          ++h0[ data[2*i] ];
          ++h1[ data[2*i+1] ];
        }
        break;
      }
      case 3:{
        int *h0 = histos[0].data();
        int *h1 = histos[1].data();
        int *h2 = histos[2].data();
        for(int i=0;i<dim;++i){
          ++h0[ data[3*i] ];
          ++h1[ data[3*i+1] ];
          ++h2[ data[3*i+2] ];
        }
        break;
      }
      case 4:{
        int *h0 = histos[0].data();
        int *h1 = histos[1].data();
        int *h2 = histos[2].data();
        int *h3 = histos[3].data();
        for(int i=0;i<dim;++i){
          ++h0[ data[4*i] ];
          ++h1[ data[4*i+1] ];
          ++h2[ data[4*i+2] ];
          ++h3[ data[4*i+3] ];
        }
        break;
      }
      default:
        for(int i=0;i<dim;++i){
          for(int c=0;c<channels;++c){
            ++ histos[c][ data[i * channels + c] ];
          }
        }  
        break;
    }
  }

  struct GLImg::Data{
    static bool useDirtyFlag;
    
    scalemode sm;
    bool dirty;
    
    depth imageDepth;
    depth origImageDepth;
    Size imageSize;
    Rect imageROI;
    int imageChannels;
    int maxCellSize;
    int bci[3];
    
    struct TextureInfo{
      inline TextureInfo():threadID(0){}
      inline TextureInfo(unsigned threadID, const GLContext &ctx):
        threadID(threadID),ctx(ctx){}
      
      unsigned int threadID;
      GLContext ctx;
      
      static inline TextureInfo getCurrentTextureInfo(){
        return TextureInfo(pthread_self(), GLContext::currentContext());
      }
      inline bool operator==(const TextureInfo &other) const{
        return threadID == other.threadID && ctx == other.ctx;
      }
    };
      
    TextureInfo textureInfo;
    
    std::vector<GLuint> textures;
    Array2D<TextureElementPtr> data;
    mutable ImgBase *extractedImageBuffer;

    std::vector<GLImg::Vec3> gridBuffer, gridNormalBuffer;

    float gridColor[4];
    bool drawGrid;
    format imageFormat;
    Time timeStamp;
    mutable ImageStatistics stats;
    mutable QMutex textureBufferMutex;

    bool isImageNull;

    Data():textureBufferMutex(QMutex::Recursive){
    }
    
    ~Data(){
      releaseTextures();
      ICL_DELETE(extractedImageBuffer);
    }
    
    inline bool isDirty(){
      return useDirtyFlag ? dirty : true;
    }
    
    void releaseTextures(){

      if(textures.size()){
#ifdef HAVE_QT
        if(QCoreApplication::instance()){
          if(textureInfo == TextureInfo::getCurrentTextureInfo()){
            glDeleteTextures(textures.size(),textures.data());
          }else{
            freeTextures(textures);
          }
        }else{
            glDeleteTextures(textures.size(),textures.data());
        }
        textures.clear();
#else
        glDeleteTextures(textures.size(),textures.data());
#endif

      }
    }


    template<class ExternalType, class InternalType>
    void bufferTextureData(const Img<ExternalType> &src, int maxCellSize){
      textureBufferMutex.lock();
      timeStamp = src.getTime();
      imageROI = src.getROI();
      
      const int w = src.getWidth(), h = src.getHeight(), c = src.getChannels();
      const int M = maxCellSize, nx = ceil(float(w)/M), ny = ceil(float(h)/M);
      
      if(imageSize != Size(nx,ny) || imageChannels != c || imageDepth != icl::getDepth<InternalType>()
         || maxCellSize != this->maxCellSize){
        this->maxCellSize = maxCellSize;
        imageSize = Size(w,h);
        imageDepth = icl::getDepth<InternalType>();
        imageChannels  = c;
        imageFormat = src.getFormat();
        
        data = Array2D<TextureElementPtr>(nx,ny);

        for(int y=0;y<ny;++y){
          for(int x=0;x<nx;++x){
            Point offs(x*M, y*M);
            Size size(iclMin(M,w-x*M),iclMin(M,h-y*M));
            data(x,y) = new TextureElement(offs,size, c*sizeof(InternalType), imageSize);
          }
        }
      }
      for(int y=0;y<ny;++y){
        for(int x=0;x<nx;++x){
          TextureElement &t = *data(x,y);
          SmartPtr<const Img<ExternalType> > roi = src.shallowCopy(Rect(t.offset,t.size));
          planarToInterleaved(roi.get(), reinterpret_cast<InternalType*>(t.data.data()), 
                              t.size.width*imageChannels*sizeof(InternalType));
        }
      }
      
      dirty = true;
      textureBufferMutex.unlock();
    }

    
    const ImageStatistics &updateStats() const {
      textureBufferMutex.lock();
      stats.params = ImgParams(imageSize,imageChannels);
      stats.time = timeStamp;
      stats.d = origImageDepth;
      stats.ranges = findMinMaxGeneric();
      stats.histos.resize(imageChannels);
      stats.isNull = false;
      for(int i=0;i<imageChannels;++i){ 
        stats.histos[i].resize(256);
        std::fill(stats.histos[i].begin(), stats.histos[i].end(), 0);
      }
      for(int x=0;x<data.getWidth();++x){
        for(int y=0;y<data.getHeight();++y){
          const TextureElement &t = *data(x,y);
          if(imageDepth == depth8u){
            histo_interleaved<icl8u>(t.data.data(),t.size.getDim(),
                                     imageChannels, stats.ranges,
                                     stats.histos);
          }else if(imageDepth == depth16s){
            histo_interleaved<icl16s>(t.data.data(),t.size.getDim(),
                                      imageChannels, stats.ranges,
                                      stats.histos);
          }else{
            histo_interleaved<icl32f>(t.data.data(),t.size.getDim(),
                                      imageChannels, stats.ranges,
                                      stats.histos);
          }
        }
      }
      
      
      textureBufferMutex.unlock();
      return stats;
    }

    template<class T, int C>
    std::vector<Range64f> findMinMax() const{
      textureBufferMutex.lock();
      std::vector<Range64f> all;
      for(int y=0;y<data.getHeight();++y){
        for(int x=0;x<data.getWidth();++x){
          std::vector<Range64f> rs = data(x,y)->findMinMax<T,C>();
          if(!x && !y) all = rs;
          else{
            for(int i=0;i<C;++i){
              if(rs[i].minVal < all[i].minVal) all[i].minVal = rs[i].minVal;
              if(rs[i].maxVal > all[i].maxVal) all[i].maxVal = rs[i].maxVal;
            }
          }
        }
      }
      textureBufferMutex.unlock();
      return all;
    }
    std::vector<Range64f> findMinMaxGeneric() const{
      const int c = imageChannels;
      if(imageDepth == depth8u){
        switch(c){
          case 1: return findMinMax<icl8u,1>();
          case 2: return findMinMax<icl8u,2>();
          case 3: return findMinMax<icl8u,3>();
          case 4: return findMinMax<icl8u,4>();
        }
      }else if(imageDepth == depth16s){
        switch(c){
          case 1: return findMinMax<icl16s,1>();
          case 2: return findMinMax<icl16s,2>();
          case 3: return findMinMax<icl16s,3>();
          case 4: return findMinMax<icl16s,4>();
        }
      }else{
        switch(c){
          case 1: return findMinMax<icl32f,1>();
          case 2: return findMinMax<icl32f,2>();
          case 3: return findMinMax<icl32f,3>();
          case 4: return findMinMax<icl32f,4>();
        }
      }
      return std::vector<Range64f>();
    }
    
    void setupUnpackAllignment( int w){
      int wBytes = w * iclMin(4u,getSizeOf(imageDepth));
      for(int i=3;i>=0;++i){
        if( !((wBytes)%(1<<i)) ){
          glPixelStorei(GL_UNPACK_ALIGNMENT,1<<i);
          return;
        }
      }
    }

    static inline void setup_pixel_transfer(float sa, float sr, float sg, float sb,
                                            float a, float r, float g, float b){
      glPixelTransferf(GL_ALPHA_SCALE,sa);
      glPixelTransferf(GL_RED_SCALE,sr);
      glPixelTransferf(GL_GREEN_SCALE,sg);
      glPixelTransferf(GL_BLUE_SCALE,sb);
      glPixelTransferf(GL_ALPHA_BIAS,a);
      glPixelTransferf(GL_RED_BIAS,r);
      glPixelTransferf(GL_GREEN_BIAS,g);
      glPixelTransferf(GL_BLUE_BIAS,b);
    }
    
    static inline void reset_bci(){
      setup_pixel_transfer(1,1,1,1,0,0,0,0);
    }
    
    template<class T>
    const std::vector<double> findColor(int x, int y) const {
      int nx = imageSize.width, ny = imageSize.height;
      for(int yCell=0;yCell<ny;++yCell){
        for(int xCell=0;xCell<nx;++xCell){
          const TextureElement &t = *data(xCell,yCell);
          if(Rect(t.offset,t.size).contains(x,y)){
            const T *p = reinterpret_cast<const T*>(t.data.data());
            p += imageChannels*((x-t.offset.x) + t.size.width * (y-t.offset.y));
            return std::vector<double>(p, p+imageChannels);
          }
        }
      }
      return std::vector<double>();
    }

    template<class T>
    const ImgBase &extractImage(Img<T> &dst) const {
      int nx = data.getWidth(), ny = data.getHeight();
      for(int y=0;y<ny;++y){
        for(int x=0;x<nx;++x){
          const TextureElement &t = *data(x,y);
          SmartPtr<Img<T> > roi = dst.shallowCopy(Rect(t.offset,t.size));
          interleavedToPlanar<T,T>(reinterpret_cast<const T*>(t.data.data()), roi.get());
        }
      }
      return dst;
    }

    std::vector<double> findColorGeneric(int x, int y) const{
      if(imageDepth == depth8u) return findColor<icl8u>(x,y);
      else if(imageDepth == depth16s) return findColor<icl16s>(x,y);
      return findColor<icl32f>(x,y);
    }
    
    const ImgBase &extractImageGeneric() const{
      depth d = ((imageDepth == depth8u) ? depth8u : 
                 (imageDepth == depth16s) ? depth16s : 
                 depth32f);
      ensureCompatible(&extractedImageBuffer, d, imageSize, imageChannels);
      if(extractedImageBuffer->getChannels() == 3) extractedImageBuffer->setFormat(formatRGB);
      else if(extractedImageBuffer->getChannels() == 1) extractedImageBuffer->setFormat(formatGray);
      else extractedImageBuffer->setFormat(formatMatrix);

      if(imageDepth == depth8u) return extractImage<icl8u>(*extractedImageBuffer->as8u());
      else if(imageDepth == depth16s) return extractImage<icl16s>(*extractedImageBuffer->as16s());
      return extractImage<icl32f>(*extractedImageBuffer->as32f());
    }


    void setupPixelTransfer(){
      if(!imageChannels || !imageSize.getDim()) return;
      
      icl32f fScaleRGB(0),fBiasRGB(0);
      if( (bci[0] < 0)  && (bci[1] < 0) && (bci[2] < 0)){
        // auto adaption
        std::vector<Range64f> rs = findMinMaxGeneric();
        Range64f all  = rs[0];
        for(unsigned int i=1;i<rs.size();i++){
          if(rs[i].minVal < all.minVal) all.minVal = rs[i].minVal;
          if(rs[i].maxVal > all.maxVal) all.maxVal = rs[i].maxVal;
        }
        
        icl64f l = iclMax(double(1),all.getLength());
        
        switch (imageDepth){
          case depth8u:{
            fScaleRGB  = l ? 255.0/l : 255;
            fBiasRGB = (- fScaleRGB * all.minVal)/255.0;
            break;
          }
          case depth16s:{
            static const icl64f _max = (65536/2-1);
            fScaleRGB  = l ? _max/l : _max;
            fBiasRGB = (- fScaleRGB * all.minVal)/255.0;
            break;
          }
          default: // all others are drawn as float
            fScaleRGB  = l ? 255.0/l : 255;
            fBiasRGB = (- fScaleRGB * all.minVal)/255.0;
            fScaleRGB /= 255;
        }
      }else{
        fBiasRGB = bci[0]/255.0;
        fScaleRGB  = 1;
        if(imageDepth == depth16s) fScaleRGB = 127;
        else if(imageDepth != depth8u) fScaleRGB = 1./255; 
      }
      
      float c = (float)bci[1]/255;
      if(c>0) c*=10;
      float s = fScaleRGB*(1.0+c);
      float b = fBiasRGB-c/2;
      
      setup_pixel_transfer(s,s,s,s,b,b,b,b);
      
    }

    void uploadTextureData(){
      ICLASSERT_THROW(data.getDim(),ICLException("unable to draw GLImg: no texture data available"));

      if(!isDirty()) return;
      setupPixelTransfer();
      
      if(textures.size()){
        glDeleteTextures(textures.size(),textures.data());
      }
      textures.resize(data.getDim());
      glGenTextures(textures.size(), textures.data());
      
      textureInfo = TextureInfo::getCurrentTextureInfo();
      
      textureBufferMutex.lock();
      

      static GLenum types[] = { GL_UNSIGNED_BYTE, GL_SHORT, GL_FLOAT, GL_FLOAT, GL_FLOAT };
      static GLenum chan[] = { 0, GL_LUMINANCE, GL_RGB, GL_RGB, GL_RGBA};
      
      for(int y=0;y<data.getHeight();++y){
        for(int x=0;x<data.getWidth();++x){
          TextureElement &t = *data(x,y);
          
          t.tex = textures[x+data.getWidth()*y];

          glBindTexture(GL_TEXTURE_2D, t.tex);

          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,sm == interpolateNN ? GL_NEAREST : GL_LINEAR);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,sm == interpolateNN ? GL_NEAREST : GL_LINEAR);

          setupUnpackAllignment( t.size.width );

          glTexImage2D(GL_TEXTURE_2D, 0, imageChannels == 4 ? GL_RGBA : GL_RGB, 
                       t.size.width, t.size.height, 0, 
                       chan[imageChannels], types[imageDepth], t.data.data());

        }
      }
      dirty = false;
      textureBufferMutex.unlock();
    }
  };

  
  bool GLImg::Data::useDirtyFlag = true;
  
  void GLImg::set_use_dirty_flag(bool useIt){
    Data::useDirtyFlag = useIt;
  }  
  
  bool GLImg::get_use_dirty_flag(){
    return Data::useDirtyFlag;
  }
   

  
  GLImg::GLImg(const ImgBase *src, scalemode sm, int maxCellSize):m_data(new Data){
    m_data->sm = sm;
    m_data->dirty = true;
    m_data->extractedImageBuffer = 0;
    std::fill(m_data->bci,m_data->bci+3,0);

    std::fill(m_data->gridColor,m_data->gridColor+4,1.0f);
    m_data->drawGrid = false;
    m_data->isImageNull = true;
    if(src){
      update(src,maxCellSize);
    }
  }
  GLImg::~GLImg(){
    // actually not: expose m_data as a deletable to GUI thread
    // todo: check whether we are in the GUI thread : then delete data
    // else: set up qt to free the texture when possible
    delete m_data;
  }
  
  
  void GLImg::update(const ImgBase *src, int maxCellSize){
    if(maxCellSize < 1){
      ERROR_LOG("maxCellSize must be >= 1 (using max possible size instead)");
      maxCellSize = getMaxTextureSize();
    }
    if(!src){
      m_data->isImageNull = true;
      m_data->releaseTextures();
      return;
    }
    m_data->isImageNull = false;

    SmartPtr<ImgBase> pSrc;
    if(src->getChannels() > 4){
      pSrc = const_cast<ImgBase*>(src)->shallowCopy();
      pSrc->setChannels(4);
      src = pSrc.get();
    }else if(src->getChannels() == 2){
      pSrc = const_cast<ImgBase*>(src)->shallowCopy();
      pSrc->setChannels(3); // todo use a buffer for the channel data
      src = pSrc.get();
    }

    m_data->origImageDepth = src->getDepth();
    
    switch(src->getDepth()){
      case depth8u: m_data->bufferTextureData<icl8u, icl8u>(*src->as8u(), maxCellSize); break;
      case depth16s: m_data->bufferTextureData<icl16s, icl16s>(*src->as16s(), maxCellSize); break;
      case depth32s: m_data->bufferTextureData<icl32s, icl32f>(*src->as32s(), maxCellSize); break;
      case depth32f: m_data->bufferTextureData<icl32f, icl32f>(*src->as32f(), maxCellSize); break;
      case depth64f: m_data->bufferTextureData<icl64f, icl32f>(*src->as64f(), maxCellSize); break;
      default:
        ICL_INVALID_DEPTH;
    }
  }

  bool GLImg::isNull() const{
    return m_data->isImageNull;
  }

  void GLImg::setScaleMode(scalemode sm){
    m_data->sm = sm;
  }
    
  void GLImg::draw2D(const Rect &rect, const Size &win){
    ICLASSERT_RETURN(!isNull());

    float left = winToDraw(rect.x,win.width);
    float top = winToDraw(rect.y,win.height);
    float right = winToDraw(rect.right(), win.width);
    float bottom = winToDraw(rect.bottom(), win.height);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glColor4f(1,1,1,1);
    glScalef(1,-1,1); // flip y

    const float a[3]={left,top,0}, b[3]={right,top,0}, d[3]={left,bottom,0},c[3]={right,bottom,0};
    draw3D(a,b,c,d);

    if(m_data->drawGrid){
      glColor4fv(m_data->gridColor);
      
      //glLineWidth(1); dunno?
      
      float dx = (right - left)/m_data->imageSize.width;
      float dy = (bottom - top)/m_data->imageSize.height;

      float pixDX = (dx*win.width)/2.0f;
      float pixDY = (dy*win.height)/2.0f;

      // find appropriate x-Steps
      float stepx = 1;
      float stepy = 1;
      while( (stepx*pixDX) < 10) stepx *= 2;
      while( (stepy*pixDY) < 10) stepy *= 2;

      if( (stepx != 1) || (stepy != 1) ){
        float rx = stepx*dx/4.0;
        float ry = stepy*dy/4.0;
        float rr = iclMin(rx,ry);
        
        glBegin(GL_LINES);
        for(int x=0;x<=m_data->imageSize.width;x+=stepx){
          float xx = left+x*dx;
          for(int y=0;y<=m_data->imageSize.height;y+=stepy){
            float yy = top+y*dy;
            glVertex2f(xx-rr,yy);
            glVertex2f(xx+rr,yy);

            glVertex2f(xx,yy-rr);
            glVertex2f(xx,yy+rr);
          }
        }
        glEnd();
      }else{
        glBegin(GL_LINES);
        for(int x=0;x<=m_data->imageSize.width;++x){
          float xx = left+x*dx;
          glVertex2f(xx,top);
          glVertex2f(xx,bottom);
        }
        for(int y=0;y<=m_data->imageSize.height;++y){
          float yy = top+y*dy;
          glVertex2f(left,yy);
          glVertex2f(right,yy);
        }
        glEnd();
      }
    }
    
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
  }

  void GLImg::draw2D(const float a[2], const float b[2], const float c[2],const float d[2], const Size &windowSize){
    const int w = windowSize.width;
    const int h = windowSize.height;
    
    const float da[3]={winToDraw(a[0],w), winToDraw(a[1],h),0};
    const float db[3]={winToDraw(b[0],w), winToDraw(b[1],h),0};
    const float dc[3]={winToDraw(c[0],w), winToDraw(c[1],h),0};
    const float dd[3]={winToDraw(d[0],w), winToDraw(d[1],h),0};
    
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glColor4f(1,1,1,1);
    glScalef(1,-1,1); // flip y
    
    draw3D(da,db,dc,dd);

    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
  }


  static float *interpolate_billinear(const float *a, const float *b, const float *c, const float *d, 
                                      float relx,float rely, float *dst){
    float &x = relx, &y=rely, x1=1-x, y1=1-y;
    for(int i=0;i<3;++i){
      dst[i] = a[i]*x1*y1 + b[i]*x*y1 + d[i]*y*x1 + c[i]*x*y;  
    }
    return dst;
  }
  static inline float vec_len(const float *f){
    return ::sqrt (f[0]*f[0] + f[1]*f[1] + f[2]*f[2] );
  }
  
  // for some reason we have to invert the normals for texture mapping ??
  static float *interpolate_billinear_and_normalize_and_invert(const float *a, const float *b, const float *c, const float *d, 
                                                             float relx,float rely, float *dst){
    interpolate_billinear(a,b,c,d,relx,rely,dst);
    float len = vec_len(dst);
    if(len > 1.E-6){
      float ilen = -1.0/len;
      dst[0] *= ilen;
      dst[1] *= ilen;
      dst[2] *= ilen;
    }
    return dst;
  }

  void GLImg::draw3D(const float a[3],const float b[3],const float c[3],const float d[3],
                     const float na[3], const float nb[3], const float nc[3], const float nd[3],
                     const Point32f &texCoordsA,
                     const Point32f &texCoordsB,
                     const Point32f &texCoordsC,
                     const Point32f &texCoordsD){
    ICLASSERT_RETURN(!isNull());
    
    if(m_data->isDirty()) m_data->uploadTextureData();
    /**
        a -- b
        |    |
        c -- d
    */

    glPushAttrib(GL_ENABLE_BIT);
    glEnable(GL_TEXTURE_2D);

    glColor4f(1,1,1,1);
    const bool haveNormals = na && nb && nc && nd;
    
    float tmp[3];
    
    for(int y=0;y<m_data->data.getHeight();++y){
      for(int x=0;x<m_data->data.getWidth();++x){
        TextureElement &t = *m_data->data(x,y);
        glBindTexture(GL_TEXTURE_2D, t.tex);

        const float x0 = t.relUL.x, x1=t.relLR.x, y0=t.relUL.y, y1=t.relLR.y;

        glBegin(GL_QUADS);
        glTexCoord2fv(&texCoordsA.x);
        if(haveNormals){
          glNormal3fv(interpolate_billinear_and_normalize_and_invert(a,b,c,d,x0,y0,tmp));
        }
        glVertex3fv(interpolate_billinear(a,b,c,d,x0,y0,tmp));                    


        glTexCoord2fv(&texCoordsB.x);
        if(haveNormals){
          glNormal3fv(interpolate_billinear_and_normalize_and_invert(a,b,c,d,x1,y0,tmp));
        }
        glVertex3fv(interpolate_billinear(a,b,c,d,x1,y0,tmp));                    


        glTexCoord2fv(&texCoordsD.x);
        if(haveNormals){
          glNormal3fv(interpolate_billinear_and_normalize_and_invert(a,b,c,d,x1,y1,tmp));
        }
        glVertex3fv(interpolate_billinear(a,b,c,d,x1,y1,tmp));                    



        glTexCoord2fv(&texCoordsC.x);
        if(haveNormals){
          glNormal3fv(interpolate_billinear_and_normalize_and_invert(a,b,c,d,x0,y1,tmp));
        }
        glVertex3fv(interpolate_billinear(a,b,c,d,x0,y1,tmp));                    
        glEnd();
      }
    }
    
    bindWhiteTexture();
    glPopAttrib();
  }

  void GLImg::drawToGrid(int nx, int ny, const float *xs, const float *ys, const float *zs,
                         const float *nxs, const float *nys, const float *nzs,const int stride){
    if(m_data->isDirty()) m_data->uploadTextureData();
    
    if(m_data->data.getSize() != Size(1,1)){
      WARNING_LOG("GLImg::drawToGrid: the texture was split into " << m_data->data.getSize()
                  << " cells, which is not supported by this method. The first cell element is used only!");
    }
    glPushAttrib(GL_ENABLE_BIT);
    glEnable(GL_TEXTURE_2D);
    glColor4f(1,1,1,1);
    
    TextureElement &t = *m_data->data(0,0);
    glBindTexture(GL_TEXTURE_2D, t.tex);

    const bool haveNormals = nxs && nys && nzs;

    if(!haveNormals) glDisable(GL_LIGHTING);
    const float nxf = nx-1, nyf = ny-1;

    
#define AT(_p,_x,_y) _p[stride*(_x+_y*nx)]
#define X(_x,_y) AT(xs,_x,_y)
#define Y(_x,_y) AT(ys,_x,_y)
#define Z(_x,_y) AT(zs,_x,_y)
#define NX(_x,_y) AT(nxs,_x,_y)
#define NY(_x,_y) AT(nys,_x,_y)
#define NZ(_x,_y) AT(nzs,_x,_y)


#define PART(_x,_y)                                             \
    glTexCoord2f((_x)/nxf, (_y)/nyf);                       \
    if(haveNormals) glNormal3f(NX(_x,_y),NY(_x,_y), NZ(_x,_y)); \
    glVertex3f(X(_x,_y), Y(_x,_y), Z(_x,_y));    

    glBegin(GL_QUADS);
    for(int y1=1;y1<ny; ++y1){    
      for(int x1=1; x1<nx; ++x1){
        const int x0 = x1-1, y0 = y1-1;
        PART(x0,y0);
        PART(x1,y0);
        PART(x1,y1);
        PART(x0,y1);
      }
    }
    glEnd();
    
#undef AT
#undef X
#undef Y
#undef Z
#undef NX
#undef NY
#undef NZ
#undef PART

    bindWhiteTexture();
    glPopAttrib();
  }
  
  /// draws the texture to an nx x ny grid whose positions and normals are defined by a function
  void GLImg::drawToGrid(int nx, int ny, grid_function gridVertices, 
                         grid_function gridNormals){
    ICLASSERT_RETURN(!isNull());
    
    bool haveNormals = gridNormals;
    std::vector<Vec3> &grid = m_data->gridBuffer;
    std::vector<Vec3> &normals = m_data->gridNormalBuffer;
    
    grid.resize(nx*ny);
    if(haveNormals){
      normals.resize(nx*ny);
    }
    for(int y=0;y<ny;++y){
      for(int x=0;x<nx;++x){
        grid[x + nx*y] = gridVertices(x,y);
      }
    }
    const Vec3 &p = grid[0];

    if(haveNormals){
      for(int y=0;y<ny;++y){
        for(int x=0;x<nx;++x){
          normals[x + nx*y] = gridNormals(x,y);
        }
      }
      const Vec3 &n = normals[0];
      drawToGrid(nx,ny,&p[0],&p[1],&p[2], &n[0], &n[1], &n[2], 3);
    }else{
      drawToGrid(nx,ny,&p[0],&p[1],&p[2], 0,0,0,3);
    }
  }


  int GLImg::getMaxTextureSize(){
    static int MAX_TEXTURE_SIZE = 0;
    if(!MAX_TEXTURE_SIZE) glGetIntegerv(GL_MAX_TEXTURE_SIZE,&MAX_TEXTURE_SIZE);
    return MAX_TEXTURE_SIZE;
  }

  
  Size GLImg::getCells() const{
    ICLASSERT_RETURN_VAL(!isNull(), Size::null);
    return m_data->data.getSize();
  }
    
    /// binds the given texture cell using glBindTexture(...)
  void GLImg::bind(int xCell, int yCell){
    ICLASSERT_RETURN(!isNull());

    ICLASSERT_THROW(xCell >= 0 && yCell >=0 && xCell < m_data->data.getWidth() &&
                    yCell < m_data->data.getHeight(), ICLException("GLImg::bind(x,y): invalid cell index"));

    glBindTexture(GL_TEXTURE_2D, m_data->data(xCell,yCell)->tex);
  }

  int GLImg::getWidth() const{
    ICLASSERT_RETURN_VAL(!isNull(),0);
    return m_data->imageSize.width;
  }
  
  int GLImg::getHeight() const{
    ICLASSERT_RETURN_VAL(!isNull(),0);
    return m_data->imageSize.height;
  }
  
  int GLImg::getChannels() const{
    ICLASSERT_RETURN_VAL(!isNull(),0);
    return m_data->imageChannels;
  }

  void GLImg::setBCI(int b, int c, int i){
    if(m_data->bci[0] != b ||
       m_data->bci[1] != c ||
       m_data->bci[2] != i ){
      m_data->dirty = true;
    }
    m_data->bci[0] = b;
    m_data->bci[1] = c;
    m_data->bci[2] = i;
  }
  
  std::vector<Range64f> GLImg::getMinMax() const{
    ICLASSERT_RETURN_VAL(!isNull(),std::vector<Range64f>());
    return m_data->findMinMaxGeneric();
  }

  
  std::vector<icl64f> GLImg::getColor(int x, int y)const{
    ICLASSERT_RETURN_VAL(!isNull(),std::vector<double>());
    return m_data->findColorGeneric(x,y);
  }
  scalemode GLImg::getScaleMode() const{
    return m_data->sm;
  }

  const ImgBase *GLImg::extractImage() const{
    ICLASSERT_RETURN_VAL(!isNull(),0);
    return &m_data->extractImageGeneric();
  }

  const ImageStatistics &GLImg::getStats() const{
    ICLASSERT_RETURN_VAL(!isNull(),m_data->stats);
    m_data->updateStats();
    return m_data->stats;
  }
  
  void GLImg::setDrawGrid(bool enabled, float *color){
    m_data->drawGrid = enabled;
    if(color) setGridColor(color);
  }
  
  void GLImg::setGridColor(float *color){
    std::copy(color,color+4, m_data->gridColor);
  }
  
  const float *GLImg::getGridColor() const{
    return m_data->gridColor;
  }
  
  Time GLImg::getTime() const{
    ICLASSERT_RETURN_VAL(!isNull(),Time(0));
    return m_data->timeStamp;
  }

  depth GLImg::getDepth() const{
    ICLASSERT_RETURN_VAL(!isNull(),depth8u);
    return m_data->origImageDepth;
  }
  
  format GLImg::getFormat() const{
    ICLASSERT_RETURN_VAL(!isNull(),formatMatrix);
    return m_data->imageFormat;
  }

  Rect GLImg::getROI() const{
    ICLASSERT_RETURN_VAL(!isNull(),Rect::null);
    return m_data->imageROI;
  }
  
  void GLImg::lock() const{
    m_data->textureBufferMutex.lock();

  }
  void GLImg::unlock() const{
    m_data->textureBufferMutex.unlock();
  }
    
}