/* send.c:
 *
 * send an Image to an X pixmap
 *
 * jim frost 10.02.89
 *
 * Copyright 1989, 1990, 1991 Jim Frost.
 * See included file "copyright.h" for complete copyright information.
 */

#include "copyright.h"
#include "xloadimage.h"

static int GotError;

static int pixmapErrorTrap(disp, pErrorEvent)
    Display	*disp;
    XErrorEvent * pErrorEvent;
{
#define MAXERRORLEN 100
    char buf[MAXERRORLEN+1];
    GotError = 1;
    XGetErrorText(disp, pErrorEvent->error_code, buf, MAXERRORLEN);
    printf("serial #%d (request code %d) Got Error %s\n",
	pErrorEvent->serial,
	pErrorEvent->request_code,
	buf);
    return(0);
}

Pixmap ximageToPixmap(disp, parent, ximageinfo)
     Display    *disp;
     Window      parent;
     XImageInfo *ximageinfo;
{
  int         (*old_handler)();
  Pixmap        pixmap;

  GotError = 0;
  old_handler = XSetErrorHandler(pixmapErrorTrap);
  XSync(disp, False);
  pixmap= XCreatePixmap(disp, parent,
			ximageinfo->ximage->width, ximageinfo->ximage->height,
			ximageinfo->depth);
  (void)XSetErrorHandler(old_handler);
  if (GotError)
    return(None);
  ximageinfo->drawable= pixmap;
  sendXImage(ximageinfo, 0, 0, 0, 0,
	     ximageinfo->ximage->width, ximageinfo->ximage->height);
  return(pixmap);
}

/* find the best pixmap depth supported by the server for a particular
 * visual and return that depth.
 *
 * this is complicated by R3's lack of XListPixmapFormats so we fake it
 * by looking at the structure ourselves.
 */

static unsigned int bitsPerPixelAtDepth(disp, scrn, depth)
     Display      *disp;
     int           scrn;
     unsigned int  depth;
{
#if XlibSpecificationRelease < 4 /* the way things were */
  unsigned int a;

  for (a= 0; a < disp->nformats; a++)
    if (disp->pixmap_format[a].depth == depth)
      return(disp->pixmap_format[a].bits_per_pixel);

#else /* the way things should be */
  XPixmapFormatValues *xf;
  unsigned int nxf, a;

  xf = XListPixmapFormats(disp, (int *)&nxf);
  for (a = 0; a < nxf; a++)
    if (xf[a].depth == depth)
      return(xf[a].bits_per_pixel);
#endif

  /* this should never happen; if it does, we're in trouble
   */

  fprintf(stderr, "bitsPerPixelAtDepth: Can't find pixmap depth info!\n");
  exit(1);
}
     
