/*
 *  This file is part of libcxxsupport.
 *
 *  libcxxsupport 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.
 *
 *  libcxxsupport is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with libcxxsupport; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*
 *  libcxxsupport is being developed at the Max-Planck-Institut fuer Astrophysik
 *  and financially supported by the Deutsches Zentrum fuer Luft- und Raumfahrt
 *  (DLR).
 */

/*! \file ls_image.h
 *  Classes for creation and output of image files
 *
 *  Copyright (C) 2003-2012 Max-Planck-Society
 *  \author Martin Reinecke, David Larson
 */

#ifndef PLANCK_LS_IMAGE_H
#define PLANCK_LS_IMAGE_H

#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include "arr.h"
#include "datatypes.h"

/*! \defgroup imagegroup Image creation */
/*! \{ */

/*! A very simple class for storing RGB colours. */
class Colour
  {
  public:
    float r, /*!< the red component */
          g, /*!< the green component */
          b; /*!< the blue component */

    /*! Default constructor. Does not initialize \a r, \a g and \a b. */
    Colour() {}
    /*! Initializes the colour with \a R, \a G and \a B. */
    Colour (float R, float G, float B) : r(R), g(G), b(B) {}
    /*! Multiplies all components with \a fact. */
    const Colour &operator*= (float fact)
      { r*=fact; g*=fact; b*=fact; return *this; }
    /*! Returns the sum colour of \a *this and \a c2. */
    const Colour operator+ (const Colour &c2) const
      { return Colour(r+c2.r, g+c2.g, b+c2.b); }
    /*! Returns \a *this, scaled by \a f. */
    const Colour operator* (double f) const
      { return Colour(float(r*f), float(g*f), float(b*f)); }
  };

/*! A class for mapping floting-point values into colours. */
class Palette
  {
  private:
    std::vector<Colour> cv;
    std::vector<float> fv;

  public:
    /*! Adds a new data point \a f, with the corresponding colour \a c.
        The additions must be done in the order of ascending \a f. */
    void add (float f, const Colour &c)
      {
      fv.push_back(f);
      cv.push_back(c);
      }
    /*! Sets the palette to the predefined palette \a num. */
    void setPredefined(int num);
    /*! Returns the colour corresponding to the value \a f. The colour is
        determined by linear interpolation between neighbouring data points. */
    Colour Get_Colour (float f) const
      {
      if (f<=fv[0]) return cv[0];
      if (f>=fv[fv.size()-1]) return cv[cv.size()-1];
      int i=0;
      while (f>fv[i]) ++i;
      return cv[i-1]*((fv[i]-f)/(fv[i]-fv[i-1]))
           + cv[i]*((f-fv[i-1])/(fv[i]-fv[i-1]));
      }

    Colour Get_Colour_Cubehelix (float f) const
      {
	double phi, a;
	double ch_start = 0.5, ch_cycles=-1.5, ch_saturation=1.0, ch_gamma =1.5;
	phi = 2. * 3.14 * (ch_start/3.0 + f * ch_cycles);
	if ( ch_gamma != 1.0 )
	  f = pow(f, 1./ch_gamma);
	a = ch_saturation * f * (1. - f) / 2.;
	double r = f + a * (-0.14861 * cos(phi) + 1.78277 * sin(phi));
	double g = f + a * (-0.29227 * cos(phi) - 0.90649 * sin(phi));
	double b = f + a * ( 1.97294 * cos(phi));
	if ( r > 1.0 ) r = 1.0;
	if ( r < 0.0 ) r = 0.0;
	if ( g > 1.0 ) g = 1.0;
	if ( g < 0.0 ) g = 0.0;
	if ( b > 1.0 ) b = 1.0;
	if ( b < 0.0 ) b = 0.0;
	return Colour(r, g, b);
      }
  };

class Colour8
  {
  private:
    void import (const Colour &col)
      {
      using namespace std;
      r = uint8(max(0,min(255,int(col.r*256))));
      g = uint8(max(0,min(255,int(col.g*256))));
      b = uint8(max(0,min(255,int(col.b*256))));
      }

  public:
    uint8 r,g,b;

    Colour8() {}
    Colour8 (uint8 R, uint8 G, uint8 B)
      : r(R), g(G), b(B) {}
    Colour8 (const Colour &col)
      { import (col); }
    const Colour8 &operator= (const Colour &col)
      { import (col); return *this; }
    bool operator== (const Colour8 &that)
      { return (r == that.r) && (g == that.g) && (b == that.b); }
    bool operator!= (const Colour8 &that)
      { return (r != that.r) || (g != that.g) || (b != that.b); }
  };

class MP_Font
  {
  public:
    int offset, num_chars, xpix, ypix;
    const char *data;
  };

extern const MP_Font medium_bold_font;
extern const MP_Font giant_font;

/*! Class for creating and storing image files. */
class LS_Image
  {
  private:
    MP_Font font;
    arr2<Colour8> pixel;

    void write_char (int xpos, int ypos, const Colour &col, char c,
                     int scale=1);

  public:
    /*! */
    LS_Image ();
    /*! Creates an image object with a resolution of \a xres by \a yres. */
    LS_Image (int xres, int yres);
    /*! */
    ~LS_Image () {}

    /*! Fills the entire image with colour \a col. */
    void fill (const Colour &col) { pixel.fill(col); }
    /*! Sets the font used for annotations to \a fnt. */
    void set_font (const MP_Font &fnt);
    /*! Outputs the string \a text in colour \a col.
        \a xpos, \a ypos is the lower left corner;
        the font is scaled by \a scale. */
    void annotate (int xpos, int ypos, const Colour &col,
      const std::string &text, int scale=1);
    /*! Outputs the string \a text centered at position \a xpos, \a ypos
        in colour \a col. The font is scaled by \a scale. */
    void annotate_centered (int xpos, int ypos, const Colour &col,
      const std::string &text, int scale=1);
    /*! Sets the pixel \a i, \a j, to the colour \a col. */
    void put_pixel (tsize i, tsize j, const Colour &col)
      {
      if ((i<pixel.size1()) && (j<pixel.size2()))
        pixel[i][j] = col;
      }
    /*! Returns the colour of the pixel \a i, \a j, or black if the pixel
        lies outside of the image. */
    Colour8 get_pixel (tsize i, tsize j)
      {
      return ((i<pixel.size1()) && (j<pixel.size2())) ?
        pixel[i][j] : Colour8(0, 0, 0);
      }

    /*! Writes the image to \a file in uncompressed TGA format. */
    void write_TGA (const std::string &file) const;

    /*! Writes the image to \a file in run-length encoded TGA format. */
    void write_TGA_rle (const std::string &file) const;

    /*! Writes the image to \a file in binary PPM format. */
    void write_PPM (const std::string &file) const;
  };

/*! \} */

#endif
