/*
 *  rimage.c:		Interface to wraster lib (conversion of RImage)
 *
 *  Written by:		Ullrich Hafner
 *  
 *  Copyright (C) 1999 Ullrich Hafner <hafner@bigfoot.de>
 *
 *  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, USA.
 */

/*
 *  $Date: 2000/10/16 18:24:42 $
 *  $Author: hafner $
 *  $Revision: 1.12 $
 *  $State: Exp $
 */

#include "config.h"

#if defined(PREVIEWS) && !defined(CONVERT)

#if HAVE_STDLIB_H
#	include <stdlib.h>
#endif /* not HAVE_STDLIB_H */
#if HAVE_STRING_H
#	include <string.h>
#else /* not HAVE_STRING_H */
#	include <strings.h>
#endif /* not HAVE_STRING_H */
#include <stdio.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <assert.h>
#include "proplist_t.h"
#include <wraster.h>

#include "window.h"
#include "rimage.h"
#include "error.h"


/*****************************************************************************

			global and local variables
  
*****************************************************************************/

static RContext *rc = NULL;
extern GtkWidget *main_window;

/*****************************************************************************

				prototypes
  
*****************************************************************************/

#ifdef HAVE_LIBWMFUN
RImage *
bilinear (int argc, char **argv, int width, int height, int relief);
RImage *
fade (int argc, char **argv, int width, int height, int relief);
RImage *
waves (int argc, char **argv, int width, int height, int relief);
void
initWindowMaker (Display *d, Colormap c);
#endif /* HAVE_LIBWMFUN */
static RColor *
get_background_color (void);
static RImage *
rimage_load (const char *filename, int width, int height);
static GtkWidget *
rimage_2_preview (RImage *image, GtkWidget *preview, bool_t bevel);

/*****************************************************************************

				public code
  
*****************************************************************************/

void
init_wraster_lib (void)
{
   if (!rc)
   {
      RContextAttributes rattr;
	 
      rattr.flags = 0;
      rc = RCreateContext (GDK_DISPLAY (), DefaultScreen (GDK_DISPLAY ()),
			   &rattr);
#ifdef HAVE_LIBWMFUN
      initWindowMaker (GDK_DISPLAY (), rc->cmap);
#endif /* HAVE_LIBWMFUN */
   }
}

/* We need this wrapper function since gdk_parse_color() no longer
 * understands "rgb:RR/GG/BB".
 */
gboolean
make_color(const gchar *spec, GdkColor *color)
{
  gboolean result;
  gchar * newspec = g_strdup (spec);

  if (strncmp(spec, "rgb:", 4) == 0) {
    const char * red_posn = spec + 4, * green_posn, * blue_posn;
    int red_len, green_len, blue_len;
    
    green_posn = strchr(red_posn, '/');
    if (!green_posn) return FALSE;
    red_len = green_posn - red_posn;
    green_posn++;
    
    blue_posn = strchr(green_posn, '/');
    if (!blue_posn) return FALSE;
    green_len = blue_posn - green_posn;
    blue_posn++;
    blue_len = strlen(spec) - (blue_posn - spec);

    if (red_len != green_len || green_len != blue_len ||
	red_len < 1 || red_len > 4)
      return FALSE;
    
    newspec = malloc(3 * red_len + 2);
    strcpy(newspec, "#");
    strncat(newspec, red_posn, red_len);
    strncat(newspec, green_posn, red_len);
    strncat(newspec, blue_posn, red_len);
  }

  result = gdk_color_parse (newspec, color);
  if (newspec != spec)
    free (newspec);
  return result;
}

GtkWidget *
make_image (const char *filename, int width, int height, GtkWidget *preview)
{
   RImage *rimage = rimage_load (filename, width, height);
   
   preview = rimage_2_preview (rimage, preview, FALSE);
   RReleaseImage (rimage);

   return preview;
}

