      /****************************************************************************
 * This module is all original code
 * by Rob Nation
 * Copyright 1993, Robert Nation
 *     You may use this code for any purpose, as long as the original
 *     copyright remains in the source code and all documentation
 ****************************************************************************/
/*
  Changed 02/12/97 by Dan Espen:
  - added routines to determine color closeness, for color use reduction.
  Some of the logic comes from pixy2, so the copyright is below.
  */
/*
 * $Id: Picture.c,v 1.2 2011/01/18 19:28:33 espie Exp $
 * Copyright 1996, Romano Giannetti. No guarantees or warantees or anything
 * are provided or implied in any way whatsoever. Use this program at your
 * own risk. Permission to use this program for any purpose is given,
 * as long as the copyright is kept intact.
 *
 * Romano Giannetti - Dipartimento di Ingegneria dell'Informazione
 *                    via Diotisalvi, 2  PISA
 * mailto:romano@iet.unipi.it
 * http://www.iet.unipi.it/~romano
 *
 */

/****************************************************************************
 *
 * Routines to handle initialization, loading, and removing of xpm's or mono-
 * icon images.
 *
 ****************************************************************************/

#include "config.h"

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <X11/keysym.h>
#include <sys/types.h>
#include <sys/time.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>

#ifdef XPM
/* static function prototypes */
static void c100_init_base_table ();    /* prototype */
static void c200_substitute_color(char **,int); /* prototype */
static void c300_color_to_rgb(char *, XColor *); /* prototype */
static double c400_distance(XColor *, XColor *); /* prototype */
#endif

#include "fvwmlib.h"


static FvwmPicture *PictureList=NULL;
Colormap PictureCMap;
Display *PictureSaveDisplay;            /* Save area for display pointer */

/* This routine called during fvwm and some modules initialization */
void InitPictureCMap(Display *dpy,Window Root)
{
  XWindowAttributes root_attr;
  PictureSaveDisplay = dpy;                       /* save for latter */
  XGetWindowAttributes(dpy,Root,&root_attr);
  PictureCMap=root_attr.colormap;
}


FvwmPicture *LoadPicture(Display *dpy,Window Root,char *path, int color_limit)
{
  int l;
  FvwmPicture *p;
#ifdef XPM
  XpmAttributes xpm_attributes;
  int rc;
  XpmImage	my_image = {0};
#endif

  p=(FvwmPicture*)safemalloc(sizeof(FvwmPicture));
  p->count=1;
  p->name=path;
  p->next=NULL;

#ifdef XPM
  /* Try to load it as an X Pixmap first */
  xpm_attributes.colormap=PictureCMap;
  xpm_attributes.closeness=40000; /* Allow for "similar" colors */
  xpm_attributes.valuemask=
    XpmSize | XpmReturnPixels | XpmColormap | XpmCloseness;

  rc =XpmReadFileToXpmImage(path, &my_image, NULL);
  if (rc == XpmSuccess) {
    color_reduce_pixmap(&my_image, color_limit);
    rc = XpmCreatePixmapFromXpmImage(dpy, Root, &my_image,
                                     &p->picture,&p->mask,
                                     &xpm_attributes);
    if (rc == XpmSuccess) {
      p->width = my_image.width;
      p->height = my_image.height;
      XpmFreeXpmImage(&my_image);
      p->depth = DefaultDepthOfScreen(DefaultScreenOfDisplay(dpy));
      return p;
    }
    XpmFreeXpmImage(&my_image);
  }
#endif

  /* If no XPM support, or XPM loading failed, try bitmap */
  if(XReadBitmapFile(dpy,Root,path,&p->width,&p->height,&p->picture,&l,&l)
     == BitmapSuccess)
    {
      p->depth = 0;
      p->mask = None;
      return p;
    }

  free(p);
  return NULL;
}

FvwmPicture *GetPicture(Display *dpy,Window Root,char *IconPath,
		    char *PixmapPath, char *name, int color_limit)
{
  char *path;
  FvwmPicture *p;

  if(!(path=findIconFile(name,PixmapPath,R_OK)))
    if(!(path=findIconFile(name,IconPath,R_OK)))
      return NULL;
  p = LoadPicture(dpy,Root,path, color_limit);
  if (!p)
    free(path);
  return p;
}

FvwmPicture *CachePicture(Display *dpy,Window Root,char *IconPath,char *PixmapPath,
		      char *name, int color_limit)
{
  char *path;
  FvwmPicture *p=PictureList;

  /* First find the full pathname */
#ifdef XPM
  if(!(path=findIconFile(name,PixmapPath,R_OK)))
    if(!(path=findIconFile(name,IconPath,R_OK)))
      return NULL;
#else
  /* Ignore the given pixmap path when compiled without XPM support */
  if(!(path=findIconFile(name,IconPath,R_OK)))
    return NULL;
#endif

  /* See if the picture is already cached */
  while(p)
    {
      register char *p1, *p2;

      for (p1=path, p2=p->name; *p1 && *p2; ++p1, ++p2)
	if (*p1 != *p2)
          break;

      if(!*p1 && !*p2) /* We have found a picture with the wanted name */
	{
	  p->count++; /* Put another weight on the picture */
	  free(path);
	  return p;
	}
      p=p->next;
    }

  /* Not previously cached, have to load it ourself. Put it first in list */
  p=LoadPicture(dpy,Root,path, color_limit);
  if(p)
    {
      p->next=PictureList;
      PictureList=p;
    }
  else
    free(path);
  return p;
}


