/* image.hpp -- classes for sampled (indexed, gray and rgb) images
* by [email protected] Wed Feb 27 09:24:47 CET 2002
*/
/* Imp: keep future transparency in toIndexed(...) */

#ifdef __GNUC__
#ifndef __clang__
#pragma interface
#endif
#endif

#ifndef SAMPLED_HPP
#define SAMPLED_HPP 1

#include "config2.h"
#include "gensi.hpp"

class Image {
public:
 class RGB;
 class Gray;
 class Indexed;
 /** Generic, sampled, rectangular image data. Abstract class.
  * Each sample is 1, 2, 4 or 8 bits. Regions:
  * beg..head-1: comment, ignored (e.g unused part of the indexed palette)
  * headp..rowbeg-1: header: not predicted or compressed (e.g the indexed palette)
  * rowbeg+0*rlen..rowbeg+0*rlen+rlen-1: sample data of the 0th row, compressed and predicted
  * rowbeg+1*rlen..rowbeg+1*rlen+rlen-1: sample data of the 1st row
  * rowbeg+2*rlen..rowbeg+2*rlen+rlen-1: sample data of the 1st row
  * ...
  * rowbeg+(h-1)*rlen..rowbeg+h*rlen-1: sample data of the last row
  * trail..beg+len: trailer, ignored. Its length must be >=bpc.
  * TODO: Remove trail, and see what breaks (e.g. setBpc).
  */
 class Sampled: public SimBuffer::Flat {
  public:
   /** Can hold 1 component of a sample of a single pixel. */
   typedef unsigned char sample_t;
   /** Can hold a height or depth of the image */
   typedef unsigned int dimen_t;
   /** Can hold a row length (in byte), which isn't greater than 3*(image width). */
   typedef unsigned int rlen_t;
   /** RGB = (red<<16)+(green<<8)+(blue). red, green and blue are 0..255 */
   #if SIZEOF_LONG>4 && SIZEOF_INT>=4
     typedef unsigned int rgb_t;
   #else
     typedef unsigned long rgb_t;
   #endif
   BEGIN_STATIC_ENUM1(unsigned char)
     TY_INDEXED=1, TY_GRAY=2, TY_RGB=3, TY_OTHER=4, TY_BLACKBOX=5
   END_STATIC_ENUM()
   BEGIN_STATIC_ENUM1(unsigned char) // static const unsigned char
       CS_UNKNOWN=0,           /* error/unspecified */
       CS_GRAYSCALE=1,         /* monochrome */
       CS_RGB=2,               /* red/green/blue */
       CS_YCbCr=3,             /* Y/Cb/Cr (also known as YUV) */
       CS_CMYK=4,              /* C/M/Y/K */
       CS_YCCK=5,              /* Y/Cb/Cr/K */
       CS_Indexed_RGB=12
   END_STATIC_ENUM()
   static const unsigned char cs2cpp[6];
   /** @return NULLP on error */
   static char const *cs2devcs(unsigned char cs);
  protected:
   char *headp;
   char *rowbeg;
   char *trail;
   /** Extra offset */
   slen_t xoffs;