GtkWidget *
make_gradient (proplist_t array, unsigned width, unsigned height,
	       gtype_e type, GtkWidget *preview)
{
   if (!array || !WMIsPLArray (array) || WMGetPropListItemCount (array) < 3)
      return make_image (PKGDATADIR "/black.xpm", width, height, preview);
   else
   {
      unsigned	n 	 = WMGetPropListItemCount (array);
      unsigned  start	 = n == 3 ? 1 : 2;
      RColor	**colors = Calloc (n + 1, sizeof (RColor *));
      unsigned	k;
      
      for (k = start; k < n; k++)
      {
	 GdkColor c;
	 
	 if (!make_color (WMGetFromPLString (WMGetFromPLArray (array, k)), &c))
	    c.red = c.green = c.blue = 65535; /* white */

	 colors [k - start]        = Calloc (1, sizeof (RColor)) ;
	 colors [k - start]->red   = c.red >> 8;
	 colors [k - start]->green = c.green >> 8;
	 colors [k - start]->blue  = c.blue >> 8;
      }
      colors [k - start] = NULL;
      
      {
	 RImage *rimage;
	 
	 if (n == 3)
	    rimage
	       = RRenderGradient (width, height, colors [0], colors [1],
				  type == HGRADIENT ? RHorizontalGradient
				  : (type == VGRADIENT ? RVerticalGradient
				     : RDiagonalGradient));
	 else
	    rimage
	       = RRenderMultiGradient (width, height, colors,
				       type == HGRADIENT ? RHorizontalGradient
				       : (type == VGRADIENT ? RVerticalGradient
					  : RDiagonalGradient));
	 preview = rimage_2_preview (rimage, preview,
				     TRUE && rimage->height < 100);
	 RReleaseImage (rimage);
      }

      for (k = start; k < n; k++)
	 Free (colors [k - start]);
      Free (colors);
      
      return preview;
   }
}

GtkWidget *
make_solid (const char *color, unsigned width, unsigned height,
	    GtkWidget *preview)
{
   if (!preview)
   {
      preview = gtk_preview_new (GTK_PREVIEW_COLOR);
      gtk_preview_size (GTK_PREVIEW (preview), width, height);
   }
   else
   {
      width  = GTK_WIDGET (preview)->requisition.width;
      height = GTK_WIDGET (preview)->requisition.height;
   }
   {
      guchar   *row = Calloc (width * 3, sizeof (guchar));
      unsigned x, y;
      GdkColor c;
      
      if (!make_color (color, &c))
	 c.red = c.green = c.blue = 65535; /* white */
      for (y = 0; y < height; y++)
      {
	 guchar *ptr = row;
	 for (x = 0; x < width; x++)
	 {
	    *ptr++ = c.red >> 8;
	    *ptr++ = c.green >> 8;
	    *ptr++ = c.blue >> 8;
	 }
	 gtk_preview_draw_row (GTK_PREVIEW (preview), row, 0, y, width);
      }
      Free (row);
   }
   gtk_widget_draw (preview, NULL);

   return preview;
}

GtkWidget *
make_textured_gradient (const char *filename, proplist_t array,
			unsigned width, unsigned height,
			gtype_e type, GtkWidget *preview)
{
   if (!filename || WMGetPropListItemCount (array) != 5)
      return make_image (PKGDATADIR "/black.xpm", width, height, preview);
   else
   {
      GdkColor	c1, c2;
      RColor	r1, r2;
      int	opacity = strtol (WMGetFromPLString (WMGetFromPLArray (array, 2)),
				  NULL, 10);
      RImage	*gradient, *image, *tiled;
      
      if (!make_color (WMGetFromPLString (WMGetFromPLArray (array, 3)), &c1))
	 c1.red = c1.green = c1.blue = 65535; /* white */
      if (!make_color (WMGetFromPLString (WMGetFromPLArray (array, 4)), &c2))
	 c2.red = c2.green = c2.blue = 65535; /* white */
      r1.red   = c1.red >> 8;
      r1.green = c1.green >> 8;
      r1.blue  = c1.blue >> 8;

      r2.red   = c2.red >> 8;
      r2.green = c2.green >> 8;
      r2.blue  = c2.blue >> 8;

      if (!preview)
      {
	 preview = gtk_preview_new (GTK_PREVIEW_COLOR);
	 gtk_preview_size (GTK_PREVIEW (preview), width, height);
      }
      else
      {
	 width  = GTK_WIDGET (preview)->requisition.width;
	 height = GTK_WIDGET (preview)->requisition.height;
      }

      gradient = RRenderGradient (width, height, &r1, &r2,
				  type == HGRADIENT ? RHorizontalGradient
				  : (type == VGRADIENT ? RVerticalGradient
				     : RDiagonalGradient));

      image = rimage_load (filename, -1, -1);
      tiled = RMakeTiledImage (image, width, height);
      RReleaseImage (image);
      
      if (tiled)
      {
	 RCombineImagesWithOpaqueness (tiled, gradient, opacity);
	 RReleaseImage (gradient);
	 
	 preview = rimage_2_preview (tiled, preview, FALSE);
	 RReleaseImage (tiled);
      }
      else
      {
	 preview = rimage_2_preview (gradient, preview, FALSE);
	 RReleaseImage (gradient);
      }
      return preview;
   }
}

