//<copyright>
// 
// Copyright (c) 1994
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
// 
//</copyright>

//<file>
//
// Name:        xpmraster.C
//
// Purpose:     conversion XPM to Raster
//
// Created:     28 Jun 94   Michael Pichler
//
// Changed:     17 Jan 95   Michael Pichler
//
//
//</file>


// Only XPMs with RGB colours are supported (no colour names from rgb.txt!)
// This code to be seen as Version 0.9. It handles XPMs correctly I had by hand,
// but it does not claim to work on all XPMs yet.
// Furthermore, invalid XPMs may cause troubles (seg'faults etc.)

#include "xpmraster.h"
#include "ocolor.h"  /* hextoi */

#include <InterViews/raster.h>

#include <stdio.h>
#include <iostream.h>
#include <string.h>


/*** XPMColorEntry ***/

struct XPMColorEntry
{
  XPMColorEntry ()  // cannot have constructor arguments for an array
  { line_ = 0;
    R = G = B = 0xDA;  // default grey if transparent cannot be shown
    A = 255;
  }

  void setData (const char*, int);  // set data ("NN c #RRGGBB")

  static const XPMColorEntry* findEntry (  // find color entry in an array of colour
    const XPMColorEntry* table, int n,     //   array of n color entries
    const char*, int len                   //   color with len significant positions
  );

  unsigned char R, G, B, A;  // color intensities
  const char* line_;  // colour name (first charsperpixel bytes to be used)
};


void XPMColorEntry::setData (const char* line, int cpp)
{
  line_ = line;
  const char* fispos = strchr (line + cpp, '#');  // ignore '#' as part of colour id

#define h2i(c)  OColor::hextoi (c)

  if (fispos)
  {
    if (!fispos [4])  // #RGB
    {
      R = h2i (*++fispos);  R |= R << 4;
      G = h2i (*++fispos);  G |= G << 4;
      B = h2i (*++fispos);  B |= B << 4;
    }
    else if (!fispos [7])  // #RRGGBB
    {
      R = (h2i (fispos [1]) << 4) | h2i (fispos [2]);
      G = (h2i (fispos [3]) << 4) | h2i (fispos [4]);
      B = (h2i (fispos [5]) << 4) | h2i (fispos [6]);
    }
    else if (!fispos [10])  // #RRRGGGBBB
    {
      R = (h2i (fispos [1]) << 4) | h2i (fispos [2]);
      G = (h2i (fispos [4]) << 4) | h2i (fispos [5]);
      B = (h2i (fispos [7]) << 4) | h2i (fispos [8]);
    }
    else if (!fispos [13])  // #RRRRGGGGBBBB
    {
      R = (h2i (fispos [1]) << 4) | h2i (fispos [2]);
      G = (h2i (fispos [5]) << 4) | h2i (fispos [6]);
      B = (h2i (fispos [9]) << 4) | h2i (fispos [10]);
    }
    else
      cerr << "XPMRaster: could not find RGB specification in colour: " << line << endl;
  }
  else if (strstr (line, "None"))
    A = 0;  // transparent (masked out)
  else
    cerr << "XPMRaster: sorry cannot convert symbolic colour name: " << line << endl;

#undef h2i

} // setData


const XPMColorEntry* XPMColorEntry::findEntry (const XPMColorEntry* table, int n, const char* col, int len)
{
  int upper = n-1;
  int lower = 0;
  int med, comp;

  // do binary search in array of color entries
  while (lower <= upper)
  {
    med = (lower + upper) >> 1;

    comp = (len == 1) ? (int) *col - (int) *(table [med].line_)
                      : strncmp (col, table [med].line_, len);
    if (comp < 0)  // go on in lower half
      upper = med-1;
    else if (comp > 0)  // go on in upper half
      lower = med+1;
    else  // colour found!
      return table+med;
  }
  return nil;

} // findEntry


/*** XPMRaster ***/