   /** Length of one row, in bytes. Each row must begin on a byte boundary, so
    * extra bits are appended after the rightmost pixel. These bits are
    * arbitrary, and are ignored by the PostScript interpreter.
    */
   rlen_t rlen;
   /** Image height, in pixels. */
   dimen_t ht;
   /** Image width, in pixels. */
   dimen_t wd;
   /** Color space. */
   unsigned char cs;
   /** Components per pixel. (number of planes, image depth). 1 for indexed,
    * 1 for gray, 3 for RGB
    */
   unsigned char cpp;
   /** BitsPerComponent: 1, 2, 4 or 8. PostScript allows 12 too. */
   unsigned char bpc;
   /** Transparent color value. Imp: ... */
   rgb_t transpc;
   /** Image type, TY_... */
   unsigned char ty;
   /** Initializes various fields, allocates memory. Called from descendants'
    * constructors.
    */
   void init(slen_t l_comment, slen_t l_header, dimen_t wd_, dimen_t ht_,
     unsigned char bpc_, unsigned char ty_, unsigned char cpp_);
   /** Convert samples, make bpc=8, multiplication. */
   void to8mul();
   /** Convert samples, make bpc=8, no multiplication. */
   void to8nomul();
   /** Calls copyRGBRow.
    * @return an Image::Indexed version of (this) iff the number of
    *         colors<=256. Otherwise, returns NULLP.
    */
   Indexed* toIndexed0()/* const*/;
   /** No averaging is done, only the red component is extracted */
   Gray* toGray0(unsigned char bpc_);
   RGB * toRGB0(unsigned char bpc_);
   /** @return if any pixels are not gray: false. otherwise: true or false. */
  public:
   inline bool hasTransp() const { return transpc!=0x1000000UL; }
   virtual bool canGray() const =0;
   /** @return an RGB BitsPerComponent number (1,2,4 or 8) to which the image
    * could be converted without any loss. The default implementation calls
    * copyRGBRow().
    */
   virtual unsigned char minRGBBpc() const;
   inline virtual ~Sampled() { delete [] const_cast<char*>(beg); }
   /** Copies whichrow as wd*3 bytes (R0,G0,B0,R1,G1,B1...) to `to' */
   virtual void copyRGBRow(char *to, dimen_t whichrow) const =0;
   virtual bool hasPixelRGB(Image::Sampled::rgb_t rgb) const;
   inline char *getRowbeg() const { return rowbeg; }
   inline dimen_t getWd() const { return wd; }
   inline dimen_t getHt() const { return ht; }
   inline unsigned char getTy() const { return ty; }
   inline unsigned char getBpc() const { return bpc; }
   inline unsigned char getCpp() const { return cpp; }
   inline unsigned char getCs() const { return cs; }
   inline slen_t getXoffs() const { return xoffs; }
   inline rlen_t getRlen() const { return rlen; }
   inline rgb_t getTranspc() const { return transpc; }
   inline char *getHeadp() const { return headp; }
   /** Convert samples, make bpc=8. */
   virtual void to8() =0;
   /** @return NULLP if too many colors for indexed; otherwise a new Image::Indexed.
    * The caller should `delete' (this) if toIndexed()==NULLP.
    */
   virtual /*Image::*/Indexed* toIndexed() =0;
   virtual /*Image::*/RGB*  toRGB(unsigned char bpc_) =0;
   virtual /*Image::*/Gray* toGray(unsigned char bpc_) =0;
   // virtual void setBpc(unsigned char bpc_) =0;
   friend GenBuffer::Writable& operator<<(GenBuffer::Writable&, /*Image::*/Sampled const&);
   /** @return address of static buffer: "#RRGGBB" */
   static char *rgb2webhash(rgb_t);
   /** @return (this) or an image containing (this) composed with alpha
    * channel `al'
    */
   virtual Sampled* addAlpha(/*Image::*/Gray *al) =0;
   /** assert(al.bpp=8) etc. Imp: document this */
   static Indexed* addAlpha0(Indexed *iimg, Gray *al);
 };