XImageInfo *imageToXImage(disp, scrn, visual, ddepth, image, private_cmap, fit,
			  verbose)
     Display      *disp;
     int           scrn;
     Visual       *visual; /* visual to use */
     unsigned int  ddepth; /* depth of the visual to use */
     Image        *image;
     unsigned int  private_cmap;
     unsigned int  fit;
     unsigned int  verbose;
{ Pixel        *index, *redvalue, *greenvalue, *bluevalue;
  unsigned int  a, b, newmap, x, y, linelen, dpixlen, dbits;
  XColor        xcolor;
  XGCValues     gcv;
  XImageInfo   *ximageinfo;
  Image        *orig_image;

  goodImage(image, "imageToXimage");

  xcolor.flags= DoRed | DoGreen | DoBlue;
  index= redvalue= greenvalue= bluevalue= NULL;
  orig_image= image;
  ximageinfo= (XImageInfo *)lmalloc(sizeof(XImageInfo));
  ximageinfo->disp= disp;
  ximageinfo->scrn= scrn;
  ximageinfo->depth= 0;
  ximageinfo->drawable= None;
  ximageinfo->foreground= ximageinfo->background= 0;
  ximageinfo->gc= NULL;
  ximageinfo->ximage= NULL;

  /* process image based on type of visual we're sending to
   */

  switch (image->type) {
  case ITRUE:
    switch (visual->class) {
    case TrueColor:
    case DirectColor:
      /* goody goody */
      break;
    default:
      if (visual->bits_per_rgb > 1)
	image= reduce(image, depthToColors(visual->bits_per_rgb), verbose);
      else
	image= dither(image, verbose);
    }
    break;

  case IRGB:
    switch(visual->class) {
    case TrueColor:
    case DirectColor:
      /* no problem, we handle this just fine */
      break;
    default:
      if (visual->bits_per_rgb < 2)
	image= dither(image, verbose);
      break;
    }

  case IBITMAP:
    /* no processing ever needs to be done for bitmaps */
    break;
  }

  /* do color allocation
   */

  switch (visual->class) {
  case TrueColor:
  case DirectColor:
    if (!BITMAPP(image)) { /* bitmaps don't need all of this */
      Pixel pixval;
      unsigned int redcolors, greencolors, bluecolors;
      unsigned int redstep, greenstep, bluestep;
      unsigned int redbottom, greenbottom, bluebottom;
      unsigned int redtop, greentop, bluetop;

      redvalue= (Pixel *)lmalloc(sizeof(Pixel) * 256);
      greenvalue= (Pixel *)lmalloc(sizeof(Pixel) * 256);
      bluevalue= (Pixel *)lmalloc(sizeof(Pixel) * 256);

      if (visual == DefaultVisual(disp, scrn))
	ximageinfo->cmap= DefaultColormap(disp, scrn);
      else
	ximageinfo->cmap= XCreateColormap(disp, RootWindow(disp, scrn),
					  visual, AllocNone);

      retry_direct: /* tag we hit if a DirectColor allocation fails on
		     * default colormap */

      /* calculate number of distinct colors in each band
       */

      redcolors= greencolors= bluecolors= 1;
      for (pixval= 1; pixval; pixval <<= 1) {
	if (pixval & visual->red_mask)
	  redcolors <<= 1;
	if (pixval & visual->green_mask)
	  greencolors <<= 1;
	if (pixval & visual->blue_mask)
	  bluecolors <<= 1;
      }
      
      /* sanity check
       */

      if ((redcolors > visual->map_entries) ||
	  (greencolors > visual->map_entries) ||
	  (bluecolors > visual->map_entries)) {
	fprintf(stderr, "\
Warning: inconsistency in color information (this may be ugly)\n");
      }

      redstep= 256 / redcolors;
      greenstep= 256 / greencolors;
      bluestep= 256 / bluecolors;
      redbottom= greenbottom= bluebottom= 0;
      for (a= 0; a < visual->map_entries; a++) {
	if (redbottom < 256)
	  redtop= redbottom + redstep;
	if (greenbottom < 256)
	  greentop= greenbottom + greenstep;
	if (bluebottom < 256)
	  bluetop= bluebottom + bluestep;

	xcolor.red= (redtop - 1) << 8;
	xcolor.green= (greentop - 1) << 8;
	xcolor.blue= (bluetop - 1) << 8;
	if (! XAllocColor(disp, ximageinfo->cmap, &xcolor)) {

	  /* if an allocation fails for a DirectColor default visual then
	   * we should create a private colormap and try again.
	   */

	  if ((visual->class == DirectColor) &&
	      (visual == DefaultVisual(disp, scrn))) {
	    ximageinfo->cmap= XCreateColormap(disp, RootWindow(disp, scrn),
					      visual, AllocNone);
	    goto retry_direct;
	  }

	  /* something completely unexpected happened
	   */

	  fprintf(stderr, "\
imageToXImage: XAllocColor failed on a TrueColor/Directcolor visual\n");
	  return(NULL);
	}

	/* fill in pixel values for each band at this intensity
	 */

	while ((redbottom < 256) && (redbottom < redtop))
	  redvalue[redbottom++]= xcolor.pixel & visual->red_mask;
	while ((greenbottom < 256) && (greenbottom < greentop))
	  greenvalue[greenbottom++]= xcolor.pixel & visual->green_mask;
	while ((bluebottom < 256) && (bluebottom < bluetop))
	  bluevalue[bluebottom++]= xcolor.pixel & visual->blue_mask;
      }
      break;
    }
    /* bitmaps will fall through to the default case */
    /* FALLTHRU */

  default:
  retry: /* this tag is used when retrying because we couldn't get a fit */
    compress(image, verbose);

    index= (Pixel *)lmalloc(sizeof(Pixel) * image->rgb.used);

    /* private_cmap flag is invalid if not a dynamic visual
     */

    switch (visual->class) {
    case StaticColor:
    case StaticGray:
      private_cmap= 0;
    }

    /* get the colormap to use.
     */

    if (private_cmap) { /* user asked us to use a private cmap */
      newmap= 1;
      fit= 0;
    }
    else if ((visual == DefaultVisual(disp, scrn)) ||
	     (visual->class == StaticGray) ||
	     (visual->class == StaticColor) ||
	     (visual->class == TrueColor) ||
	     (visual->class == DirectColor)) {

      /* if we're using the default visual, try to alloc colors shareable.
       * otherwise we're using a static visual and should treat it
       * accordingly.
       */

      if (visual == DefaultVisual(disp, scrn))
	ximageinfo->cmap= DefaultColormap(disp, scrn);
      else
	ximageinfo->cmap= XCreateColormap(disp, RootWindow(disp, scrn),
					  visual, AllocNone);
      newmap= 0;

      /* allocate colors shareable (if we can)
       */

      for (a= 0; a < image->rgb.used; a++) {
	xcolor.red= *(image->rgb.red + a);
	xcolor.green= *(image->rgb.green + a);
	xcolor.blue= *(image->rgb.blue + a);
	if (! XAllocColor(disp, ximageinfo->cmap, &xcolor))
	  if ((visual->class == StaticColor) ||
	      (visual->class == StaticGray) ||
	      (visual->class == TrueColor) ||
	      (visual->class == DirectColor)) {
	    printf("imageToXImage: XAllocColor failed on a static visual\n");
	    return(NULL);
	  }
	  else {

	    /* we can't allocate the colors shareable so free all the colors
	     * we had allocated and create a private colormap (or fit
	     * into the default cmap if `fit' is true).
	     */

	    XFreeColors(disp, ximageinfo->cmap, index, a, 0);
	    newmap= 1;
	    break;
	  }
	*(index + a)= xcolor.pixel;
      }
    }
    else {
      newmap= 1;
      fit= 0;
    }

    if (newmap) {

      /* either create a new colormap or fit the image into the one we
       * have.  to create a new one, we create a private cmap and allocate
       * the colors writable.  fitting the colors is harder, we have to:
       *  1. grab the server so no one can goof with the colormap.
       *  2. count the available colors using XAllocColorCells.
       *  3. free the colors we just allocated.
       *  4. reduce the depth of the image to fit.
       *  5. allocate the colors again shareable.
       *  6. ungrab the server and continue on our way.
       * someone should shoot the people who designed X color allocation.
       */

      if (fit) {
	if (verbose)
	  printf("  Fitting image into default colormap\n");
	XGrabServer(disp);
      }
      else {
	if (verbose)
	  printf("  Using private colormap\n");

	/* create new colormap
	 */

	ximageinfo->cmap= XCreateColormap(disp, RootWindow(disp, scrn),
					  visual, AllocNone);
      }

      for (a= 0; a < image->rgb.used; a++) /* count entries we got */
	if (! XAllocColorCells(disp, ximageinfo->cmap, False, NULL, 0,
			       index + a, 1))
	  break;

      if (fit) {
	if (a > 0)
	  XFreeColors(disp, ximageinfo->cmap, index, a, 0);
	if (a <= 2) {
	  if (verbose) {
	    printf("  Cannot fit into default colormap, dithering...");
	    fflush(stdout);
	  }
	  image= dither(image, 0);
	  if (verbose)
	    printf("done\n");
	  fit= 0;
	  lfree((byte *)index);
	  XUngrabServer(disp);
	  goto retry;
	}
      }

      if (a == 0) {
	fprintf(stderr, "imageToXImage: Color allocation failed!\n");
	lfree((byte *)index);
	XUngrabServer(disp);
	return(NULL);
      }

      if (a < image->rgb.used)
	image= reduce(image, a, verbose);

      if (fit) {
	for (a= 0; a < image->rgb.used; a++) {
	  xcolor.red= *(image->rgb.red + a);
	  xcolor.green= *(image->rgb.green + a);
	  xcolor.blue= *(image->rgb.blue + a);

	  /* if this fails we're in trouble
	   */

	  if (! XAllocColor(disp, ximageinfo->cmap, &xcolor)) {
	    XUngrabServer(disp);
	    printf("XAllocColor failed while fitting colormap!\n");
	    return(NULL);
	  }
	  *(index + a)= xcolor.pixel;
	}
	XUngrabServer(disp);
      }
      else {
	for (b= 0; b < a; b++) {
	  xcolor.pixel= *(index + b);
	  xcolor.red= *(image->rgb.red + b);
	  xcolor.green= *(image->rgb.green + b);
	  xcolor.blue= *(image->rgb.blue + b);
	  XStoreColor(disp, ximageinfo->cmap, &xcolor);
	}
      }
    }
    break;
  }

  /* create an XImage and related colormap based on the image type
   * we have.
   */

  if (verbose) {
    printf("  Building XImage...");
    fflush(stdout);
  }

  switch (image->type) {
  case IBITMAP:
    { byte *data;

      /* we copy the data to be more consistent
       */

      data= lmalloc((image->width + 7) / 8 * image->height);
      bcopy(image->data, data, ((image->width + 7) / 8) * image->height);

      gcv.function= GXcopy;
      ximageinfo->ximage= XCreateImage(disp, visual, 1, XYBitmap,
				       0, (char *)data, image->width, image->height,
				       8, 0);
      ximageinfo->depth= ddepth;
      ximageinfo->foreground= *(index + 1);
      ximageinfo->background= *index;
      ximageinfo->ximage->bitmap_bit_order= MSBFirst;
      ximageinfo->ximage->byte_order= MSBFirst;
      break;
    }

  case IRGB:
  case ITRUE:

    /* modify image data to match visual and colormap
     */

    dbits= bitsPerPixelAtDepth(disp, scrn, ddepth);
    ximageinfo->depth= ddepth;
    dpixlen= (dbits + 7) / 8;

    switch (visual->class) {
    case DirectColor:
    case TrueColor:
      { byte *data, *destptr, *srcptr;
	Pixel pixval, newpixval;

	ximageinfo->ximage = XCreateImage(disp, visual, ddepth, ZPixmap, 0,
					  NULL, image->width, image->height,
					  8, 0);
	data= lmalloc(image->width * image->height * dpixlen);
	ximageinfo->ximage->data= (char *)data;
	destptr= data;
	srcptr= image->data;
	switch (image->type) {
	case ITRUE:
	  for (y= 0; y < image->height; y++)
	    for (x= 0; x < image->width; x++) {
	      pixval= memToVal(srcptr, image->pixlen);
	      newpixval= redvalue[TRUE_RED(pixval)] |
		greenvalue[TRUE_GREEN(pixval)] | bluevalue[TRUE_BLUE(pixval)];
	      valToMem(newpixval, destptr, dpixlen);
	      srcptr += image->pixlen;
	      destptr += dpixlen;
	    }
	  break;
	case IRGB:
	  for (y= 0; y < image->height; y++)
	    for (x= 0; x < image->width; x++) {
	      pixval= memToVal(srcptr, image->pixlen);
	      pixval= redvalue[image->rgb.red[pixval] >> 8] |
		greenvalue[image->rgb.green[pixval] >> 8] |
		  bluevalue[image->rgb.blue[pixval] >> 8];
	      valToMem(pixval, destptr, dpixlen);
	      srcptr += image->pixlen;
	      destptr += dpixlen;
	    }
	  break;
	default: /* something's broken */
	  printf("Unexpected image type for DirectColor/TrueColor visual!\n");
	  exit(0);
	}
	ximageinfo->ximage->byte_order= MSBFirst; /* trust me, i know what
						   * i'm talking about */
	break;
      }
    default:

      /* only IRGB images make it this far.
       */

      /* if our XImage doesn't have modulus 8 bits per pixel, it's unclear
       * how to pack bits so we instead use an XYPixmap image.  this is
       * slower.
       */

      if (dbits % 8) {
	byte *data, *destdata, *destptr, *srcptr, mask;
	Pixel pixmask, pixval;

	ximageinfo->ximage = XCreateImage(disp, visual, ddepth, XYPixmap, 0,
					  NULL, image->width, image->height,
					  8, 0);

	data= (byte *)lmalloc(image->width * image->height * dpixlen);
	ximageinfo->ximage->data= (char *)data;
	bzero(data, image->width * image->height * dpixlen);
	ximageinfo->ximage->bitmap_bit_order= MSBFirst;
	ximageinfo->ximage->byte_order= MSBFirst;
	linelen= (image->width + 7) / 8;
	for (a= 0; a < dbits; a++) {
	  pixmask= 1 << a;
	  destdata= data + ((ddepth - a - 1) * image->height * linelen);
	  srcptr= image->data;
	  for (y= 0; y < image->height; y++) {
	    destptr= destdata + (y * linelen);
	    *destptr= 0;
	    mask= 0x80;
	    for (x= 0; x < image->width; x++) {
	      pixval= memToVal(srcptr, image->pixlen);
	      srcptr += image->pixlen;
	      if (index[pixval] & pixmask)
		*destptr |= mask;
	      mask >>= 1;
	      if (mask == 0) {
		mask= 0x80;
		destptr++;
	      }
	    }
	  }
	}
      }
      else {
	byte *data, *srcptr, *destptr;

	ximageinfo->ximage = XCreateImage(disp, visual, ddepth, ZPixmap, 0,
					  NULL, image->width, image->height,
					  8, 0);

	dpixlen= (ximageinfo->ximage->bits_per_pixel + 7) / 8;
	data= (byte *)lmalloc(image->width * image->height * dpixlen);
	ximageinfo->ximage->data= (char *)data;
	ximageinfo->ximage->byte_order= MSBFirst; /* trust me, i know what
						   * i'm talking about */
	srcptr= image->data;
	destptr= data;
	for (y= 0; y < image->height; y++)
	  for (x= 0; x < image->width; x++) {
	    valToMem(index[memToVal(srcptr, image->pixlen)], destptr, dpixlen);
	    srcptr += image->pixlen;
	    destptr += dpixlen;
	  }
      }
      break;
    }
  }

  if (verbose)
    printf("done\n");

  if (index)
    lfree((byte *)index);
  if (redvalue) {
    lfree((byte *)redvalue);
    lfree((byte *)greenvalue);
    lfree((byte *)bluevalue);
  }
  if (image != orig_image)
    freeImage(image);
  return(ximageinfo);
}

