/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <math.h>
#include <string.h>
#include <sys/types.h>
#include <sys/param.h>
#include <netinet/in.h>
#include "gdk/gdkx.h"
#include "gtkpreview.h"
#include "gtksignal.h"


#define IMAGE_SIZE            256
#define PREVIEW_CLASS(w)      GTK_PREVIEW_CLASS (GTK_OBJECT (w)->klass)
#define COLOR_COMPOSE(r,g,b)  (lookup_red[r] | lookup_green[g] | lookup_blue[b])


typedef struct _GtkPreviewProp  GtkPreviewProp;
typedef void (*GtkTransferFunc) (guchar *dest, guchar *src, gint count);

struct _GtkPreviewProp
{
  guint16 ref_count;
  guint16 nred_shades;
  guint16 ngreen_shades;
  guint16 nblue_shades;
  guint16 ngray_shades;
};


static void   gtk_preview_class_init    (GtkPreviewClass  *klass);
static void   gtk_preview_init          (GtkPreview       *preview);
static void   gtk_preview_destroy       (GtkObject        *object);
static void   gtk_preview_realize       (GtkWidget        *widget);
static void   gtk_preview_unrealize     (GtkWidget        *widget);
static gint   gtk_preview_expose        (GtkWidget        *widget,
				         GdkEventExpose   *event);
static void   gtk_preview_make_buffer   (GtkPreview       *preview);
static void   gtk_preview_get_visuals   (GtkPreviewClass  *klass);
static void   gtk_preview_get_cmaps     (GtkPreviewClass  *klass);
static void   gtk_preview_dither_init   (GtkPreviewClass  *klass);
static void   gtk_fill_lookup_array     (gulong           *array,
					 int               depth,
					 int               shift,
					 int               prec);
static void   gtk_trim_cmap             (GtkPreviewClass  *klass);
static void   gtk_create_8_bit          (GtkPreviewClass  *klass);

static void   gtk_color_8               (guchar           *src,
				         guchar           *data,
				         gint              x,
				         gint              y,
				         gulong            width);
static void   gtk_color_16              (guchar           *src,
				         guchar           *data,
				         gulong            width);
static void   gtk_color_24              (guchar           *src,
				         guchar           *data,
				         gulong            width);
static void   gtk_grayscale_8           (guchar           *src,
				         guchar           *data,
				         gint              x,
				         gint              y,
				         gulong            width);
static void   gtk_grayscale_16          (guchar           *src,
				         guchar           *data,
				         gulong            width);
static void   gtk_grayscale_24          (guchar           *src,
				         guchar           *data,
				         gulong            width);

static gint   gtk_get_preview_prop      (guint            *nred,
					 guint            *nblue,
					 guint            *ngreen,
					 guint            *ngray);
static void   gtk_set_preview_prop      (guint             nred,
					 guint             ngreen,
					 guint             nblue,
					 guint             ngray);

/* transfer functions:
 *  destination byte order/source bpp/destination bpp
 */
static void   gtk_lsbmsb_1_1            (guchar           *dest,
					 guchar           *src,
					 gint              count);
static void   gtk_lsb_2_2               (guchar           *dest,
					 guchar           *src,
					 gint              count);
static void   gtk_msb_2_2               (guchar           *dest,
					 guchar           *src,
					 gint              count);
static void   gtk_lsb_3_3               (guchar           *dest,
					 guchar           *src,
					 gint              count);
static void   gtk_msb_3_3               (guchar           *dest,
					 guchar           *src,
					 gint              count);
static void   gtk_lsb_3_4               (guchar           *dest,
					 guchar           *src,
					 gint              count);
static void   gtk_msb_3_4               (guchar           *dest,
					 guchar           *src,
					 gint              count);


static GtkWidgetClass *parent_class = NULL;
static GtkPreviewClass *preview_class = NULL;
static GtkPreviewInfo *preview_info = NULL;
static gint install_cmap = FALSE;


guint
gtk_preview_get_type ()
{
  static guint preview_type = 0;

  if (!preview_type)
    {
      GtkTypeInfo preview_info =
      {
        "GtkPreview",
        sizeof (GtkPreview),
        sizeof (GtkPreviewClass),
        (GtkClassInitFunc) gtk_preview_class_init,
        (GtkObjectInitFunc) gtk_preview_init,
	(GtkArgFunc) NULL,
      };

      preview_type = gtk_type_unique (gtk_widget_get_type (), &preview_info);
    }

  return preview_type;
}

static void
gtk_preview_class_init (GtkPreviewClass *klass)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;

  object_class = (GtkObjectClass*) klass;
  widget_class = (GtkWidgetClass*) klass;

  parent_class = gtk_type_class (gtk_widget_get_type ());
  preview_class = klass;

  object_class->destroy = gtk_preview_destroy;

  widget_class->realize = gtk_preview_realize;
  widget_class->unrealize = gtk_preview_unrealize;
  widget_class->expose_event = gtk_preview_expose;

  if (preview_info)
    klass->info = *preview_info;
  else
    {
      klass->info.visual = NULL;
      klass->info.cmap = NULL;

      klass->info.color_pixels = NULL;
      klass->info.gray_pixels = NULL;
      klass->info.reserved_pixels = NULL;

      klass->info.lookup_red = NULL;
      klass->info.lookup_green = NULL;
      klass->info.lookup_blue = NULL;

      klass->info.dither_red = NULL;
      klass->info.dither_green = NULL;
      klass->info.dither_blue = NULL;
      klass->info.dither_gray = NULL;
      klass->info.dither_matrix = NULL;

      klass->info.nred_shades = 6;
      klass->info.ngreen_shades = 6;
      klass->info.nblue_shades = 4;
      klass->info.ngray_shades = 24;
      klass->info.nreserved = 0;

      klass->info.bpp = 0;
      klass->info.cmap_alloced = FALSE;
      klass->info.gamma = 1.0;
    }

  klass->image = NULL;

  gtk_preview_get_visuals (klass);
  gtk_preview_get_cmaps (klass);
  gtk_preview_dither_init (klass);
}