 class Indexed: public Sampled {
  public:
   /** @param ncols_ must be >= the colors used */
   Indexed(dimen_t wd_, dimen_t ht_, unsigned short ncols_, unsigned char bpc_);
   /** This includes the transparent color as well. */
   inline unsigned short getNcols() const { return (rowbeg-headp)/3; }
   /** Destroys the color table, and creates one with ncols_ colors.
    * @param ncols_ must be <= the ncols_ specified in the constructor
    */
   void setNcols(unsigned short ncols_);
   /** Decreases the size of the palette (forgets last colors) to the
    * specified amount.
    */
   void setNcolsMove(unsigned short ncols_);
   void setPal(unsigned char coloridx, rgb_t rgb);
   rgb_t getPal(unsigned char coloridx) const;
   /** @param coloridx must be >=0, transp must be -1 */
   void setTransp(unsigned char coloridx);
   /** @return new hasTransp */
   bool setTranspc(rgb_t color);
   /** Returns like setTranspc, but doesn't change the image.
    * @return new hasTransp
    */
   bool wouldSetTranspc(rgb_t color) const;
   /** Like setTranspc, but if it makes any changes, then it calls
    * packPal() and changes back bpc to its old value.
    */
   void setTranspcAndRepack(rgb_t color);
   virtual void copyRGBRow(char *to, dimen_t whichrow) const;
   /* virtual bool hasPixelRGB(Image::Sampled::rgb_t rgb) const; */
   /** Packs (compresses) the palette so that it will be continuous in
    * 0..ncols-1, and each color will be used exactly once. The
    * transparent color (if present) will become black. As a side-effect,
    * packPal() may set (this)->bpc=8.
    */
   void packPal();
   virtual void to8();
   virtual /*Image::*/Indexed* toIndexed();
   virtual /*Image::*/RGB*  toRGB(unsigned char bpc_);
   virtual /*Image::*/Gray* toGray(unsigned char bpc_);
   virtual bool canGray() const;
   inline signed short getTransp() const { return transp; }
   inline signed short getClearTransp() { signed short ret=transp; transp=-1; return ret; }
   /** if (transp>0) transp=0;, converts image data. Does not change bpc. */
   void makeTranspZero();
   virtual unsigned char minRGBBpc() const;
   /** Separates the current image into Indexed1 images. The caller is
    * recommended to call packPal() first to reduce the number of required
    * images.
    * As a side-effect,
    * separate() may set (this)->bpc=8.
    * @return the array of images after full color separation: that is
    *   a dynamically allocated array of `getNcols()-(getTransp()!=-1)'
    *   Indexed images: each image is Indexed1, color 0 is opaque (with the
    *   color obtained from (this)), color 1 is transparent. The returned
    *   array is NULLP-terminated.
    */
   Indexed **separate();
   /** Also calls packPal(). As a side effect, changes all transparent
    * pixels to color index 0.
    * @return NULLP if no transparent pixels.
    */
   Indexed *calcAlpha();
   /** Deletes all elements of p, but not p itself.
    * @param p a NULLP-terminated list of (Indexed*)s.
    */
   static void delete_separated(Indexed **p);
   /** Reorganizes the image so it will have the specified bpc. Issues a
    * runtime error if the specified bpc cannot be achieved.
    * @param bpc_ the desired bpc, or 0: the best achievable.
    */
   virtual void setBpc(unsigned char bpc_);
   void dumpDebug(GenBuffer::Writable& gw);
  protected:
   /* Index of the transparent color, or -1. */
   signed short transp;
   virtual /*Image::*/Sampled* addAlpha(/*Image::*/Gray *al);
   /** Sorts the palette colors in lexicographic, stable order.
    * Called from packPal() to get a consistent palette.
    */
   void sortPal();
 };
 class Gray: public Sampled {
  public:
   Gray(dimen_t wd_, dimen_t ht_, unsigned char bpc_);
   virtual void copyRGBRow(char *to, dimen_t whichrow) const;
   virtual bool hasPixelRGB(Image::Sampled::rgb_t rgb) const;
   virtual void to8();
   virtual /*Image::*/Indexed* toIndexed();
   virtual bool canGray() const;
   // virtual void setBpc(unsigned char bpc_);
   virtual /*Image::*/RGB    * toRGB(unsigned char bpc_);
   virtual /*Image::*/Gray   * toGray(unsigned char bpc_);
   virtual /*Image::*/Sampled* addAlpha(/*Image::*/Gray *al);
   /** Calls to8(). */
   void calcExtrema(unsigned char &lightest, unsigned char &darkest);
 };
 class RGB: public Sampled {
  public:
   RGB(dimen_t wd_, dimen_t ht_, unsigned char bpc_);
   virtual void copyRGBRow(char *to, dimen_t whichrow) const;
   /* virtual bool hasPixelRGB(Image::Sampled::rgb_t rgb) const; */
   virtual void to8();
   virtual /*Image::*/Indexed* toIndexed();
   virtual bool canGray() const;
   // virtual void setBpc(unsigned char bpc_);
   virtual /*Image::*/RGB    * toRGB(unsigned char bpc_);
   virtual /*Image::*/Gray   * toGray(unsigned char bpc_);
   virtual /*Image::*/Sampled* addAlpha(/*Image::*/Gray *al);
 };

 /** Avoid including <stdio.h> */
 typedef void *filep_t;

 /** Describes a driver that can load a specific image file format. */
 struct Loader {
   /** Filter::UngetFILED */
   class UFD;
   /** A function that can (allocate and) load a sampled image. Never
    * returns NULL. On error, it calls Error::.... The filep_t argument
    * should be really cast back to FILE*. The reader must fclose the FILE*.
    */
   // typedef Sampled*(*reader_t)(filep_t, SimBuffer::Flat const& loadHints);
   typedef Sampled*(*reader_t)(UFD* ufd, SimBuffer::Flat const& loadHints);
   BEGIN_STATIC_ENUM1(unsigned) MAGIC_LEN=64 END_STATIC_ENUM()
   /** A function that checks the magic numbers at the beginning of a file
    * (already read into buf), and returns NULL if it cannot load an image
    * of that type, or a reader_t that will load the image. If (and only if!)
    * file is shorter than 64 bytes, the buf is padded with '\000' bytes.
    * @param f may read from freely if necessary (MAGIC_LEN is short), but
    *   has to call rewind(f) before reading
    */
   typedef reader_t(*checker_t)(char buf[MAGIC_LEN], char bufend[MAGIC_LEN], SimBuffer::Flat const& loadHints, UFD* ufd);
   /** A null-terminated, compact string describing (not defining!) the image
    * file format.
    * Examples: "GIF", "XPM", "PNM"
    */
   char const*format;
   checker_t checker;
   /** Null or next loader. */
   Loader *next;
 };

