/*-------------------------------------------------------------------------------

  File        : CImg_test.cpp

  Description : A multi-part demo demonstrating some of the CImg capabilities

  Author      : David Tschumperlé
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  ------------------------------------------------------------------------------*/

#include "../CImg.h"
using namespace cimg_library;
// The line below is necessary for compilation with non-standart C++ compilers.
#if !(( defined(_MSC_VER) && _MSC_VER<=1200 ) || defined(__DMC__))
using namespace std;
#endif

// Routine used to compute point depth in Mandelbrot fractals
inline const int mandelbrot(const double x,const double y,const int maxiter) {
  static double old_x,fx,fy;
  static int m;
  fx=fy=m=0;
  do { old_x=fx; fx=fx*fx-fy*fy+x; fy=2*old_x*fy+y; m++; }
  while (((fx*fx+fy*fy)<4) && (m<maxiter));
  return m;
}


/*---------------------------

  Main procedure

  --------------------------*/
int main() {
  cimg::info();
  
  // Demo selection menu
  //---------------------
  const unsigned char 
      white[3]  = {255,255,255}, red[3]    = {120,50,80},
      yellow[3] = {200,155,0},   green[3]  = {30,200,70};
  float t=0, rx=(float)(2*cimg::crand()), ry=(float)(2*cimg::crand());
  int demo_number,y0=2*11;
  bool stopflag = false;

  CImg<unsigned char> back(1,2,1,3,10), img;
  back(0,1,2) = 235;
  back.resize(300,240,1,3,3).ref_channel(2).noise(10,1);
  back.draw_rectangle(0,y0,back.width-1,y0+10,red).draw_text((back.dimx()-7*37)/2,y0,white,NULL,1,
                                                             "=> CImg %g Demos : \n\n\n\
- Blurring gradient\n- Rotozoom\n- Image denoising\n- Fractal animation\n- Gamma correction and histograms\n\
- Filled triangles\n- Mandelbrot explorer\n- Mini-paint\n- Soccer Bobs\n- Bump Effect\n- Bouncing Bubble\n\
- Virtual Landscape\n- Plasma & Sinus Scroll\n- Oriented convolutions\n- Shade Bobs",cimg_version);
  CImgDisplay disp(back,"CImg Demo Menu",0);
  img=back; back*=0.2;
  for(y0+=3*11; !stopflag; ) {
    while(!(disp.button&1) && !stopflag) {
      if (disp.closed || disp.key == cimg::keyQ || disp.key==cimg::keyESC) stopflag=true;
      img*=0.8; img+=back;
      int y = disp.mousey;
      if (y>=y0 && y<y0+15*11) {
        y = (y/11)*11+5;
        for (int yy=y-5; yy<=y+5; yy++) img.draw_rectangle(0,yy,0,1,img.width-1,yy,0,1,(unsigned char)(130-15*abs(yy-y)));
        img.draw_triangle(2,y-4,2,y+4,8,y,yellow).draw_triangle(img.width-2,y-4,img.width-2,y+4,img.width-8,y,yellow);
      }
      for (int i=0; i<20; i++) {
        const int 
          mx = (int)(img.dimx()/2+(img.dimx()/2-30)*cos(3*t+rx*i*36*cimg::PI/180)), 
          my = (int)(img.dimy()/2+(img.dimy()/2-30)*sin(4*t+ry*i*36*cimg::PI/180));
        img.draw_circle(mx,my,10,i%3?(i%2?green:red):yellow,0.2f).draw_circle(mx+3,my-3,3,white,0.1f);
      }
      if ((t+=0.007f)>1) { rx=(float)(2*cimg::crand()); ry=(float)(2*cimg::crand()); t=0; }
      disp.resize(disp).display(img).wait(25);
    }
    disp.button=0;
    if (!stopflag) demo_number = (disp.mousey-y0)/11; else break;
    
    switch(demo_number) {
    case 0: 
      // Blurring gradient demo
      //------------------------
      {
        const CImg<> src = "img/milla.ppm";
        CImgl<> grad = src.get_gradientXY(3);
        CImgl<unsigned char> visu(src,((grad[0].pow(2))+(grad[1].pow(2))).sqrt().normalize(0,255),src);
        CImgDisplay disp(visu,"Image, Gradient Norm and Blurring Gradient",0,1);
        for (double sigma=0; !disp.closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC; sigma+=0.05) {
          visu[2] = visu[1].get_blur((float)fabs(30*cos(sigma))).normalize(0,255);
          disp.resize().display(visu).wait(20);
        }
      }
      break;

      case 1: 
        // Rotozoom demo
        //---------------
      {
        CImg<unsigned char> src = CImg<unsigned char>("img/milla.ppm").resize(320,200,1,3,3), img(src);
        CImgDisplay disp(src,"Rotozoom Demo",0);
        float alpha=0,t=0,angle=0,zoom0 = 1;
        unsigned char color[3]={0,0,0};
        while(!disp.closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC) {
          cimg_mapYV(src,y,k) {
            const int xc = 4*src.width+(int)(60*sin((float)y*3/src.height+10*t));
            cimg_mapX(src,x) {
              const float val = (float)(src((xc+x)%src.width,y,0,k)*
                                        (1.3f+0.20*sin(alpha+k*k*((float)src.width/2-x)*
                                                            ((float)src.height/2-y)*cos(t)/300.0)));
              img(x,y,0,k) = (unsigned char)(val>255.0f?255:val);
            }
          }
          const float zoom=(float)(zoom0+0.3*(1+cos(3*t)));

          img.get_rotate(angle,0.5f*img.width,0.5f*img.height,1+zoom,1).
            draw_text("Mouse buttons\nto zoom in/out",3,3,color).display(disp);
          alpha+=0.7f;
          t+=0.01f;
          angle+=0.8f;
          if (disp.button&1) zoom0+=0.10f;
          if (disp.button&2) zoom0-=0.10f;
          disp.resize(disp).wait(20);
        }
      }
      break;

      case 2: 
        // Image denoising (Total Variation)
        //-----------------------------------
      {
        const CImg<> src = CImg<>("img/milla.ppm").noise(-3,2).noise(-10,1);
        CImgl<> images(src,src);
        CImgDisplay disp(images,"Image denoising",1,1);
        float white[3]={255,255,255}, black[3]={0,0,0};
        CImg_3x3(I,float);
        for (unsigned int iter=0; !disp.closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC; iter++) {
          CImg<> veloc(src);
          cimg_mapV(src,k) cimg_map3x3(images[1],x,y,0,k,I) {
            const float ix = (Inc-Ipc)/2, iy = (Icn-Icp)/2, ng = ix*ix + iy*iy;
            veloc(x,y,k) = (ng>1e-14)?( ix*ix * ( Icn + Icp - 2*Icc ) + iy*iy * ( Inc + Ipc - 2*Icc ) - ix*iy * ( Ipp + Inn - Ipn - Inp )/2 )/ng:0;
          }
          images[1]+=veloc*0.02;
          images[0].draw_text(0,0,white,black,1,"iter %d",iter++);
          disp.resize(disp).display(images);
        }
      }
      break;

    case 3:
      // Fractal-like animation
      //------------------------
      {
        CImg<unsigned char> img(400,300,1,3,0),noise(3,2,1,3);
        CImgDisplay disp(img,"Super Zoomator",0,1);
        double zoom=0;
        for (unsigned int iter=0; !disp.closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC; iter++,zoom+=0.2) {
          img.draw_image(noise.fill(0).noise(255,1),(img.width-noise.width)/2,(img.height-noise.height)/2);
          img.rotate((float)(10*sin(iter/25.0)),0.5f*img.width,0.5f*img.height,(float)(1.04+0.02*sin(zoom/10)),0);
          disp.resize(img.resize(disp.window_width,disp.window_height)).display(img).wait(25);
        }
      }
      break;

    case 4:
      // Gamma correction and histogram visualization
      //----------------------------------------------
      {
        CImg<> img = CImg<>("img/milla.ppm").normalize(0,1);
        CImgl<unsigned char> visu(img*255, CImg<unsigned char>(512,300,1,3,0));
        const unsigned char yellow[3] = {255,255,0}, blue[3]={0,155,255}, blue2[3]={0,0,255}, blue3[3]={0,0,155}, white[3]={255,255,255};
        CImgDisplay disp(visu,"Image and Histogram (Mouse click to set the Gamma correction)",0);
        for (double gamma=1;!disp.closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC; ) {
          cimg_mapXYZV(visu[0],x,y,z,k) visu[0](x,y,z,k) = (unsigned char)(pow((double)img(x,y,z,k),1.0/gamma)*256);  
          const CImg<> hist = visu[0].get_histogram(50,0,255);
          visu[1].fill(0).draw_text(50,5,white,NULL,1,"Gamma = %g",gamma).
            draw_graph(hist,yellow,1,0,20000).draw_graph(hist,white,2,0,20000);
          const int xb = (int)(50+gamma*150);
          visu[1].draw_rectangle(51,21,xb-1,29,blue2).draw_rectangle(50,20,xb,20,blue).draw_rectangle(xb,20,xb,30,blue);
          visu[1].draw_rectangle(xb,30,50,29,blue3).draw_rectangle(50,20,51,30,blue3);
          if (disp.button && disp.mousex>=img.dimx()+50 && disp.mousex<=img.dimx()+450) gamma = (disp.mousex-img.dimx()-50)/150.0;
          disp.resize(disp).display(visu).wait();
        }
      }
      break;

    case 5:
      // Filled triangle demos
      //-----------------------
      {
        CImg<> background(640,480,1,3);
        cimg_mapXY(background,x,y) {
          background(x,y,0) = (float)(x*cos(6.0*y/background.height)+y*sin(9.0*x/background.width));
          background(x,y,1) = (float)(x*sin(8.0*y/background.height)-y*cos(11.0*x/background.width));
          background(x,y,2) = (float)(x*cos(13.0*y/background.height)-y*sin(8.0*x/background.width));
        }
        background.normalize(0,180);
        CImg<unsigned char> img0(background), img;
        unsigned char colline[3] = { 255,255,255 }, color[100][3];
        CImgDisplay disp(img0,"Filled Triangle Demo !",0,3);
        
        float posx[100],posy[100],rayon[100],angle[100],veloc[100],opacity[100];
        int num=1;
        srand((unsigned int)time(NULL));
        for (int k=0; k<100; k++) {
          posx[k]  = (float)(cimg::rand()*img0.width);
          posy[k]  = (float)(cimg::rand()*img0.height);
          rayon[k] = (float)(10+cimg::rand()*50);
          angle[k] = (float)(cimg::rand()*360);
          veloc[k] = (float)(cimg::rand()*20-10);
          color[k][0] = (unsigned char)(cimg::rand()*255);
          color[k][1] = (unsigned char)(cimg::rand()*255);
          color[k][2] = (unsigned char)(cimg::rand()*255);
          opacity[k] = (float)(0.3+1.5*cimg::rand());
        }
        while(!disp.closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC) {          
          img = img0;
          for (int k=0; k<num; k++) {
            const int
              x0 = (int)(posx[k]+rayon[k]*cos(angle[k]*cimg::PI/180)),
              y0 = (int)(posy[k]+rayon[k]*sin(angle[k]*cimg::PI/180)),
              x1 = (int)(posx[k]+rayon[k]*cos((angle[k]+120)*cimg::PI/180)),
              y1 = (int)(posy[k]+rayon[k]*sin((angle[k]+120)*cimg::PI/180)),
              x2 = (int)(posx[k]+rayon[k]*cos((angle[k]+240)*cimg::PI/180)),
              y2 = (int)(posy[k]+rayon[k]*sin((angle[k]+240)*cimg::PI/180));
            if (k%10) img.draw_triangle(x0,y0,x1,y1,x2,y2,color[k],opacity[k]);
            else img.draw_triangle(x0,y0,x1,y1,x2,y2,img0,0,0,img0.width-1,0,0,img.height-1,opacity[k]);
            img.draw_line(x0,y0,x1,y1,colline,(unsigned long)~0,opacity[k]).
              draw_line(x1,y1,x2,y2,colline,(unsigned long)~0,opacity[k]).
			  draw_line(x2,y2,x0,y0,colline,(unsigned long)~0,opacity[k]);
            angle[k]+=veloc[k];
            if (disp.button && disp.mousex>0 && disp.mousey>0) {
              float u = disp.mousex-posx[k], v = disp.mousey-posy[k];
              if (disp.button&2) { u=-u; v=-v; }
              posx[k]-=0.1f*u, posy[k]-=0.1f*v;
              if (posx[k]<0 || posx[k]>=img.dimx()) posx[k]=(float)(cimg::rand()*img.width);
              if (posy[k]<0 || posy[k]>=img.dimy()) posy[k]=(float)(cimg::rand()*img.height);
            }
          }
          disp.display(img).resize(false).wait(25);
          img0.resize(disp);
          num++; if (num>100) num=100;
        }
      }
      break;

    case 6: 
      // Mandelbrot explorer
      //----------------------
      {
        CImg<unsigned char> img(800,600,1,3,0);
        CImg<unsigned int> iterimg(img.width,img.height);
        CImgDisplay disp(img,"Mandelbrot Explorer",0,3);
        unsigned char color[3]={255,255,255};
        for (bool endflag=false; !endflag;) {
          bool stopflag = false;
          double xmin=-2.25, xmax=1, ymin=-1.5, ymax=1.5;
          const unsigned int cr = (2+(int)(8*cimg::rand()))*4;
          const unsigned int cg = (2+(int)(8*cimg::rand()))*4;
          const unsigned int cb = (2+(int)(8*cimg::rand()))*4;
          unsigned int maxiter=64;

          while(!stopflag) {
            const double dx=(xmax-xmin)/img.width;
            const double dy=(ymax-ymin)/img.height;
            double y = ymin;
            cimg_mapY(img,py) {
              double x = xmin;
              cimg_mapX(img,px) {
                const unsigned int m = mandelbrot(x,y,maxiter);
                iterimg(px,py) = m;
                img(px,py,0)=(unsigned char)(cr*m);
                img(px,py,1)=(unsigned char)(cg*m);
                img(px,py,2)=(unsigned char)(cb*m);
                x+=dx;
              }
              y+=dy;
            }
            int selection[6];
            img.feature_selection(selection,2,disp,NULL,color);
            if (selection[0]>=0 && selection[1]>=0 && selection[3]>=0 && selection[4]>=0) {
              if (selection[3]-selection[0]<5 || selection[4]-selection[1]<5) stopflag=true;
              xmax = xmin + selection[3]*dx; ymax = ymin + selection[4]*dy;
              xmin = xmin + selection[0]*dx; ymin = ymin + selection[1]*dy;
            }
            CImgStats stats(iterimg,0);
            if (stats.mean>0.7*maxiter) maxiter+=64;
            if (maxiter>1024) maxiter=1024;
            if (disp.closed || disp.key==cimg::keyQ || disp.key==cimg::keyESC) stopflag=endflag=true;
          }
        }
      }
      break;

    case 7:
      // Mini-Paint demo
      //-----------------
      {
        int xo=-1,yo=-1,x=-1,y=-1;
        CImg<unsigned char> img(256,256+64,1,3,0);
        unsigned char color[3]={255,255,255};
        cimg_mapXY(img,xx,yy) if (yy>=256) { 
		   img(xx,yy,0) = (unsigned char)xx;
		   img(xx,yy,1) = (unsigned char)((yy-256)*4);
		   img(xx,yy,2) = (unsigned char)((3*xx)%256);
		}
        CImgDisplay disp(img.draw_text("   ",5,5,color,color),"Mini-Paint",0);
        while (!disp.closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC) {
          if (xo<0 || yo<0) { xo=disp.mousex; yo=disp.mousey; } else { xo=x; yo=y<256?y:255; }
          x = disp.mousex;
          y = disp.mousey;
          if (disp.button&1) {
            if (y<256 && (xo!=x || yo!=y)) img.draw_line(xo,yo,x,y,color);
            if (y>=256) { color[0]=img(x,y,0); color[1]=img(x,y,1); color[2]=img(x,y,2); img.draw_text("   ",5,5,color,color); }
          }
          if (disp.button&2) img.draw_fill(x,y,color);
          if (disp.button&4 && y<=253) img.draw_circle(x,y,5,color).display(disp);
          if (disp.key) cimg_mapV(img,k) img.ref_lineset(0,255,0,k).fill(0);
          disp.resize(disp).display(img,0,255).wait();
        }
      }
      break;

    case 8:
      // Soccer Bobs Demo
      //------------------
      {
        const unsigned int nb_canvas=16;
        unsigned int curr_canvas=1;
        float zoom=0.2f;
        unsigned char color[3]={255,255,0};
        CImg<unsigned char> foot = "img/foot.ppm",*canvas = new CImg<unsigned char>[nb_canvas];
        CImg<float> mask(foot.width,foot.height);
        canvas[0] = CImg<unsigned char>(640,480,1,3,0); cimg_mapXY(canvas[0],x,y) canvas[0](x,y,1)=(unsigned char)(20+(y*215/canvas[0].height)+cimg::crand()*19);
        canvas[0].draw_text("Left/Right Mouse Button = Zoom In/Out\nMiddle Button = Reset Screen",1,1,color);
        for (unsigned int l=1; l<nb_canvas; l++) canvas[l] = canvas[0];
        { cimg_mapXY(mask,x,y) mask(x,y) = (foot(x,y,0)==255 && !foot(x,y,1) && !foot(x,y,2))?0:0.5f; }
        CImgDisplay disp(canvas[0],"Unlimited Soccer Bobs",0);
        while (!disp.closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC) {
          if (disp.mousex>=0) {
            canvas[curr_canvas].draw_image(foot.get_resize((int)(foot.width*zoom),(int)(foot.height*zoom)),
					   mask.get_resize((int)(foot.width*zoom),(int)(foot.height*zoom)),
					   (int)(disp.mousex-0.5*zoom*foot.width),(int)(disp.mousey-0.5*zoom*foot.height),0,0);
          }
          if (disp.button&1) zoom+=0.03f;
          if (disp.button&2) zoom-=0.03f;
          if (disp.button&4) for (unsigned int l=1; l<nb_canvas; l++) canvas[l] = canvas[0];
          if (zoom>1) zoom=1;
          if (zoom<0.1) zoom=0.1f;
          canvas[curr_canvas].display(disp);
          curr_canvas++; curr_canvas%=nb_canvas; if (!curr_canvas) curr_canvas++;
          disp.resize(disp).wait(20);
        }
        delete[] canvas;
      }
      break;

    case 9:
      // Bump Effect
      //-------------
      {
        float t=0,color = 255;
        CImg<> logo = CImg<>(56,32,1,1,0).draw_text("I Love\n CImg!",6,5,&color).resize(-800,-800,1,1,3).blur(6).normalize(0,255);
        logo+=CImg<>(logo,false).fill(0).noise(80,1).deriche(2,0,'y',0).deriche(10,0,'x',0);
        CImgl<> grad = logo.get_gradientXY();
        cimgl_map(grad,l) grad[l].normalize(-140,140);
        logo.normalize(0,255);
        CImg<> light(300+2*logo.width,300+2*logo.height);
        light.draw_gaussian(0.5f*light.width,0.5f*light.height,80,&color);
        CImg<unsigned char> img(logo.width,logo.height,1,3,0);
        CImgDisplay disp(img,"Bump Effect",0);
        while(!disp.closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC) {
          const int
            mousex = (disp.mousex>=0 && disp.button)?disp.mousex:(int)(img.dimx()/2 + img.dimx()*cos(1*t)/2),
            mousey = (disp.mousey>=0 && disp.button)?disp.mousey:(int)(img.dimy()/2 + img.dimy()*sin(3*(t+=0.03f))/2);
          cimg_mapXY(img,x,y) {
            const int gx = (int)grad[0](x,y), gy = (int)grad[1](x,y);
            const float val = 40+(gx+gy)/2+light(light.dimx()/2+mousex-x+gx,light.dimy()/2+mousey-y+gy);
            img(x,y,0) = img(x,y,1) = img(x,y,2) = (unsigned char)(val>255?255:(val<0?0:val));
          }
          img.draw_image(logo,0,0,0,1,0.1f).display(disp);	  
          disp.resize(disp).wait(30);
        }
      }
      break;

    case 10:
      // Bouncing bubble
      //------------------
      {
        CImg<unsigned char> back(320,256,1,3,0),img;
        cimg_mapXY(back,x,y) back(x,y,2) = (unsigned char)((y<2*back.dimy()/3)?30:(255-2*(y+back.dimy()/2)));
        CImgDisplay disp(back,"Bouncing bubble",0,1);
        const unsigned char col1[3]={40,100,10}, col2[3]={20,70,0}, col3[3]={40,150,10}, col4[3]={200,255,100}, white[3]={255,255,255};
        double u = sqrt(2.0),  cx = back.dimx()/2, t = 0, vt=0.05, vx = 2;
        while (!disp.closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC) {
          img = back;
          int xm =(int)cx, ym = (int)(img.dimy()/2-70 + (img.dimy()/2+10)* (1-cimg::abs(cos((t+=vt)))));
          float r1 = 50, r2 = 50;
          vt=0.05;
          if (xm+r1>=img.dimx())    { const float delta = (xm+r1)-img.width; r1-=delta; r2+=delta; }
          if (xm-r1<0)              { const float delta = -(xm-r1); r1-=delta; r2+=delta; }
          if (ym+r2>=img.dimy()-40) { const float delta = (ym+r2)-img.height+40; r2-=delta; r1+=delta; vt=0.05-0.0015*(50-r2); }
          if (ym-r2<0)              { const float delta = -(ym-r2); r2-=delta; r1+=delta; }
          img.draw_ellipse(xm,ym,r1,r2,1,0,col1);
          img.draw_ellipse((int)(xm+0.03*r1*u),(int)(ym-0.03*r2*u),0.85f*r1,0.85f*r2,1,0,col2);
          img.draw_ellipse((int)(xm+0.1*r1*u),(int)(ym-0.1*r2*u),0.8f*r1,0.8f*r2,1,0,col1);
          img.draw_ellipse((int)(xm+0.2*r1*u),(int)(ym-0.2*r2*u),r1/2,r2/2,1,0,col3); 
          img.draw_ellipse((int)(xm+0.3*r1*u),(int)(ym-0.3*r2*u),r1/4,r2/4,1,0,col4);
          img.draw_image(img.get_crop(0,img.height-80,img.width-1,img.height-40).flip('y'),0,img.height-40,0,0,0.45f);
          img.draw_text(xm-56,(int)(ym-r2-20),white,NULL,1,"Bubble (%d,%d)",xm,ym);
          if ((cx+=20*vt*vx)>=img.dimx()-30 || cx<30) vx=-vx;
          disp.display(img).wait(20);
          if (disp.resized) {
            disp.resize(disp.window_width>200?disp.window_width:200,disp.height,false);
            back.resize(disp);
            cx = back.dimx()/2;
          }
        }	
      }
      break;
      
    case 11: 
      // Virtual landscape
      //-------------------
      {
        CImg<int> background(400,300,1,3,0), visu(background);
        cimg_mapXY(background,x,y) {
          if (y>background.dimy()/2) { background(x,y,2)=255; background(x,y,0)=(y-background.height/2)*512/background.height; }
          else background(x,y,2)=y*512/background.dimy();
        }
        CImgDisplay disp(visu.draw_text("Please wait, generating landscape....",10,10).normalize(0,255),"Virtual Landscape",0,1);
        const CImg<> map = 5*(CImg<>(700,700,1,1,300).noise(300).draw_plasma(0.2,300).normalize(-140,150).blur(5).cut(0,150));
        CImg<> cmap(map,false);
        CImg_3x3(I,float);
        { cimg_map3x3(map,x,y,0,0,I) { const float nox=0.5f*(Inc-Ipc), noy=0.5f*(Icn-Icp); cmap(x,y)=cimg::max(0.0f,0.5f*nox+noy); }}
        cmap.normalize(0,255);
        
        for (float t=0; !disp.closed && disp.key!=cimg::keyESC && disp.key!=cimg::keyESC; t+=0.0025f) {
          visu = background;
          int xm = (int)(map.dimx()/2 + (map.dimx()/3)*cos(4.2*t)), ym = (int)(map.dimy()/2 + (map.dimy()/3)*sin(5.6*t));
          const CImg<> smap = map.get_crop(xm,ym,xm+100,ym+90), scmap = cmap.get_crop(xm,ym,xm+100,ym+90);
          CImg<int> ymin(visu.width,1,1,1,visu.dimy()), ymax(ymin,0);
          cimg_mapY(smap,z) {
            const int y0 = (int)(visu.dimy()-1-10*pow((double)z,0.63) + 80);
            cimg_mapX(visu,x) {
              const int nz = smap.dimy()-z;            
              float mx = x*(smap.width-2.0f*nz*0.2f)/visu.width + nz*0.2f;
              const int y = (int)(y0-smap.linear_pix1d(mx,z)/(1+0.02*z));
              const float cc = (float)scmap.linear_pix1d(mx,z);
              if (y<visu.dimy() && y<ymin(x)) {
                const float cz = (smap.height-(float)z)/smap.height, czz = cz>0.25?1:4*cz;
                if (y!=y0) for (int l=y>0?y:0; l<ymin(x); l++) {
                  visu(x,l,0) = (int)((1-czz)*visu(x,l,0)+4*cc*czz); 
                  visu(x,l,1) = (int)((1-czz)*visu(x,l,1)+3*cc*czz);
                  visu(x,l,2) = (int)((1-czz)*visu(x,l,2)+  cc*czz); 
                } else for (int l=y>0?y:0; l<ymin(x); l++) { int cl = l-visu.height/2;
                  visu(x,l,0) = 10; visu(x,l,1) = 200-cl; visu(x,l,2) = 255-cl;
                }
              }
              ymin(x)=cimg::min(ymin(x),y);
              ymax(x)=cimg::max(ymax(x),y);
            }
          }
          disp.resize(disp).display(visu.cut(0,255)).wait(20);
        }
      }
      break;

    case 12:
      // Plasma effect with sinus scrolling.
      //------------------------------------
      {
        CImg<> plasma,camp(3),cfreq(3),namp(3),nfreq(3);
        CImgl<unsigned char> font = CImg<unsigned char>::get_font7x11();
        CImg<unsigned char> visu(400,300,1,3,0);
        const char *title = "Plasma Effect (Space bar to toggle fullscreen)", 
          *text = "         This is a nice plasma effect, isn't it ?";
        CImgDisplay *disp = new CImgDisplay(visu,title,0);
        const unsigned char white[3]={255,255,255};
        bool fullscreen = false;
        unsigned int cplasma = 0, pos = 0, tpos = 0;
        float tx=0,ts=0,alpha=2.0f,beta=0;
        namp.fill(0).noise(visu.height/4,0);
        nfreq.fill(0).noise(0.1);
        
        // Generate plasma and scroll font
        visu.draw_text("Please wait, the plasma is generating...",10,10,white).display(*disp);
        const unsigned int nb_plasmas = 5;
        plasma = CImg<>(3*visu.width/2,visu.height,1,nb_plasmas,0);
        plasma.noise(100).draw_plasma();
        cimg_mapV(plasma,k) plasma.ref_channel(k).blur((float)(cimg::rand()*6)).normalize(0,255);
        cimgl_map(font,l) font[l].normalize(0,255).resize(16,24,1,1).blur(0.9f).threshold(70);
        CImg<unsigned char> scroll(visu.width+font[' '].width,font[' '].height,1,1,0);

        // Do the animation
        while (!disp->closed && disp->key!=cimg::keyQ && disp->key!=cimg::keyESC) {
          
          if (disp->key==cimg::keySPACE) {
            delete disp;
            if (fullscreen) {
              disp = new CImgDisplay(visu,title,0); fullscreen=false; 
            } else { disp = new CImgDisplay(visu,NULL,0,3,true); fullscreen=true; }        
          }
          if (alpha>1) {
            alpha-=1;
            cplasma=(cplasma+1)%plasma.dim;
            camp = namp;
            cfreq = nfreq;
            namp.fill(0).noise(100).normalize(0,visu.height/4.0f);
            nfreq.fill(0).noise(0.2);
          }
          
          const unsigned int v0=cplasma,v1=(cplasma+1)%plasma.dim,v2=(cplasma+2)%plasma.dim,v3=(cplasma+3)%plasma.dim;
          cimg_mapY(visu,y) {     
            unsigned int xR1 = (unsigned int)(camp(0)*(1+sin(tx+cfreq(0)*y)));
            unsigned int xG1 = (unsigned int)(camp(1)*(1+sin(tx+cfreq(1)*y)));
            unsigned int xB1 = (unsigned int)(camp(2)*(1+sin(tx+cfreq(2)*y)));
            unsigned int xR2 = (unsigned int)(namp(0)*(1+sin(tx+nfreq(0)*y)));
            unsigned int xG2 = (unsigned int)(namp(1)*(1+sin(tx+nfreq(1)*y)));
            unsigned int xB2 = (unsigned int)(namp(2)*(1+sin(tx+nfreq(2)*y)));
            cimg_mapX(visu,x) {           
              visu(x,y,0) = (unsigned char)((1-alpha)*plasma(xR1++,y,v0)+alpha*plasma(xR2++,y,v1));
              visu(x,y,1) = (unsigned char)((1-alpha)*plasma(xG1++,y,v1)+alpha*plasma(xG2++,y,v2));
              visu(x,y,2) = (unsigned char)((1-alpha)*plasma(xB1++,y,v2)+alpha*plasma(xB2++,y,v3));
            }
          }    
          if (!pos) { scroll.draw_image(font(text[tpos++]),scroll.width-font[' '].width); tpos%=strlen(text); }
          pos=(pos+=2)%font[' '].width;
          cimg_mapY(scroll,yy) memmove(scroll.ptr(0,yy),scroll.ptr(2,yy),scroll.width-2);
          cimg_mapX(visu,x) {
            const int y0 = (int)(visu.height/2+visu.height/4*sin(ts+x/(70+30*cos(beta))));
            cimg_mapY(scroll,y) {
              if (scroll(x,y)) {
                const unsigned int y1 = y0+y+2; visu(x,y1,0)=visu(x,y1,1)=visu(x,y1,2)=0;
                const unsigned int y2 = y1-2;   visu(x,y2,0)=visu(x,y2,1)=visu(x,y2,2)=255;
              }
            }
          }
          alpha+=0.007f;
          beta+=0.04f;
          tx+=0.09f;
          ts+=0.04f;
          disp->resize(*disp).display(visu).wait(20);
        }
        delete disp;
      }
      break;

    case 13:
      // Oriented convolutions
      //-----------------------
      {
        const CImg<unsigned char> img = CImg<unsigned char>("img/sh0r.pgm").noise(50,2);
        CImg<float> mask(16,16);        
        CImgl<unsigned char> visu(img,img,img);
        const float value = 255;
        CImgDisplay disp(visu,"Original image, Oriented kernel and Convolved image",0,1);
        for (float angle=0; !disp.closed && disp.key!=cimg::keyQ && disp.key!=cimg::keyESC; angle+=0.1f) {
          const float ca = (float)cos(angle), sa = (float)sin(angle);
          const CImg<> 
            u = CImg<>(1,2).fill(ca,sa),
            v = CImg<>(1,2).fill(-sa,ca),
            tensor = 30*u*u.get_transpose() + 0.2*v*v.get_transpose();
          mask.draw_gaussian(0.5f*mask.width,0.5f*mask.height,1,tensor,&value);
          mask/=mask.sum();
          visu[1] = mask.get_resize(img).normalize(0,255).
            draw_text(2,2,&value,NULL,1,"Angle = %d deg",cimg::modi((int)(angle*180/cimg::PI),360));
          visu[2] = img.get_convolve(mask);
          disp.resize(disp.window_width,(int)(disp.height*disp.window_width/disp.width)).display(visu).wait(25);
        }
      }
      break;

    case 14:
      // Shade bobs
      //------------
      {
        CImg<unsigned char> img(300,300,1,1,0), palette;
        CImgDisplay *disp = new CImgDisplay(img.width,img.height,"Shade Bobs",0);
        float t=100, rx=0, ry=0, rz=0, rt=0, rcx=0;
        const unsigned char one = 1;
        int nbbobs = 0, rybobs = 0;
        
        while (!disp->closed) {

          if ((t+=0.015f)>4*cimg::PI) { 
            img.fill(0);
            rx=(float)(cimg::crand());
            ry=(float)(cimg::crand()); 
            rz=(float)(cimg::crand());
            rt=(float)(cimg::crand());
            rcx = (float)(cimg::crand());
            t=0; 
            palette = CImg<unsigned char>(3,4+(int)(12*cimg::rand()),1,1,0).noise(255,2).resize(3,256,1,1,3);
            palette(0)=palette(1)=palette(2)=0;
            nbbobs = 10+(int)(cimg::rand()*40);
            rybobs = (10+(int)(cimg::rand()*50))*cimg::min(img.width,img.height)/300;
          }        
          
          for (int i=0; i<nbbobs; i++) {
            const float 
              r = (float)(ry + rx*cos(6*rz*t) + (1-rx)*sin(6*rt*t)),
              a = (float)((360*sin(rz*t)+30*ry*i)*cimg::PI/180),
              ax = (float)(i*2*cimg::PI/nbbobs+t);
            const int
              cx = (int)((1+rcx*cos(ax)+r*cos(a))*img.dimx()/2),
              cy = (int)((1+rcx*sin(ax)+r*sin(a))*img.dimy()/2);
            img.draw_circle(cx,cy,(float)rybobs,&one,-1.0f);
          }
          
          CImg<unsigned char> visu(img.width,img.height,1,3);
          cimg_mapXY(visu,x,y) { 
            const unsigned char *col = palette.ptr(0,img(x,y));
            visu(x,y,0) = *(col++);
            visu(x,y,1) = *(col++);
            visu(x,y,2) = *(col++);
          }
          if (disp->resized) { img.resize(disp->resize(false)); t = 100; }
          else disp->display(visu).wait(20);
	  if (disp->key) { disp->key=0; t=100; }
        }
        delete disp;
      }
      break;
    }    
  }

  // Exit demo menu
  //----------------
  fprintf(stderr,"Exit CImg Demo\n");
  exit(0);
  return 0;
}


