
// epimgconv: Enterprise 128 image converter utility
// Copyright (C) 2008-2016 Istvan Varga <istvanv@users.sourceforge.net>
//
// 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.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// The Enterprise 128 program files generated by this utility are not covered
// by the GNU General Public License, and can be used, modified, and
// distributed without any restrictions.

#ifndef EPIMGCONV_EPIMGCONV_HPP
#define EPIMGCONV_EPIMGCONV_HPP

#include "ep128emu.hpp"
#include "img_cfg.hpp"

#include <cmath>

namespace Ep128ImgConv {

  class ImageConvConfig;
  class YUVImageConverter;

  void defaultProgressMessageCb(void *userData, const char *msg);
  bool defaultProgressPercentageCb(void *userData, int n);

  class ImageData {
   private:
    unsigned char *buf;
    unsigned char *fileHeader;
    unsigned char **videoMode0;
    unsigned char **fixBias0;
    unsigned char **palette0;
    unsigned char **attribute0;
    unsigned char **videoData0;
    unsigned char **videoMode1;
    unsigned char **fixBias1;
    unsigned char **palette1;
    unsigned char **attribute1;
    unsigned char **videoData1;
    size_t  dataSize;
    int     width;
    int     height;
    int     videoMode;
    int     biasResolution;
    int     paletteResolution;
    int     interlaceMode;
    int     compressionType;
   public:
    ImageData(int width_ = 40, int height_ = 240, int videoMode_ = 0x42,
              int biasResolution_ = 0, int paletteResolution_ = 1,
              int interlaceMode_ = 0, int compressionType_ = 0);
    virtual ~ImageData();
    inline size_t size() const
    {
      return dataSize;
    }
    inline unsigned char& operator[](long n)
    {
      return buf[n];
    }
    inline const unsigned char& operator[](long n) const
    {
      return buf[n];
    }
    inline size_t getVideoModeTableOffset(int fieldNum = 0) const
    {
      const unsigned char * const *p =
          (fieldNum == 0 ? videoMode0 : videoMode1);
      if (!p)
        return 0;
      return size_t(p[0] - buf);
    }
    inline size_t getFixBiasTableOffset(int fieldNum = 0) const
    {
      const unsigned char * const *p = (fieldNum == 0 ? fixBias0 : fixBias1);
      if (!p)
        return 0;
      return size_t(p[0] - buf);
    }
    inline size_t getPaletteDataOffset(int fieldNum = 0) const
    {
      const unsigned char * const *p = (fieldNum == 0 ? palette0 : palette1);
      if (!p)
        return 0;
      return size_t(p[0] - buf);
    }
    inline size_t getAttributeDataOffset(int fieldNum = 0) const
    {
      const unsigned char * const *p =
          (fieldNum == 0 ? attribute0 : attribute1);
      if (!p)
        return 0;
      return size_t(p[0] - buf);
    }
    inline size_t getVideoDataOffset(int fieldNum = 0) const
    {
      const unsigned char * const *p =
          (fieldNum == 0 ? videoData0 : videoData1);
      if (!p)
        return 0;
      return size_t(p[0] - buf);
    }
    void setBorderColor(int c);
    void setFixBias(long yc, int c);
    void setPaletteColor(long yc, int n, int c);
    void setAttributes(long xc, long yc, int c0, int c1);
    void setPixel(long xc, long yc, int c);
  };

}       // namespace Ep128ImgConv

#ifndef DISABLE_OPENGL_DISPLAY
#  include <FL/Fl.H>
#  include <FL/Fl_File_Chooser.H>
class Ep128ImgConvGUI;
class Ep128ImgConvGUI_Display;
class Ep128ImgConvGUI_Nick;
#  include "img_disp.hpp"
#  include "epimgconv_fl.hpp"
#endif

namespace Ep128ImgConv {