static void
gtk_preview_init (GtkPreview *preview)
{
  GTK_WIDGET_SET_FLAGS (preview, GTK_BASIC);

  preview->buffer = NULL;
  preview->buffer_width = 0;
  preview->buffer_height = 0;
  preview->expand = FALSE;
}

void
gtk_preview_uninit ()
{
  GtkPreviewProp *prop;
  GdkAtom property;

  if (preview_class && !install_cmap &&
      (preview_class->info.visual->type != GDK_VISUAL_TRUE_COLOR) &&
      (preview_class->info.visual->type != GDK_VISUAL_DIRECT_COLOR))
    {
      property = gdk_atom_intern ("GTK_PREVIEW_INFO", FALSE);

      if (gdk_property_get (NULL, property, property,
			    0, sizeof (GtkPreviewProp), FALSE,
			    NULL, NULL, (guchar**) &prop))
	{
	  prop->ref_count = ntohs (prop->ref_count) - 1;
	  if (prop->ref_count == 0)
	    {
	      gdk_property_delete (NULL, property);
	    }
	  else
	    {
	      prop->ref_count = htons (prop->ref_count);
	      gdk_property_change (NULL, property, property, 16,
				   GDK_PROP_MODE_REPLACE,
				   (guchar*) prop, 5);
	    }
	}
    }
}

GtkWidget*
gtk_preview_new (GtkPreviewType type)
{
  GtkPreview *preview;

  preview = gtk_type_new (gtk_preview_get_type ());
  preview->type = type;

  return GTK_WIDGET (preview);
}

void
gtk_preview_size (GtkPreview *preview,
		  gint        width,
		  gint        height)
{
  g_return_if_fail (preview != NULL);
  g_return_if_fail (GTK_IS_PREVIEW (preview));

  if ((width != GTK_WIDGET (preview)->requisition.width) ||
      (height != GTK_WIDGET (preview)->requisition.height))
    {
      GTK_WIDGET (preview)->requisition.width = width;
      GTK_WIDGET (preview)->requisition.height = height;

      if (preview->buffer)
	g_free (preview->buffer);
      preview->buffer = NULL;
    }
}

void
gtk_preview_put (GtkPreview   *preview,
		 GdkWindow    *window,
		 GdkGC        *gc,
		 gint          srcx,
		 gint          srcy,
		 gint          destx,
		 gint          desty,
		 gint          width,
		 gint          height)
{
  GtkWidget *widget;
  GdkImage *image;
  GdkRectangle r1, r2, r3;
  GtkTransferFunc transfer_func;
  guchar *image_mem;
  guchar *src, *dest;
  gint x, xe, x2;
  gint y, ye, y2;
  guint dest_rowstride;
  guint src_bpp;
  guint dest_bpp;
  gint i;

  g_return_if_fail (preview != NULL);
  g_return_if_fail (GTK_IS_PREVIEW (preview));
  g_return_if_fail (window != NULL);

  if (!preview->buffer)
    return;

  widget = GTK_WIDGET (preview);

  r1.x = srcx;
  r1.y = srcy;
  r1.width = preview->buffer_width;
  r1.height = preview->buffer_height;

  r2.x = destx;
  r2.y = desty;
  r2.width = width;
  r2.height = height;

  if (!gdk_rectangle_intersect (&r1, &r2, &r3))
    return;

  x2 = r3.x + r3.width;
  y2 = r3.y + r3.height;

  if (!preview_class->image)
    preview_class->image = gdk_image_new (GDK_IMAGE_FASTEST,
					  preview_class->info.visual,
					  IMAGE_SIZE, IMAGE_SIZE);
  image = preview_class->image;
  src_bpp = preview_class->info.bpp;

  image_mem = image->mem;
  dest_bpp = image->bpp;
  dest_rowstride = image->bpl;

  transfer_func = NULL;

  switch (dest_bpp)
    {
    case 1:
      switch (src_bpp)
	{
	case 1:
	  transfer_func = gtk_lsbmsb_1_1;
	  break;
	}
      break;
    case 2:
      switch (src_bpp)
	{
	case 2:
	  if (image->byte_order == GDK_MSB_FIRST)
	    transfer_func = gtk_msb_2_2;
	  else
	    transfer_func = gtk_lsb_2_2;
	  break;
	case 3:
	  break;
	}
      break;
    case 3:
      switch (src_bpp)
	{
	case 3:
	  if (image->byte_order == GDK_MSB_FIRST)
	    transfer_func = gtk_msb_3_3;
	  else
	    transfer_func = gtk_lsb_3_3;
	  break;
	}
      break;
    case 4:
      switch (src_bpp)
	{
	case 3:
	  if (image->byte_order == GDK_MSB_FIRST)
	    transfer_func = gtk_msb_3_4;
	  else
	    transfer_func = gtk_lsb_3_4;
	  break;
	}
      break;
    }

  if (!transfer_func)
    {
      g_warning ("unsupported byte order/src bpp/dest bpp combination: %s:%d:%d",
		 (image->byte_order == GDK_MSB_FIRST) ? "msb" : "lsb", src_bpp, dest_bpp);
      return;
    }

  for (y = r3.y; y < y2; y += IMAGE_SIZE)
    {
      for (x = r3.x; x < x2; x += IMAGE_SIZE)
	{
	  xe = x + IMAGE_SIZE;
	  if (xe > x2)
	    xe = x2;

	  ye = y + IMAGE_SIZE;
	  if (ye > y2)
	    ye = y2;

	  for (i = y; i < ye; i++)
	    {
	      src = preview->buffer + (((gulong) (i - r1.y) * (gulong) preview->buffer_width) +
				       (x - r1.x)) * (gulong) src_bpp;
	      dest = image_mem + ((gulong) (i - y) * dest_rowstride);

	      if (xe > x)
		(* transfer_func) (dest, src, xe - x);
	    }

	  gdk_draw_image (window, gc,
			  image, 0, 0, x, y,
			  xe - x, ye - y);
	  gdk_flush ();
	}
    }
}