Raster* XPMRaster::read (const char** line, int dither)
{
  /*
   * first line: "width height numcolors charsperpixel"
   */
  int nf;
  int width = 0, height = 0, numcolors = 0, charsperpixel = 0;
  int transp = 0;

  nf = sscanf (*line, "%d %d %d %d", &width, &height, &numcolors, &charsperpixel);
// cerr << "width: " << width << ", height: " << height << endl;
// cerr << "numcolors: " << numcolors << ", charsperpixel: " << charsperpixel << endl;
  if (nf < 4 || width < 1 || height < 1 || numcolors < 1 || charsperpixel < 1)
  {
    cerr << "XPMRaster::read: error in first data row; <width height numcolors charsperpixel> expected" << endl;
    return nil;
  }
  line++;

  // allocate color table
  XPMColorEntry* colortable = new XPMColorEntry [numcolors];

  /*
   * color table: numcolors color specifications;
   * each color: "X c #RRGGBB", where X are charsperpixel characters
   */

  int row;  // gcc keeps loop-vars local
  for (row = 0;  row < numcolors;  row++)
  {
    XPMColorEntry* thiscolor = colortable + row;
    thiscolor->setData (*line, charsperpixel);
// XPMColorEntry* col = colortable + row;
// cerr << "color " << row << ": " << col->R << ", " << col->G << ", " << col->B << endl;
    if (thiscolor->A < 255)
      transp = 1;
    line++;
  } // color table

// cerr << "---unsorted colours---" << endl;
// for (row = 0;  row < numcolors;  row++)
// { XPMColorEntry& debugentry = colortable [row];
//   cerr << debugentry.line_ << " ... "
//        << (int) debugentry.R << '/' << (int) debugentry.G << '/' << (int) debugentry.B << endl;
// }

  // sort the colour table
  // use insertion sort, because colour tables are (i) small and (ii) in most cases already sorted
  for (row = 1;  row < numcolors;  row++)
  {
    int newpos = row;
    XPMColorEntry thiscolor = colortable [row];
    const char* thiscolid = thiscolor.line_;
    while (newpos && strcmp (thiscolid, colortable [newpos-1].line_) < 0)
    {
      colortable [newpos] = colortable [newpos-1];
      // default assignment operator to move pointers and RGB triples around
      newpos--;
    }
    if (newpos < row)
      colortable [newpos] = thiscolor;
  } // for

// cerr << "---sorted colours---" << endl;
// for (row = 0;  row < numcolors;  row++)
// { XPMColorEntry& debugentry = colortable [row];
//   cerr << debugentry.line_ << " ... "
//        << (int) debugentry.R << '/' << (int) debugentry.G << '/' << (int) debugentry.B << endl;
// }

  // allocate raster
  Raster* raster = new Raster (width, height, dither);
  if (!raster)
  {
    cerr << "XPMRaster::read: unexpected error: could not create Raster (" << width << ", " << height << ")" << endl;
    delete[] colortable;
    return nil;
  }

  if (transp)
    raster->useAlphaTransparency ();  // RGBA

  /*
   * pixel data: height strings with width*charsperpixel characters
   */
  for (row = 0;  row < height;  row++)
  {
    const char* thisrow = *line;
    for (int col = 0;  col < width;  col++)
    {
      const XPMColorEntry* color = XPMColorEntry::findEntry (colortable, numcolors, thisrow, charsperpixel);
      if (color)
      { raster->pokeChr (col, height-1-row, color->R, color->G, color->B, color->A);
// cerr << "raster->pokeChr (" << col << ", " << height-1-row << ", "
// << (int) color->R << ", " << (int) color->G << ", " << (int) color->B << ", " << (int) color->A << ")" << endl;
      }
      else
      { cerr << "XPMRaster::read: warning: invalid pixel '" << *thisrow << "' at (" << row << ", " << col << ")" << endl;
        raster->pokeChr (col, height-1-row, 0, 0, 0);
      }
      thisrow += charsperpixel;
    }
    line++;
  } // pixel rows

  delete[] colortable;  // no longer needed

  return raster;

} // read