  class YUVImage {
   private:
    float   **bufY;
    float   **bufU;
    float   **bufV;
    int     width;
    int     height;
    float   borderColorY;
    float   borderColorU;
    float   borderColorV;
    // --------
    static void allocateBuffers(float**& bufY_, float**& bufU_, float**& bufV_,
                                int w, int h);
    static void freeBuffers(float**& bufY_, float**& bufU_, float**& bufV_,
                            int w, int h);
   public:
    YUVImage(int w, int h);
    YUVImage(const YUVImage& r);
    virtual ~YUVImage();
    YUVImage& operator=(const YUVImage& r);
    void clear(float y_ = 0.0f, float u_ = 0.0f, float v_ = 0.0f);
    void resize(int w, int h);
    void setBorderColor(float y_, float u_, float v_);
    inline float& y(long xc, long yc)
    {
      return bufY[yc][xc];
    }
    inline float& u(long xc, long yc)
    {
      return bufU[yc][xc];
    }
    inline float& v(long xc, long yc)
    {
      return bufV[yc][xc];
    }
    inline const float& y(long xc, long yc) const
    {
      return bufY[yc][xc];
    }
    inline const float& u(long xc, long yc) const
    {
      return bufU[yc][xc];
    }
    inline const float& v(long xc, long yc) const
    {
      return bufV[yc][xc];
    }
    inline int getWidth() const
    {
      return width;
    }
    inline int getHeight() const
    {
      return height;
    }
    void getPixel(int xc, int yc, float& y_, float& u_, float& v_) const;
    void setPixel(int xc, int yc, float y_, float u_, float v_);
  };

  class IndexedImage {
   private:
    unsigned char **buf;
    int           width;
    int           height;
    unsigned char borderColor;
    // --------
    static void allocateBuffers(unsigned char**& buf_, int w, int h);
    static void freeBuffers(unsigned char**& buf_, int w, int h);
   public:
    IndexedImage(int w, int h);
    IndexedImage(const IndexedImage& r);
    virtual ~IndexedImage();
    IndexedImage& operator=(const IndexedImage& r);
    void clear(int c = 0);
    void resize(int w, int h);
    void setBorderColor(int c);
    inline unsigned char * operator[](long yc)
    {
      return buf[yc];
    }
    inline const unsigned char * operator[](long yc) const
    {
      return buf[yc];
    }
    inline int getWidth() const
    {
      return width;
    }
    inline int getHeight() const
    {
      return height;
    }
    unsigned char getPixel(int xc, int yc) const;
    void setPixel(int xc, int yc, int c);
  };

  class ImageConverter {
   protected:
    void    (*progressMessageCallback)(void *userData, const char *msg);
    void    *progressMessageUserData;
    bool    (*progressPercentageCallback)(void *userData, int n);
    void    *progressPercentageUserData;
    int     prvProgressPercentage;
   public:
    ImageConverter();
    virtual ~ImageConverter();
    // the return value is false if the processing has been stopped
    virtual bool processImage(ImageData& imgData, const char *infileName,
                              YUVImageConverter& imgConv,
                              const ImageConvConfig& config);
    virtual void setProgressMessageCallback(void (*func)(void *userData,
                                                         const char *msg),
                                            void *userData_);
    virtual void setProgressPercentageCallback(bool (*func)(void *userData,
                                                            int n),
                                               void *userData_);
   protected:
    virtual void progressMessage(const char *msg);
    virtual bool setProgressPercentage(int n);
  };

  static inline double calculateError(double a, double b)
  {
    return std::fabs(a - b);
  }

  static inline double calculateErrorSqr(double a, double b)
  {
    return ((a - b) * (a - b));
  }

  static inline double calculateYUVErrorSqr(double y0, double u0, double v0,
                                            double y1, double u1, double v1,
                                            double colorErrorScale = 0.5)
  {
    return (calculateErrorSqr(y0, y1)
            + (colorErrorScale * (calculateErrorSqr(u0, u1)
                                  + calculateErrorSqr(v0, v1))));
  }

  template < typename T >
  static inline void limitValue(T& x, T min_, T max_)
  {
    x = (x > min_ ? (x < max_ ? x : max_) : min_);
  }