/* Given an XImage and a drawable, move a rectangle from the Ximage
 * to the drawable.
 */

void sendXImage(ximageinfo, src_x, src_y, dst_x, dst_y, w, h)
     XImageInfo  *ximageinfo;
     int          src_x, src_y, dst_x, dst_y;
     unsigned int w, h;
{ XGCValues gcv;
  int a, orig_depth;
  char *orig_data;

  /* build and cache the GC
   */

  if (!ximageinfo->gc) {
    gcv.function= GXcopy;
    if (ximageinfo->ximage->depth == 1) {
      gcv.foreground= ximageinfo->foreground;
      gcv.background= ximageinfo->background;
      ximageinfo->gc= XCreateGC(ximageinfo->disp, ximageinfo->drawable,
				GCFunction | GCForeground | GCBackground,
				&gcv);
    }
    else
      ximageinfo->gc= XCreateGC(ximageinfo->disp, ximageinfo->drawable,
				GCFunction, &gcv);
  }

  XPutImage(ximageinfo->disp, ximageinfo->drawable, ximageinfo->gc,
	    ximageinfo->ximage, src_x, src_y, dst_x, dst_y, w, h);
}

/* free up anything cached in the local Ximage structure.
 */

void freeXImage(image, ximageinfo)
     Image        *image;
     XImageInfo   *ximageinfo;
{
  if (ximageinfo->gc)
    XFreeGC(ximageinfo->disp, ximageinfo->gc);
  lfree((byte *)ximageinfo->ximage->data);
  ximageinfo->ximage->data= NULL;
  XDestroyImage(ximageinfo->ximage);
  lfree((byte *)ximageinfo);
}