#ifdef HAVE_INTERWOVEN_GRADIENT

GtkWidget *
make_igradient (proplist_t array, unsigned width, unsigned height,
		GtkWidget *preview)
{
   if (WMGetPropListItemCount (array) != 7)
      return make_image (PKGDATADIR "/black.xpm", width, height, preview);
   else
   {
      GdkColor	from1, to1, from2, to2;
      RColor	r1 [2], r2 [2];
      int	thickness1 = strtol (WMGetFromPLString (WMGetFromPLArray (array,
								     3)),
				     NULL, 10);
      int	thickness2 = strtol (WMGetFromPLString (WMGetFromPLArray (array,
								     6)),
				     NULL, 10);
      RImage	*gradient;
      
      if (!make_color (WMGetFromPLString (WMGetFromPLArray (array, 1)),
			    &from1))
	 from1.red = from1.green = from1.blue = 65535; /* white */
      if (!make_color (WMGetFromPLString (WMGetFromPLArray (array, 2)), &to1))
	 to1.red = to1.green = to1.blue = 65535; /* white */
      if (!make_color (WMGetFromPLString (WMGetFromPLArray (array, 4)),
			    &from2))
	 from2.red = from2.green = from2.blue = 65535; /* white */
      if (!make_color (WMGetFromPLString (WMGetFromPLArray (array, 5)), &to2))
	 to2.red = to2.green = to2.blue = 65535; /* white */
      
      r1 [0].red   = from1.red >> 8;
      r1 [0].green = from1.green >> 8;
      r1 [0].blue  = from1.blue >> 8;

      r1 [1].red   = to1.red >> 8;
      r1 [1].green = to1.green >> 8;
      r1 [1].blue  = to1.blue >> 8;

      r2 [0].red   = from2.red >> 8;
      r2 [0].green = from2.green >> 8;
      r2 [0].blue  = from2.blue >> 8;

      r2 [1].red   = to2.red >> 8;
      r2 [1].green = to2.green >> 8;
      r2 [1].blue  = to2.blue >> 8;

      if (!preview)
      {
	 preview = gtk_preview_new (GTK_PREVIEW_COLOR);
	 gtk_preview_size (GTK_PREVIEW (preview), width, height);
      }
      else
      {
	 width  = GTK_WIDGET (preview)->requisition.width;
	 height = GTK_WIDGET (preview)->requisition.height;
      }

      gradient = RRenderInterwovenGradient (width, height,
					    r1, thickness1,
					    r2, thickness2);

      if (gradient)
      {
	 preview = rimage_2_preview (gradient, preview,
				     gradient->height < 100);
	 RReleaseImage (gradient);
      }
      else
	 preview = make_image (PKGDATADIR "/black.xpm",
			       width, height, preview);

      return preview;
   }
}

#endif /* HAVE_INTERWOVEN_GRADIENT */