void
gtk_preview_put_row (GtkPreview *preview,
		     guchar     *src,
		     guchar     *dest,
		     gint        x,
		     gint        y,
		     gint        w)
{
  g_return_if_fail (preview != NULL);
  g_return_if_fail (GTK_IS_PREVIEW (preview));
  g_return_if_fail (src != NULL);
  g_return_if_fail (dest != NULL);

  switch (preview->type)
    {
    case GTK_PREVIEW_COLOR:
      switch (preview_class->info.visual->depth)
	{
	case 8:
	  gtk_color_8 (src, dest, x, y, w);
	  break;
	case 15:
	case 16:
	  gtk_color_16 (src, dest, w);
	  break;
	case 24:
	case 32:
	  gtk_color_24 (src, dest, w);
	  break;
	}
      break;
    case GTK_PREVIEW_GRAYSCALE:
      switch (preview_class->info.visual->depth)
	{
	case 8:
	  gtk_grayscale_8 (src, dest, x, y, w);
	  break;
	case 15:
	case 16:
	  gtk_grayscale_16 (src, dest, w);
	  break;
	case 24:
	case 32:
	  gtk_grayscale_24 (src, dest, w);
	  break;
	}
      break;
    }
}

void
gtk_preview_draw_row (GtkPreview *preview,
		      guchar     *data,
		      gint        x,
		      gint        y,
		      gint        w)
{
  guchar *dest;

  g_return_if_fail (preview != NULL);
  g_return_if_fail (GTK_IS_PREVIEW (preview));
  g_return_if_fail (data != NULL);

  if ((w <= 0) || (y < 0))
    return;

  g_return_if_fail (data != NULL);

  gtk_preview_make_buffer (preview);

  if (y >= preview->buffer_height)
    return;

  switch (preview->type)
    {
    case GTK_PREVIEW_COLOR:
      switch (preview_class->info.visual->depth)
	{
	case 8:
	  dest = preview->buffer + ((gulong) y * (gulong) preview->buffer_width + (gulong) x);
	  gtk_color_8 (data, dest, x, y, w);
	  break;
	case 15:
	case 16:
	  dest = preview->buffer + ((gulong) y * (gulong) preview->buffer_width + (gulong) x) * 2;
	  gtk_color_16 (data, dest, w);
	  break;
	case 24:
	case 32:
	  dest = preview->buffer + ((gulong) y * (gulong) preview->buffer_width + (gulong) x) * 3;
	  gtk_color_24 (data, dest, w);
	  break;
	}
      break;
    case GTK_PREVIEW_GRAYSCALE:
      switch (preview_class->info.visual->depth)
	{
	case 8:
	  dest = preview->buffer + ((gulong) y * (gulong) preview->buffer_width + (gulong) x);
	  gtk_grayscale_8 (data, dest, x, y, w);
	  break;
	case 15:
	case 16:
	  dest = preview->buffer + ((gulong) y * (gulong) preview->buffer_width + (gulong) x) * 2;
	  gtk_grayscale_16 (data, dest, w);
	  break;
	case 24:
	case 32:
	  dest = preview->buffer + ((gulong) y * (gulong) preview->buffer_width + (gulong) x) * 3;
	  gtk_grayscale_24 (data, dest, w);
	  break;
	}
      break;
    }
}

void
gtk_preview_set_expand (GtkPreview *preview,
			gint        expand)
{
  g_return_if_fail (preview != NULL);
  g_return_if_fail (GTK_IS_PREVIEW (preview));

  preview->expand = (expand != FALSE);
}

void
gtk_preview_set_gamma (double _gamma)
{
  g_return_if_fail (preview_class == NULL);

  if (!preview_info)
    {
      preview_info = g_new0 (GtkPreviewInfo, 1);
      preview_info->nred_shades = 6;
      preview_info->ngreen_shades = 6;
      preview_info->nblue_shades = 4;
      preview_info->ngray_shades = 24;
    }

  preview_info->gamma = _gamma;
}

void
gtk_preview_set_color_cube (guint nred_shades,
			    guint ngreen_shades,
			    guint nblue_shades,
			    guint ngray_shades)
{
  g_return_if_fail (preview_class == NULL);

  if (!preview_info)
    {
      preview_info = g_new0 (GtkPreviewInfo, 1);
      preview_info->gamma = 1.0;
    }

  preview_info->nred_shades = nred_shades;
  preview_info->ngreen_shades = ngreen_shades;
  preview_info->nblue_shades = nblue_shades;
  preview_info->ngray_shades = ngray_shades;
}