 /** Registers a new type of image Loader, i.e a new image file format. The
  * new image format will be put in front of all others, and will be checked
  * first
  */
 static void register0(Loader *);
 /** Loads the image contained in te file `filename'.
  * @param format NULLP is unknown (load any format)
  *   or an Image::Loader::format already registered
  */
 static Sampled* load(Loader::UFD* ufd, SimBuffer::Flat const& loadHints, char const* format);
 static Sampled* load(char const *filename, SimBuffer::Flat const& loadHints, filep_t stdin_f=(filep_t*)NULLP, char const* format=(char const*)NULLP);
 /* Prints the list of available Loaders (->format), separated by spaces.
  * Returns the number of available Loaders. Prepends a space if >= loaders.
  */
 static unsigned printLoaders(GenBuffer::Writable &);

 /** SampleFormat constants */
 BEGIN_STATIC_ENUM(unsigned, sf_t)
   SF_None=0, /* no specific sample format */
   SF_Opaque=1,
   SF_Transparent=2,
   SF_Gray1=3,
   SF_Indexed1=4,
   SF_Mask=5,
   SF_Transparent2=6,
   SF_Gray2=7,
   SF_Indexed2=8,
   SF_Transparent4=9,
   SF_Rgb1=10,
   SF_Gray4=11,
   SF_Indexed4=12,
   SF_Transparent8=13,
   SF_Rgb2=14,
   SF_Gray8=15,
   SF_Indexed8=16,
   SF_Rgb4=17,
   SF_Rgb8=18,
   SF_Asis=19,
   SF_Bbox=20,
   SF_max=31
 END_STATIC_ENUM()

 /** Contains (and memory-manages) an image, and optimization information
  * as a cache.
  *
  * A SampledInfo contains an image in a canonical format. That is, if two
  * images have the same RGB8 (identical width, height and pixels) or
  * blackbox (identical bytes) representation, and SampledInfo{} are
  * created for both of them, it is guaranteed that the two SampledInfo{}s
  * contain the same image data (width, height, canGray, minRGBBpc,
  * SampleFormat (except for bpc), pixel data, palette (same size, colors
  * and color order)).
  */
 class SampledInfo {
  public:
   /** This constructor call takes ownership of the `img_' pointer: it either
    * reuses the original image (and will delete it in ~SampledInfo), or it
    * immediately deletes the image, and uses another image.
    */
   SampledInfo(Sampled *img_);
   ~SampledInfo();
   inline Sampled* getImage() const { return img; }
   /**
    * Source image, may be modified even if TryOnly==false. If
    * modified, the original will be freed.
    * @param sf desired sample format, see Image::SF_* constants
    * @param WarningOK if false: make the conversion fail if it would produces
    *        a Warning
    * @param TryOnly if true: don't do the real conversion (but may do auxilary,
    *        idempontent, helper conversion), assume it has succeeded
    * @param Transparent: as part of the conversion, try to make this RGB color
    *        transparent
    * @return true iff the conversion succeeded. Note that img may be the same
    *         pointer even when true is returned. If false is returned, keeps
    *         the image unchanged.
    */
   bool setSampleFormat(sf_t sf, bool WarningOK, bool TryOnly, Sampled::rgb_t Transparent);
   inline Indexed **getImgs() const { return imgs; }
   inline Sampled *getImg() const { return img; }
   inline unsigned getNncols() const { return nncols; }
   void separate();
   inline bool canGrayy() const { return canGray; }
   inline unsigned char minRGBBpcc() const { return minRGBBpc; }
   inline bool hasTranspp() const { return hasTransp; }
   inline unsigned char minGrayBpcc() const { return canGray ? minRGBBpc : 0; }
   inline void clearTransp() { hasTransp=false; }
  protected:
   bool hasTransp;
   /** Number of non-transparent colors, or 257 if >=257. */
   unsigned nncols;
   /** Has only gray colors. */
   bool canGray;
   unsigned char minRGBBpc;
   Sampled *img;
   /** The array of images after full color separation. May be NULLP (default),
    * or a dynamically allocated array of `nncols' Indexed images: each
    * image is Indexed1, color 0 is opaque (with any value), color 1
    * is transparent.
    */
   Indexed **imgs;
   sf_t sf;
 };
};

/** Dumps this Image as a rawbits PPM file (plus a comment indicating transparency)
* @return the Writable.
*/
GenBuffer::Writable& operator<<(GenBuffer::Writable&, Image::Sampled const&);

#endif