#ifdef HAVE_LIBWMFUN
GtkWidget *
make_wmfun (proplist_t array, unsigned width, unsigned height,
	    GtkWidget *preview)
{
   unsigned n 		 = WMGetPropListItemCount (array);
   const char	*library = n > 2
			   ? WMGetFromPLString (WMGetFromPLArray (array, 1)) : NULL;
   const char	*type 	 = n > 2
			   ? WMGetFromPLString (WMGetFromPLArray (array, 0)) : NULL;
   if (!array || !WMIsPLArray (array) || !library || !type ||
       !strcaseeq (type, "function") || !strncaseeq (library, "libwmfun",
						     strlen ("libwmfun")))
      return make_image (PKGDATADIR "/black.xpm", width, height, preview);
   else
   {
      unsigned	 n    	  = WMGetPropListItemCount (array);
      char     **argv 	  = Calloc (n + 2, sizeof (char *));
      char 	*function = WMGetFromPLString (WMGetFromPLArray (array, 2));
      unsigned	 k;
      unsigned	 argc;
      RImage 	*image;

      for (k = 2, argc = 0; k < n; k++)
	 argv [argc++] = WMGetFromPLString (WMGetFromPLArray (array, k));
      argv [argc] = NULL;
      
      if ((strcaseeq (function, "bilinear")))
	 image = bilinear (argc, argv, width, height, 0);
      else if ((strcaseeq (function, "fade")))
	 image = fade (argc, argv, width, height, 0);
      else if ((strcaseeq (function, "waves")))
	 image = waves (argc, argv, width, height, 0);
      else
	 image = NULL;

      if (image)
      {
	 preview = rimage_2_preview (image, preview, image->height < 100);
	 RReleaseImage (image);
      }
      else
	 preview = make_image (PKGDATADIR "/black.xpm",
			       width, height, preview);
      Free (argv);
      return preview;
   }
}
#endif /* HAVE_LIBWMFUN */

/*****************************************************************************

				private code
  
*****************************************************************************/

static RImage *
rimage_load (const char *filename, int width, int height)
{
   char *path = get_pixmap_path (filename);

   if (!path)
      return rimage_load (PKGDATADIR "/black.xpm", width, height);
   else
   {
      RImage *image;

      image = RLoadImage (rc, path, 0);
      if (!image || !image->width || !image->height)
	 return rimage_load (PKGDATADIR "/black.xpm", width, height);
      if (height > 0 || width > 0)	/* enforce scaling */
      {
	 if (height <= 0)
	    height = image->height;
	 if (width <= 0)
	    width = image->width;

	 if (streq (filename, PKGDATADIR "/black.xpm"))
	 {
	    RImage *scaled = RScaleImage (image, width, height);
	    RReleaseImage (image);
	    image = scaled;
	 }
	 else if (image->width > (unsigned) width
		  || image->height > (unsigned) height)
	 {
	    RImage *scaled;
	 
	    if (height * image->width > width * image->height)
	       height = width * image->height / image->width;
	    else
	       width  = height * image->width / image->height;
	    
	    scaled = RScaleImage (image, max (width, 22), max (height, 22));
	    RReleaseImage (image);
	    image = scaled;
	 }
      }
      Free (path);      
      return image;
   }
}