void
gtk_preview_set_install_cmap (gint _install_cmap)
{
  g_return_if_fail (preview_class == NULL);

  install_cmap = _install_cmap;
}

void
gtk_preview_set_reserved (gint nreserved)
{
  if (!preview_info)
    preview_info = g_new0 (GtkPreviewInfo, 1);

  preview_info->nreserved = nreserved;
}

GdkVisual*
gtk_preview_get_visual ()
{
  if (!preview_class)
    preview_class = gtk_type_class (gtk_preview_get_type ());

  return preview_class->info.visual;
}

GdkColormap*
gtk_preview_get_cmap ()
{
  if (!preview_class)
    preview_class = gtk_type_class (gtk_preview_get_type ());

  return preview_class->info.cmap;
}

GtkPreviewInfo*
gtk_preview_get_info ()
{
  if (!preview_class)
    preview_class = gtk_type_class (gtk_preview_get_type ());

  return &preview_class->info;
}


static void
gtk_preview_destroy (GtkObject *object)
{
  GtkPreview *preview;

  g_return_if_fail (object != NULL);
  g_return_if_fail (GTK_IS_PREVIEW (object));

  preview = GTK_PREVIEW (object);
  if (preview->buffer)
    g_free (preview->buffer);
  preview->type = (GtkPreviewType) -1;

  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

static void
gtk_preview_realize (GtkWidget *widget)
{
  GtkPreview *preview;
  GdkWindowAttr attributes;
  gint attributes_mask;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_PREVIEW (widget));

  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
  preview = GTK_PREVIEW (widget);

  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.x = widget->allocation.x;
  attributes.y = widget->allocation.y;
  attributes.width = widget->allocation.width;
  attributes.height = widget->allocation.height;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.colormap = gtk_widget_get_colormap (widget);
  attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

  widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
  gdk_window_set_user_data (widget->window, widget);

  widget->style = gtk_style_attach (widget->style, widget->window);
  gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
}