void DestroyPicture(Display *dpy,FvwmPicture *p)
{
  FvwmPicture *q=PictureList;

  if (!p) /* bag out if NULL */
    return;
  if(--(p->count)>0) /* Remove a weight, still too heavy? */
    return;

  /* Let it fly */
  if(p->name!=NULL)
    free(p->name);
  if(p->picture!=None)
    XFreePixmap(dpy,p->picture);
  if(p->mask!=None)
    XFreePixmap(dpy,p->mask);

  /* Link it out of the list (it might not be there) */
  if(p==q) /* in head? simple */
    PictureList=p->next;
  else
    {
      while(q && q->next!=p) /* fast forward until end or found */
	q=q->next;
      if(q) /* not end? means we found it in there, possibly at end */
	q->next=p->next; /* link around it */
    }
  free(p);
}

/****************************************************************************
 *
 * Find the specified icon file somewhere along the given path.
 *
 * There is a possible race condition here:  We check the file and later
 * do something with it.  By then, the file might not be accessible.
 * Oh well.
 *
 ****************************************************************************/
char *findIconFile(char *icon, char *pathlist, int type)
{
  char *path;
  char *dir_end;
  int l;
  size_t pathlen;

  if (!icon)
    return NULL;

  l = (pathlist) ? strlen(pathlist) : 0;
  pathlen = strlen(icon) + l + 10;
  path = safemalloc(pathlen);
  *path = '\0';
  if (*icon == '/' || pathlist == NULL || *pathlist == '\0')
    {
      /* No search if icon begins with a slash */
      /* No search if pathlist is empty */
	    strlcpy(path, icon, pathlen);
      return path;
    }

  /* Search each element of the pathlist for the icon file */
  while ((pathlist)&&(*pathlist))
    {
      dir_end = strchr(pathlist, ':');
      if (dir_end != NULL)
	{
	  strncpy(path, pathlist, dir_end - pathlist);
	  path[dir_end - pathlist] = 0;
	}
      else
	  strlcpy(path, pathlist, pathlen);

      strlcat(path, "/", pathlen);
      strlcat(path, icon, pathlen);
      if (access(path, type) == 0)
	return path;
      strlcat(path, ".gz", pathlen);
      if (access(path, type) == 0)
	return path;

      /* Point to next element of the path */
      if(dir_end == NULL)
	pathlist = NULL;
      else
	pathlist = dir_end + 1;
    }
  /* Hmm, couldn't find the file.  Return NULL */
  free(path);
  return NULL;
}


#ifdef XPM
/* This structure is used to quickly access the RGB values of the colors */
/* without repeatedly having to transform them.   */
typedef struct {
  char * c_color;	/* Pointer to the name of the color */
  XColor rgb_space;                     /* rgb color info */
} Color_Info;

/* First thing in base array are colors probably already in the color map
   because they have familiar names.
   I pasted them into a xpm and spread them out so that similar colors are
   spread out.
   Toward the end are some colors to fill in the gaps.
   Currently 61 colors in this list.
   */
static Color_Info base_array[] = {
  {"white"},
  {"black"},
  {"grey"},
  {"green"},
  {"blue"},
  {"red"},
  {"cyan"},
  {"yellow"},
  {"magenta"},
  {"DodgerBlue"},
  {"SteelBlue"},
  {"chartreuse"},
  {"wheat"},
  {"turquoise"},
  {"CadetBlue"},
  {"gray87"},
  {"CornflowerBlue"},
  {"YellowGreen"},
  {"NavyBlue"},
  {"MediumBlue"},
  {"plum"},
  {"aquamarine"},
  {"orchid"},
  {"ForestGreen"},
  {"lightyellow"},
  {"brown"},
  {"orange"},
  {"red3"},
  {"HotPink"},
  {"LightBlue"},
  {"gray47"},
  {"pink"},
  {"red4"},
  {"violet"},
  {"purple"},
  {"gray63"},
  {"gray94"},
  {"plum1"},
  {"PeachPuff"},
  {"maroon"},
  {"lavender"},
  {"salmon"},                           /* for peachpuff, orange gap */
  {"blue4"},                            /* for navyblue/mediumblue gap */
  {"PaleGreen4"},                       /* for forestgreen, yellowgreen gap */
  {"#AA7700"},                          /* brick, no close named color */
  {"#11EE88"},                          /* light green, no close named color */
  {"#884466"},                          /* dark brown, no close named color */
  {"#CC8888"},                          /* light brick, no close named color */
  {"#EECC44"},                          /* gold, no close named color */
  {"#AAAA44"},                          /* dull green, no close named color */
  {"#FF1188"},                          /* pinkish red */
  {"#992299"},                          /* purple */
  {"#CCFFAA"},                          /* light green */
  {"#664400"},                          /* dark brown*/
  {"#AADD99"},                          /* light green */
  {"#66CCFF"},                          /* light blue */
  {"#CC2299"},                          /* dark red */
  {"#FF11CC"},                          /* bright pink */
  {"#11CC99"},                          /* grey/green */
  {"#AA77AA"},                          /* purple/red */
  {"#EEBB77"}                           /* orange/yellow */
};