static GtkWidget *
rimage_2_preview (RImage *image, GtkWidget *preview, bool_t bevel)
{
   assert (image && image->width && image->height);

#ifdef HAVE_WRASTER_0_20
   {
      guchar		*row;
      unsigned		x, y;
      unsigned		x0, y0, width, height;
      RColor		*bg = get_background_color ();
      unsigned char	*ptr = image->data;

      if (bevel)
	 RBevelImage (image, RBEV_RAISED2);
      if (!preview)
      {
	 preview = gtk_preview_new (GTK_PREVIEW_COLOR);
	 gtk_preview_size (GTK_PREVIEW (preview), image->width, image->height);
	 width 	 = image->width;
	 height  = image->height;
	 x0 = y0 = 0;
      }
      else
      {
	 width  = GTK_WIDGET (preview)->requisition.width;
	 height = GTK_WIDGET (preview)->requisition.height;

	 assert (image->width <= width && image->height <= height);
	 
	 x0 = (width - image->width) / 2;
	 y0 = (height - image->height) / 2;
      }
      
      row = Calloc (width * 3, sizeof (guchar));
      {
	 guchar *pixel = row;
	 
	 for (x = 0; x < width; x++)
	 {
	    *pixel++ = bg->red;
	    *pixel++ = bg->green;
	    *pixel++ = bg->blue;
	 }
      }
      for (y = 0; y < height; y++)
	 gtk_preview_draw_row (GTK_PREVIEW (preview), row, 0, y, width);

      for (y = 0; y < image->height; y++)
      {
	 guchar *pixel = row;
	 
	 for (x = 0; x < image->width; x++)
	    if (image->format == RRGBAFormat)
	    {
	       unsigned int r = *ptr++;
	       unsigned int g = *ptr++;
	       unsigned int b = *ptr++;
	       unsigned int a = *ptr++;
	       unsigned int na = 255 - a;
	       
	       *pixel++ = (bg->red * na + r * a) / 256;
	       *pixel++ = (bg->green * na + g * a) / 256;
	       *pixel++ = (bg->blue * na + b * a) / 256;
	    }
	    else
	    {
	       *pixel++ = *ptr++;
	       *pixel++ = *ptr++;
	       *pixel++ = *ptr++;
	    }
	 
	 gtk_preview_draw_row (GTK_PREVIEW (preview), row, x0, y0 + y,
			       image->width);
      }
      Free (row);
      gtk_widget_draw (preview, NULL);
      return preview;
   }
#else  /* old wraster of wmaker < 0.62.0 */
   {
      guchar		*row;
      unsigned		x, y;
      unsigned char	*r, *g, *b, *a;
      unsigned		x0, y0, width, height;
      RColor		*bg = get_background_color ();
      
      r = image->data [0];
      g = image->data [1];
      b = image->data [2];
      a = image->data [3];

      if (bevel)
	 RBevelImage (image, RBEV_RAISED2);
      if (!preview)
      {
	 preview = gtk_preview_new (GTK_PREVIEW_COLOR);
	 gtk_preview_size (GTK_PREVIEW (preview), image->width, image->height);
	 width 	 = image->width;
	 height  = image->height;
	 x0 = y0 = 0;
      }
      else
      {
	 width  = GTK_WIDGET (preview)->requisition.width;
	 height = GTK_WIDGET (preview)->requisition.height;

	 assert (image->width <= width && image->height <= height);
	 
	 x0 = (width - image->width) / 2;
	 y0 = (height - image->height) / 2;
      }
      
      row = Calloc (width * 3, sizeof (guchar));
      {
	 guchar *pixel = row;
	 
	 for (x = 0; x < width; x++)
	 {
	    *pixel++ = bg->red;
	    *pixel++ = bg->green;
	    *pixel++ = bg->blue;
	 }
      }
      for (y = 0; y < height; y++)
	 gtk_preview_draw_row (GTK_PREVIEW (preview), row, 0, y, width);

      for (y = 0; y < image->height; y++)
      {
	 guchar *pixel = row;
	 
	 for (x = 0; x < image->width; x++)
	    if (!a || *a++)
	    {
	       *pixel++ = *r++;
	       *pixel++ = *g++;
	       *pixel++ = *b++;
	    }
	    else
	    {
	       *pixel++ = bg->red;
	       *pixel++ = bg->green;
	       *pixel++ = bg->blue;
	       r++;
	       g++;
	       b++;
	    }
	 
	 gtk_preview_draw_row (GTK_PREVIEW (preview), row, x0, y0 + y,
			       image->width);
      }
      Free (row);
      gtk_widget_draw (preview, NULL);
      return preview;
   }
#endif    
}

static RColor *
get_background_color (void)
{
   static RColor *background = NULL;

   if (!background)
   {
      background = Calloc (1, sizeof (RColor));

      if (main_window && main_window->style)
      {
	 background->red
	    = main_window->style->bg [GTK_STATE_NORMAL].red >> 8;
	 background->green
	    = main_window->style->bg [GTK_STATE_NORMAL].green >> 8;
	 background->blue
	    = main_window->style->bg [GTK_STATE_NORMAL].blue >> 8;
      }
      else
      {
	 background->red   = 0xa8;
	 background->green = 0xa8;
	 background->blue  = 0xa8;
      }
   }
   return background;
}

#endif /* defined(PREVIEWS) && !defined(CONVERT) */