  template < typename T >
  static inline void rgbToYUV(T& y, T& u, T& v, T r, T g, T b)
  {
    y = (T(0.299) * r) + (T(0.587) * g) + (T(0.114) * b);
    u = (b - y) * T(0.492);
    v = (r - y) * T(0.877);
  }

  template < typename T >
  static inline void yuvToRGB(T& r, T& g, T& b, T y, T u, T v)
  {
    r = (v * T(1.0 / 0.877)) + y;
    b = (u * T(1.0 / 0.492)) + y;
    g = y - ((v * T(0.299 / (0.877 * 0.587)))
             + (u * T(0.114 / (0.492 * 0.587))));
  }

  template < typename T >
  static inline void limitRGBColor(T& r, T& g, T& b)
  {
    r = (r > T(0.0) ? (r < T(1.0) ? r : T(1.0)) : T(0.0));
    g = (g > T(0.0) ? (g < T(1.0) ? g : T(1.0)) : T(0.0));
    b = (b > T(0.0) ? (b < T(1.0) ? b : T(1.0)) : T(0.0));
  }

  template < typename T >
  static inline void limitYUVColor(T& y, T& u, T& v)
  {
    y = (y > T(0.0) ? (y < T(1.0) ? y : T(1.0)) : T(0.0));
    u = (u > T(-0.435912) ? (u < T(0.435912) ? u : T(0.435912)) : T(-0.435912));
    v = (v > T(-0.614777) ? (v < T(0.614777) ? v : T(0.614777)) : T(-0.614777));
  }

  static inline void convertEPColorToRGB(int c, int& r, int& g, int& b)
  {
    r = ((c & 1) << 2) | ((c & 8) >> 2) | ((c & 64) >> 6);
    g = ((c & 2) << 1) | ((c & 16) >> 3) | ((c & 128) >> 7);
    b = ((c & 4) >> 1) | ((c & 32) >> 5);
  }

  static inline void convertRGBToEPColor(int& c, int r, int g, int b)
  {
    c = ((r & 4) >> 2) | ((r & 2) << 2) | ((r & 1) << 6)
        | ((g & 4) >> 1) | ((g & 2) << 3) | ((g & 1) << 7)
        | ((b & 2) << 1) | ((b & 1) << 5);
  }

  void convertEPColorToYUV(int c, float& y, float& u, float& v,
                           double monitorGamma_ = 1.0);

  void convertTVCColorToYUV(int c, float& y, float& u, float& v,
                            double monitorGamma_ = 1.0);

  // make sure that the color is not out of range in the RGB colorspace,
  // but preserve the original hue
  void limitYUVColorToRGB(float& y, float& u, float& v);

  int getRandomNumber(int& seedValue);

  void setRandomSeed(int& seedValue, uint32_t n);

  void ditherLine(IndexedImage& ditheredImage, const YUVImage& inputImage,
                  YUVImage& ditherErrorImage,
                  long yc, int ditherType, double ditherDiffusion,
                  double colorErrorScale,
                  const unsigned char *linePalette, size_t linePaletteSize,
                  const float *epPaletteY, const float *epPaletteU,
                  const float *epPaletteV);

  void ditherLine_ordered(IndexedImage& ditheredImage,
                          const YUVImage& inputImage, long yc, int ditherType,
                          const unsigned char *linePalette,
                          size_t linePaletteSize, const float *epPaletteY);

  void ditherLine_ordered_TVC16(IndexedImage& ditheredImage,
                                const YUVImage& inputImage, long yc,
                                int ditherType);

  ImageData *convertImage(
      const char *inputFileName, const ImageConvConfig& config_,
      void (*progressMessageCallback)(void *userData, const char *msg) =
          (void (*)(void *, const char *)) 0,
      bool (*progressPercentageCallback)(void *userData, int n) =
          (bool (*)(void *, int)) 0,
      void *progressCallbackUserData = (void *) 0);

}       // namespace Ep128ImgConv

#endif  // EPIMGCONV_EPIMGCONV_HPP