static void
gtk_preview_unrealize (GtkWidget *widget)
{
  GtkPreview *preview;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_PREVIEW (widget));

  preview = GTK_PREVIEW (widget);

  if (GTK_WIDGET_CLASS (parent_class)->unrealize)
    (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

static gint
gtk_preview_expose (GtkWidget      *widget,
		    GdkEventExpose *event)
{
  GtkPreview *preview;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_PREVIEW (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  if (GTK_WIDGET_DRAWABLE (widget))
    {
      preview = GTK_PREVIEW (widget);

      gtk_preview_put (GTK_PREVIEW (widget),
		       widget->window, widget->style->black_gc,
		       (widget->allocation.width - preview->buffer_width) / 2,
		       (widget->allocation.height - preview->buffer_height) / 2,
		       event->area.x, event->area.y,
		       event->area.width, event->area.height);
    }

  return FALSE;
}

static void
gtk_preview_make_buffer (GtkPreview *preview)
{
  GtkWidget *widget;
  gint width;
  gint height;

  g_return_if_fail (preview != NULL);
  g_return_if_fail (GTK_IS_PREVIEW (preview));

  widget = GTK_WIDGET (preview);

  if (preview->expand &&
      (widget->allocation.width != 0) &&
      (widget->allocation.height != 0))
    {
      width = widget->allocation.width;
      height = widget->allocation.height;
    }
  else
    {
      width = widget->requisition.width;
      height = widget->requisition.height;
    }

  if (!preview->buffer ||
      (preview->buffer_width != width) ||
      (preview->buffer_height != height))
    {
      if (preview->buffer)
	g_free (preview->buffer);

      preview->buffer_width = width;
      preview->buffer_height = height;

      preview->buffer = g_new0 (guchar,
				preview->buffer_width *
				preview->buffer_height *
				preview_class->info.bpp);
    }
}

static void
gtk_preview_get_visuals (GtkPreviewClass *klass)
{
  static GdkVisualType types[] =
  {
    GDK_VISUAL_TRUE_COLOR,
    GDK_VISUAL_DIRECT_COLOR,
    GDK_VISUAL_TRUE_COLOR,
    GDK_VISUAL_DIRECT_COLOR,
    GDK_VISUAL_TRUE_COLOR,
    GDK_VISUAL_DIRECT_COLOR,
    GDK_VISUAL_TRUE_COLOR,
    GDK_VISUAL_DIRECT_COLOR,
    GDK_VISUAL_PSEUDO_COLOR
  };
  static gint depths[] = { 24, 24, 32, 32, 16, 16, 15, 15, 8 };
  static nvisual_types = sizeof (types) / sizeof (types[0]);

  int i;

  g_return_if_fail (klass != NULL);

  if (!klass->info.visual)
    for (i = 0; i < nvisual_types; i++)
      if ((klass->info.visual = gdk_visual_get_best_with_both (depths[i], types[i])))
	{
	  if ((klass->info.visual->type == GDK_VISUAL_TRUE_COLOR) ||
	      (klass->info.visual->type == GDK_VISUAL_DIRECT_COLOR))
	    {
	      klass->info.lookup_red = g_new (gulong, 256);
	      klass->info.lookup_green = g_new (gulong, 256);
	      klass->info.lookup_blue = g_new (gulong, 256);

	      gtk_fill_lookup_array (klass->info.lookup_red,
				     klass->info.visual->depth,
				     klass->info.visual->red_shift,
				     8 - klass->info.visual->red_prec);
	      gtk_fill_lookup_array (klass->info.lookup_green,
				     klass->info.visual->depth,
				     klass->info.visual->green_shift,
				     8 - klass->info.visual->green_prec);
	      gtk_fill_lookup_array (klass->info.lookup_blue,
				     klass->info.visual->depth,
				     klass->info.visual->blue_shift,
				     8 - klass->info.visual->blue_prec);
	    }
	  break;
	}

  if (!klass->info.visual)
    {
      g_warning ("unable to find a suitable visual for color image display.\n");
      return;
    }

  switch (klass->info.visual->depth)
    {
    case 8:
      klass->info.bpp = 1;
      break;
    case 15:
    case 16:
      klass->info.bpp = 2;
      break;
    case 24:
    case 32:
      klass->info.bpp = 3;
      break;
    }
}

static void
gtk_preview_get_cmaps (GtkPreviewClass *klass)
{
  g_return_if_fail (klass != NULL);
  g_return_if_fail (klass->info.visual != NULL);

  if ((klass->info.visual->type != GDK_VISUAL_TRUE_COLOR) &&
      (klass->info.visual->type != GDK_VISUAL_DIRECT_COLOR))
    {
      if (install_cmap)
	{
	  klass->info.cmap = gdk_colormap_new (klass->info.visual, FALSE);
	  klass->info.cmap_alloced = install_cmap;

	  gtk_trim_cmap (klass);
	  gtk_create_8_bit (klass);
	}
      else
	{
	  guint nred;
	  guint ngreen;
	  guint nblue;
	  guint ngray;
	  gint set_prop;

	  klass->info.cmap = gdk_colormap_get_system ();

	  set_prop = TRUE;
	  if (gtk_get_preview_prop (&nred, &ngreen, &nblue, &ngray))
	    {
	      set_prop = FALSE;

	      klass->info.nred_shades = nred;
	      klass->info.ngreen_shades = ngreen;
	      klass->info.nblue_shades = nblue;
	      klass->info.ngray_shades = ngray;

	      if (klass->info.nreserved)
		{
		  klass->info.reserved_pixels = g_new (gulong, klass->info.nreserved);
		  if (!gdk_colors_alloc (klass->info.cmap, 0, NULL, 0,
					 klass->info.reserved_pixels,
					 klass->info.nreserved))
		    {
		      g_free (klass->info.reserved_pixels);
		      klass->info.reserved_pixels = NULL;
		    }
		}
	    }
	  else
	    {
	      gtk_trim_cmap (klass);
	    }

	  gtk_create_8_bit (klass);

	  if (set_prop)
	    gtk_set_preview_prop (klass->info.nred_shades,
				  klass->info.ngreen_shades,
				  klass->info.nblue_shades,
				  klass->info.ngray_shades);
	}
    }
  else
    {
      if (klass->info.visual == gdk_visual_get_system ())
	klass->info.cmap = gdk_colormap_get_system ();
      else
	klass->info.cmap = gdk_colormap_new (klass->info.visual, FALSE);
      klass->info.cmap_alloced = TRUE;

      klass->info.nred_shades = 0;
      klass->info.ngreen_shades = 0;
      klass->info.nblue_shades = 0;
      klass->info.ngray_shades = 0;
    }
}

static void
gtk_preview_dither_init (GtkPreviewClass *klass)
{
  int i, j, k;
  unsigned char low_shade, high_shade;
  unsigned short index;
  long red_mult, green_mult;
  double red_matrix_width;
  double green_matrix_width;
  double blue_matrix_width;
  double gray_matrix_width;
  double red_colors_per_shade;
  double green_colors_per_shade;
  double blue_colors_per_shade;
  double gray_colors_per_shade;
  gulong *gray_pixels;
  gint shades_r, shades_g, shades_b, shades_gray;
  GtkDitherInfo *red_ordered_dither;
  GtkDitherInfo *green_ordered_dither;
  GtkDitherInfo *blue_ordered_dither;
  GtkDitherInfo *gray_ordered_dither;
  guchar ***dither_matrix;
  guchar DM[8][8] =
  {
    { 0,  32, 8,  40, 2,  34, 10, 42 },
    { 48, 16, 56, 24, 50, 18, 58, 26 },
    { 12, 44, 4,  36, 14, 46, 6,  38 },
    { 60, 28, 52, 20, 62, 30, 54, 22 },
    { 3,  35, 11, 43, 1,  33, 9,  41 },
    { 51, 19, 59, 27, 49, 17, 57, 25 },
    { 15, 47, 7,  39, 13, 45, 5,  37 },
    { 63, 31, 55, 23, 61, 29, 53, 21 }
  };

  if (klass->info.visual->type != GDK_VISUAL_PSEUDO_COLOR)
    return;

  shades_r = klass->info.nred_shades;
  shades_g = klass->info.ngreen_shades;
  shades_b = klass->info.nblue_shades;
  shades_gray = klass->info.ngray_shades;

  red_mult = shades_g * shades_b;
  green_mult = shades_b;

  red_colors_per_shade = 255.0 / (shades_r - 1);
  red_matrix_width = red_colors_per_shade / 64;

  green_colors_per_shade = 255.0 / (shades_g - 1);
  green_matrix_width = green_colors_per_shade / 64;

  blue_colors_per_shade = 255.0 / (shades_b - 1);
  blue_matrix_width = blue_colors_per_shade / 64;

  gray_colors_per_shade = 255.0 / (shades_gray - 1);
  gray_matrix_width = gray_colors_per_shade / 64;

  /*  alloc the ordered dither arrays for accelerated dithering  */

  klass->info.dither_red = g_new (GtkDitherInfo, 256);
  klass->info.dither_green = g_new (GtkDitherInfo, 256);
  klass->info.dither_blue = g_new (GtkDitherInfo, 256);
  klass->info.dither_gray = g_new (GtkDitherInfo, 256);

  red_ordered_dither = klass->info.dither_red;
  green_ordered_dither = klass->info.dither_green;
  blue_ordered_dither = klass->info.dither_blue;
  gray_ordered_dither = klass->info.dither_gray;

  dither_matrix = g_new (guchar**, 8);
  for (i = 0; i < 8; i++)
    {
      dither_matrix[i] = g_new (guchar*, 8);
      for (j = 0; j < 8; j++)
	dither_matrix[i][j] = g_new (guchar, 65);
    }

  klass->info.dither_matrix = dither_matrix;

  /*  setup the ordered_dither_matrices  */

  for (i = 0; i < 8; i++)
    for (j = 0; j < 8; j++)
      for (k = 0; k <= 64; k++)
	dither_matrix[i][j][k] = (DM[i][j] < k) ? 1 : 0;

  /*  setup arrays containing three bytes of information for red, green, & blue  */
  /*  the arrays contain :
   *    1st byte:    low end shade value
   *    2nd byte:    high end shade value
   *    3rd & 4th bytes:    ordered dither matrix index
   */

  gray_pixels = klass->info.gray_pixels;

  for (i = 0; i < 256; i++)
    {

      /*  setup the red information  */
      {
	low_shade = (unsigned char) (i / red_colors_per_shade);
	if (low_shade == (shades_r - 1))
	  low_shade--;
	high_shade = low_shade + 1;

	index = (unsigned short)
	  (((double) i - low_shade * red_colors_per_shade) /
	   red_matrix_width);

	low_shade *= red_mult;
	high_shade *= red_mult;

	red_ordered_dither[i].s[1] = index;
	red_ordered_dither[i].c[0] = low_shade;
	red_ordered_dither[i].c[1] = high_shade;
      }


      /*  setup the green information  */
      {
	low_shade = (unsigned char) (i / green_colors_per_shade);
	if (low_shade == (shades_g - 1))
	  low_shade--;
	high_shade = low_shade + 1;

	index = (unsigned short)
	  (((double) i - low_shade * green_colors_per_shade) /
	   green_matrix_width);

	low_shade *= green_mult;
	high_shade *= green_mult;

	green_ordered_dither[i].s[1] = index;
	green_ordered_dither[i].c[0] = low_shade;
	green_ordered_dither[i].c[1] = high_shade;
      }


      /*  setup the blue information  */
      {
	low_shade = (unsigned char) (i / blue_colors_per_shade);
	if (low_shade == (shades_b - 1))
	  low_shade--;
	high_shade = low_shade + 1;

	index = (unsigned short)
	  (((double) i - low_shade * blue_colors_per_shade) /
	   blue_matrix_width);

	blue_ordered_dither[i].s[1] = index;
	blue_ordered_dither[i].c[0] = low_shade;
	blue_ordered_dither[i].c[1] = high_shade;
      }


      /*  setup the gray information */
      {
	low_shade = (unsigned char) (i / gray_colors_per_shade);
	if (low_shade == (shades_gray - 1))
	  low_shade--;
	high_shade = low_shade + 1;

	index = (unsigned short)
	  (((double) i - low_shade * gray_colors_per_shade) /
	   gray_matrix_width);

	gray_ordered_dither[i].s[1] = index;
	gray_ordered_dither[i].c[0] = gray_pixels[low_shade];
	gray_ordered_dither[i].c[1] = gray_pixels[high_shade];
      }
    }
}

static void
gtk_fill_lookup_array (gulong *array,
		       int     depth,
		       int     shift,
		       int     prec)
{
  double one_over_gamma;
  double ind;
  int val;
  int i;

  if (preview_class->info.gamma != 0.0)
    one_over_gamma = 1.0 / preview_class->info.gamma;
  else
    one_over_gamma = 1.0;

  for (i = 0; i < 256; i++)
    {
      ind = (double) i / 255.0;
      val = (int) (255 * pow (ind, one_over_gamma));
      array[i] = ((val >> prec) << shift);
    }
}

static void
gtk_trim_cmap (GtkPreviewClass *klass)
{
  gulong pixels[256];
  guint nred;
  guint ngreen;
  guint nblue;
  guint ngray;
  guint nreserved;
  guint total;
  guint tmp;
  gint success;

  nred = klass->info.nred_shades;
  ngreen = klass->info.ngreen_shades;
  nblue = klass->info.nblue_shades;
  ngray = klass->info.ngray_shades;
  nreserved = klass->info.nreserved;

  success = FALSE;
  while (!success)
    {
      total = nred * ngreen * nblue + ngray + nreserved;

      if (total <= 256)
	{
	  if ((nred < 2) || (ngreen < 2) || (nblue < 2) || (ngray < 2))
	    success = TRUE;
	  else
	    {
	      success = gdk_colors_alloc (klass->info.cmap, 0, NULL, 0, pixels, total);
	      if (success)
		{
		  if (nreserved > 0)
		    {
		      klass->info.reserved_pixels = g_new (gulong, nreserved);
		      memcpy (klass->info.reserved_pixels, pixels, sizeof (gulong) * nreserved);
		      gdk_colors_free (klass->info.cmap, &pixels[nreserved],
				       total - nreserved, 0);
		    }
		  else
		    {
		      gdk_colors_free (klass->info.cmap, pixels, total, 0);
		    }
		}
	    }
	}

      if (!success)
	{
	  if ((nblue >= nred) && (nblue >= ngreen))
	    nblue = nblue - 1;
	  else if ((nred >= ngreen) && (nred >= nblue))
	    nred = nred - 1;
	  else
	    {
	      tmp = log (ngray) / log (2);

	      if (ngreen >= tmp)
		ngreen = ngreen - 1;
	      else
		ngray -= 1;
	    }
	}
    }

  if ((nred < 2) || (ngreen < 2) || (nblue < 2) || (ngray < 2))
    {
      g_print ("Unable to allocate sufficient colormap entries.\n");
      g_print ("Try exiting other color intensive applications.\n");
      return;
    }

  /*  If any of the shade values has changed, issue a warning  */
  if ((nred != klass->info.nred_shades) ||
      (ngreen != klass->info.ngreen_shades) ||
      (nblue != klass->info.nblue_shades) ||
      (ngray != klass->info.ngray_shades))
    {
      g_print ("Not enough colors to satisfy requested color cube.\n");
      g_print ("Reduced color cube shades from\n");
      g_print ("[%d of Red, %d of Green, %d of Blue, %d of Gray] ==> [%d of Red, %d of Green, %d of Blue, %d of Gray]\n",
               klass->info.nred_shades, klass->info.ngreen_shades,
	       klass->info.nblue_shades, klass->info.ngray_shades,
	       nred, ngreen, nblue, ngray);
    }

  klass->info.nred_shades = nred;
  klass->info.ngreen_shades = ngreen;
  klass->info.nblue_shades = nblue;
  klass->info.ngray_shades = ngray;
}

static void
gtk_create_8_bit (GtkPreviewClass *klass)
{
  unsigned int r, g, b;
  unsigned int rv, gv, bv;
  unsigned int dr, dg, db, dgray;
  GdkColor color;
  gulong *pixels;
  double one_over_gamma;
  int i;

  if (!klass->info.color_pixels)
    klass->info.color_pixels = g_new (gulong, 256);

  if (!klass->info.gray_pixels)
    klass->info.gray_pixels = g_new (gulong, 256);

  if (klass->info.gamma != 0.0)
    one_over_gamma = 1.0 / klass->info.gamma;
  else
    one_over_gamma = 1.0;

  dr = klass->info.nred_shades - 1;
  dg = klass->info.ngreen_shades - 1;
  db = klass->info.nblue_shades - 1;
  dgray = klass->info.ngray_shades - 1;

  pixels = klass->info.color_pixels;

  for (r = 0, i = 0; r <= dr; r++)
    for (g = 0; g <= dg; g++)
      for (b = 0; b <= db; b++, i++)
        {
          rv = (unsigned int) ((r * klass->info.visual->colormap_size) / dr);
          gv = (unsigned int) ((g * klass->info.visual->colormap_size) / dg);
          bv = (unsigned int) ((b * klass->info.visual->colormap_size) / db);
          color.red = ((int) (255 * pow ((double) rv / 256.0, one_over_gamma))) * 257;
          color.green = ((int) (255 * pow ((double) gv / 256.0, one_over_gamma))) * 257;
          color.blue = ((int) (255 * pow ((double) bv / 256.0, one_over_gamma))) * 257;

	  if (!gdk_color_alloc (klass->info.cmap, &color))
	    {
	      g_error ("could not initialize 8-bit combined colormap");
	      return;
	    }

	  pixels[i] = color.pixel;
        }

  pixels = klass->info.gray_pixels;

  for (i = 0; i < klass->info.ngray_shades; i++)
    {
      color.red = (i * klass->info.visual->colormap_size) / dgray;
      color.red = ((int) (255 * pow ((double) color.red / 256.0, one_over_gamma))) * 257;
      color.green = color.red;
      color.blue = color.red;

      if (!gdk_color_alloc (klass->info.cmap, &color))
	{
	  g_error ("could not initialize 8-bit combined colormap");
	  return;
	}

      pixels[i] = color.pixel;
    }
}


static void
gtk_color_8 (guchar *src,
	     guchar *dest,
	     gint    x,
	     gint    y,
	     gulong  width)
{
  gulong *colors;
  GtkDitherInfo *dither_red;
  GtkDitherInfo *dither_green;
  GtkDitherInfo *dither_blue;
  GtkDitherInfo r, g, b;
  guchar **dither_matrix;
  guchar *matrix;

  colors = preview_class->info.color_pixels;
  dither_red = preview_class->info.dither_red;
  dither_green = preview_class->info.dither_green;
  dither_blue = preview_class->info.dither_blue;
  dither_matrix = preview_class->info.dither_matrix[y & 0x7];

  while (width--)
    {
      r = dither_red[src[0]];
      g = dither_green[src[1]];
      b = dither_blue[src[2]];
      src += 3;

      matrix = dither_matrix[x++ & 0x7];
      *dest++ = colors[(r.c[matrix[r.s[1]]] +
			g.c[matrix[g.s[1]]] +
			b.c[matrix[b.s[1]]])];
    }
}

static void
gtk_color_16 (guchar *src,
	      guchar *dest,
	      gulong  width)
{
  gulong *lookup_red;
  gulong *lookup_green;
  gulong *lookup_blue;
  gulong val;

  lookup_red = preview_class->info.lookup_red;
  lookup_green = preview_class->info.lookup_green;
  lookup_blue = preview_class->info.lookup_blue;

  while (width--)
    {
      val = COLOR_COMPOSE (src[0], src[1], src[2]);
      dest[0] = val;
      dest[1] = val >> 8;
      dest += 2;
      src += 3;
    }
}

static void
gtk_color_24 (guchar *src,
	      guchar *dest,
	      gulong  width)
{
  gulong *lookup_red;
  gulong *lookup_green;
  gulong *lookup_blue;
  gulong val;

  lookup_red = preview_class->info.lookup_red;
  lookup_green = preview_class->info.lookup_green;
  lookup_blue = preview_class->info.lookup_blue;

  while (width--)
    {
      val = COLOR_COMPOSE (src[0], src[1], src[2]);
      dest[0] = val;
      dest[1] = val >> 8;
      dest[2] = val >> 16;
      dest += 3;
      src += 3;
    }
}

static void
gtk_grayscale_8 (guchar *src,
		 guchar *dest,
		 gint    x,
		 gint    y,
		 gulong  width)
{
  GtkDitherInfo *dither_gray;
  GtkDitherInfo gray;
  guchar **dither_matrix;
  guchar *matrix;

  dither_gray = preview_class->info.dither_gray;
  dither_matrix = preview_class->info.dither_matrix[y & 0x7];

  while (width--)
    {
      gray = dither_gray[*src++];
      matrix = dither_matrix[x++ & 0x7];
      *dest++ = gray.c[matrix[gray.s[1]]];
    }
}

static void
gtk_grayscale_16 (guchar *src,
		  guchar *dest,
		  gulong  width)
{
  gulong *lookup_red;
  gulong *lookup_green;
  gulong *lookup_blue;
  gulong val;

  lookup_red = preview_class->info.lookup_red;
  lookup_green = preview_class->info.lookup_green;
  lookup_blue = preview_class->info.lookup_blue;

  while (width--)
    {
      val = COLOR_COMPOSE (*src, *src, *src);
      dest[0] = val;
      dest[1] = val >> 8;
      dest += 2;
      src += 1;
    }
}

static void
gtk_grayscale_24 (guchar  *src,
		  guchar  *dest,
		  gulong   width)
{
  gulong *lookup_red;
  gulong *lookup_green;
  gulong *lookup_blue;
  gulong val;

  lookup_red = preview_class->info.lookup_red;
  lookup_green = preview_class->info.lookup_green;
  lookup_blue = preview_class->info.lookup_blue;

  while (width--)
    {
      val = COLOR_COMPOSE (*src, *src, *src);
      dest[0] = val;
      dest[1] = val >> 8;
      dest[2] = val >> 16;
      dest += 3;
      src += 1;
    }
}


static gint
gtk_get_preview_prop (guint *nred,
		      guint *ngreen,
		      guint *nblue,
		      guint *ngray)
{
  GtkPreviewProp *prop;
  GdkAtom property;

  property = gdk_atom_intern ("GTK_PREVIEW_INFO", FALSE);

  if (gdk_property_get (NULL, property, property,
			0, sizeof (GtkPreviewProp), FALSE,
			NULL, NULL, (guchar**) &prop))
    {
      *nred = ntohs (prop->nred_shades);
      *ngreen = ntohs (prop->ngreen_shades);
      *nblue = ntohs (prop->nblue_shades);
      *ngray = ntohs (prop->ngray_shades);

      prop->ref_count = htons (ntohs (prop->ref_count) + 1);
      gdk_property_change (NULL, property, property, 16,
			   GDK_PROP_MODE_REPLACE,
			   (guchar*) prop, 5);

      return TRUE;
    }

  return FALSE;
}

static void
gtk_set_preview_prop (guint nred,
		      guint ngreen,
		      guint nblue,
		      guint ngray)
{
  GtkPreviewProp prop;
  GdkAtom property;

  property = gdk_atom_intern ("GTK_PREVIEW_INFO", FALSE);

  prop.ref_count = htons (1);
  prop.nred_shades = htons (nred);
  prop.ngreen_shades = htons (ngreen);
  prop.nblue_shades = htons (nblue);
  prop.ngray_shades = htons (ngray);

  gdk_property_change (NULL, property, property, 16,
		       GDK_PROP_MODE_REPLACE,
		       (guchar*) &prop, 5);
}


static void
gtk_lsbmsb_1_1 (guchar *dest,
		guchar *src,
		gint    count)
{
  memcpy (dest, src, count);
}

static void
gtk_lsb_2_2 (guchar *dest,
	     guchar *src,
	     gint    count)
{
  memcpy (dest, src, count * 2);
}

static void
gtk_msb_2_2 (guchar *dest,
	     guchar *src,
	     gint    count)
{
  while (count--)
    {
      dest[0] = src[1];
      dest[1] = src[0];
      dest += 2;
      src += 2;
    }
}

static void
gtk_lsb_3_3 (guchar *dest,
	     guchar *src,
	     gint    count)
{
  memcpy (dest, src, count * 3);
}

static void
gtk_msb_3_3 (guchar *dest,
	     guchar *src,
	     gint    count)
{
  while (count--)
    {
      dest[0] = src[2];
      dest[1] = src[1];
      dest[2] = src[0];
      dest += 3;
      src += 3;
    }
}

static void
gtk_lsb_3_4 (guchar *dest,
	     guchar *src,
	     gint    count)
{
  while (count--)
    {
      dest[0] = src[0];
      dest[1] = src[1];
      dest[2] = src[2];
      dest += 4;
      src += 3;
    }
}

static void
gtk_msb_3_4 (guchar *dest,
	     guchar *src,
	     gint    count)
{
  while (count--)
    {
      dest[1] = src[2];
      dest[2] = src[1];
      dest[3] = src[0];
      dest += 4;
      src += 3;
    }
}