#define NColors (sizeof(base_array) / sizeof(Color_Info))

/* if c_color isn't set, copy it from one of the other colours */
Bool xpmcolor_require_c_color(XpmColor *p)
{
  if (p->c_color != NULL)
    return False;
  else if (p->g_color != NULL)
    p->c_color = strdup(p->g_color);
  else if (p->g4_color != NULL)
    p->c_color = strdup(p->g4_color);
  else if (p->m_color != NULL)
    p->c_color = strdup(p->m_color);
  else
    p->c_color = strdup("none");

  return True;
}

/* given an xpm, change colors to colors close to the
   subset above. */
void
color_reduce_pixmap(XpmImage *image,int color_limit) {
  int i;
  XpmColor *color_table_ptr;
  static char base_init = 'n';

  if (color_limit > 0) {                /* If colors to be limited */
    if (base_init == 'n') {             /* if base table not created yet */
      c100_init_base_table();           /* init the base table */
      base_init = 'y';                  /* remember that its set now. */
    }                                   /* end base table init */
    color_table_ptr = image->colorTable; /* start of xpm color table */
    for(i=0; i<image->ncolors; i++) {   /* all colors in the xpm */
      /* Theres an array for this in the xpm library, but it doesn't
         appear to be part of the API.  Too bad. dje 01/09/00 */
      char **visual_color = 0;
      if (color_table_ptr->c_color) {
        visual_color = &color_table_ptr->c_color;
      } else if (color_table_ptr->g_color) {
        visual_color = &color_table_ptr->g_color;
      } else if (color_table_ptr->g4_color) {
        visual_color = &color_table_ptr->g4_color;
      } else {                          /* its got to be one of these */
        visual_color = &color_table_ptr->m_color;
      }
      c200_substitute_color(visual_color,color_limit);
      color_table_ptr +=1;              /* counter for loop */
    }                                   /* end all colors in xpm */
  }                                     /* end colors limited */
  return;                               /* return, no rc! */
}

/* from the color names in the base table, calc rgbs */
static void
c100_init_base_table () {
  int i;
  for (i=0; i<NColors; i++) {           /* change all base colors to numbers */
    c300_color_to_rgb(base_array[i].c_color, &base_array[i].rgb_space);
  }
}


/* Replace the color in my_color by the closest matching color
   from base_table */
void c200_substitute_color(char **my_color, int color_limit) {
  int i, limit, minind;
  double mindst=1e20;
  double dst;
  XColor rgb;          /* place to calc rgb for each color in xpm */
  size_t len;

  if (!strcasecmp(*my_color,"none")) {
    return ;                        /* do not substitute the "none" color */
  }

  c300_color_to_rgb(*my_color, &rgb);  /* get rgb for a color in xpm */
  /* Loop over all base_array colors; find out which one is closest
     to my_color */
  minind = 0;                           /* Its going to find something... */
  limit = NColors;                      /* init to max */
  if (color_limit < NColors) {          /* can't do more than I have */
    limit = color_limit;                /* Do reduction using subset */
  }                                     /* end reducing limit */
  for(i=0; i < limit; i++) {            /* loop over base array */
    dst = c400_distance (&rgb, &base_array[i].rgb_space); /* distance */
    if (dst < mindst ) {              /* less than min and better than last */
      mindst=dst;                     /* new minimum */
      minind=i;                       /* save loc of new winner */
      if (dst <= 100) {               /* if close enough */
        break;                        /* done */
      }                               /* end close enough */
    }                                 /* end new low distance */
  }                                   /* end all base colors */
  /* Finally: replace the color string by the newly determined color string */
  free(*my_color);                      /* free old color */
  len = strlen(base_array[minind].c_color) + 1;
  *my_color = safemalloc(len); /* area for new color */
  strlcpy(*my_color,base_array[minind].c_color,len); /* put it there */
  return;                             /* all done */
 }

static void c300_color_to_rgb(char *c_color, XColor *rgb_space) {
  int rc;
  rc=XParseColor(PictureSaveDisplay, PictureCMap, c_color, rgb_space);
  if (rc==0) {
    fprintf(stderr,"color_to_rgb: can't parse color %s, rc %d\n", c_color, rc);
    return;
  }
}

/* A macro for squaring things */
#define SQUARE(X) ((X)*(X))
/* RGB Color distance sum of square of differences */
double c400_distance(XColor *target_ptr, XColor *base_ptr) {
  register double dst;
  dst = SQUARE((double)(base_ptr->red   - target_ptr->red  )/655.35)
    +   SQUARE((double)(base_ptr->green - target_ptr->green)/655.35)
    +   SQUARE((double)(base_ptr->blue  - target_ptr->blue )/655.35);
  return dst;
}
#endif /* XPM */
