/* #ident	"@(#)x11:contrib/clients/xloadimage/xloadimage.c 6.17 92/07/15 Labtam" */
/* xloadimage.c:
 *
 * generic image loader for X11
 *
 * jim frost 09.27.89
 *
 * Copyright 1989, 1990, 1991 Jim Frost.
 * See included file "copyright.h" for complete copyright information.
 */

#include "copyright.h"
#include "xloadimage.h"
#include "patchlevel"
#include <signal.h>

/* if an image loader needs to have our display and screen, it will get
 * them from here.  this is done to keep most of the image routines
 * clean
 */

Display      *Disp= NULL;
int           Scrn= 0;
char         *argv0;

int           _XDebug = FALSE;
int           _DumpCore = FALSE;

/* used for the -default option.  this is the root weave bitmap with
 * the bits in the order that xloadimage likes.
 */

#define root_weave_width 4
#define root_weave_height 4
static byte root_weave_bits[] = {
  0xe0, 0xb0, 0xd0, 0x70
};

/* the real thing
 */

main(argc, argv)
     int argc;
     char *argv[];
{ Image          *dispimage;      /* image that will be sent to the display */
  Image          *newimage;       /* new image we're loading */
  Image          *tmpimage;
  Display        *disp;           /* display we're sending to */
  int             scrn;           /* screen we're sending to */
  XColor          xcolor;         /* color for border option */
  ImageOptions    images[MAXIMAGES + 1]; /* list of image w/ options to load */
  int             a;
  unsigned int    imagecount;     /* number of images in ImageName array */
  GeneralOptions  general_ops;    /* General options */
  unsigned int    winwidth, winheight;  /* geometry of image */
  ImageOptions    persist_ops;    /* options which persist from image to image*/
                  
  /* initialise some variables
   */
  argv0 = argv[0];		/* so we can get at this elsewhere */
  persist_ops.border= NULL;
  persist_ops.bright= 0;
  persist_ops.colordither= 0;
  persist_ops.colors= 0;     /* remaining images */
  persist_ops.delay= 0;
  persist_ops.dither= 0;
  persist_ops.gamma= 0.0;	/* nonsense value */
  persist_ops.normalize= 0;
  persist_ops.smooth= 0;
  persist_ops.xzoom= 0;
  persist_ops.yzoom= 0;

  /* set up internal error handlers
   */

  signal(SIGSEGV, internalError);
  signal(SIGBUS, internalError);
  signal(SIGFPE, internalError);
  signal(SIGILL, internalError);

#if defined(_AIX) && defined(_IBMR2)
  /* the RS/6000 (AIX 3.1) has a new signal, SIGDANGER, which you get
   * when memory is exhausted.  since malloc() can overcommit, it's a good
   * idea to trap this one.
   */
  signal(SIGDANGER, memoryExhausted);
#endif

  if (argc < 2)
    usage(argv[0]);

  /* defaults and other initial settings.  some of these depend on what
   * our name was when invoked.
   */

  loadPathsAndExts();
  general_ops.onroot= FALSE;
  general_ops.verbose= TRUE;
  if (!strcmp(tail(argv[0]), "xview")) {
    general_ops.onroot= FALSE;
    general_ops.verbose= TRUE;
  }
  else if (!strcmp(tail(argv[0]), "xsetbg")) {
    general_ops.onroot= TRUE;
    general_ops.verbose= FALSE;
  }

  {	/* Get display gamma from the environment if we can */
    char *gstr;
    general_ops.display_gamma = 0.0;
    if ((gstr = getenv("DISPLAY_GAMMA")) != NULL) {
	  general_ops.display_gamma= atof(gstr);
	}
    if (general_ops.display_gamma > 20.0	/* Ignore silly values */
     || general_ops.display_gamma < 0.05)
    general_ops.display_gamma = DEFAULT_DISPLAY_GAMMA;
  }
  general_ops.dname= NULL;
  general_ops.fit= FALSE;
  general_ops.fullscreen= FALSE;
  general_ops.go_to= NULL;
  general_ops.do_fork= FALSE;
  general_ops.identify= FALSE;
  general_ops.install= FALSE;
  general_ops.private_cmap= FALSE;
  general_ops.use_pixmap= FALSE;
  general_ops.set_default= FALSE;
  general_ops.dest_window= 0;
  general_ops.user_geometry = NULL;
  general_ops.visual_class= -1;
  winwidth= winheight= 0;

#define INIT_IMAGE_OPTS(istr){	\
    istr.name= NULL;		\
    istr.loader_idx= -1;	\
    istr.atx= istr.aty= 0;	\
    istr.border= NULL;		\
    istr.bright= 0;		\
    istr.center= 0;		\
    istr.clipx= istr.clipy= 0;	\
    istr.clipw= istr.cliph= 0;	\
    istr.colordither= 0;	\
    istr.colors= 0;		\
    istr.delay= 0;		\
    istr.dither= 0;		\
    istr.gamma= 0.0;		\
    istr.gray= 0;		\
    istr.merge= 0;		\
    istr.normalize= 0;		\
    istr.rotate= 0;		\
    istr.smooth= 0;		\
    istr.xzoom= istr.yzoom= 0;	\
    istr.fg= istr.bg= NULL;	\
    istr.done_to= FALSE;	\
    istr.to_argv= NULL; }

  imagecount= 0;
  for (a= 0; a < MAXIMAGES; a++) {
    INIT_IMAGE_OPTS(images[a]);
  }
  /*
   * Process the command line
   */
  for (a= 1; a < argc; a++) {
    OptionId opid;
    opid = optionNumber(argv[a]);
	
    if (opid ==  OPT_BADOPT) {
      printf("%s: Bad option\n", argv[a]);
      usage(argv[0]);
      /* NOTREACHED */
    } else
    if (opid ==  OPT_NOTOPT || opid == NAME) {
      if (opid == NAME && argv[++a] == NULL)
        continue;
      if (imagecount == MAXIMAGES)
	printf("%s: Too many images (ignoring)\n", argv[a]);
      else {
	images[imagecount].name= argv[a];
	images[imagecount].border= persist_ops.border;
	images[imagecount].bright= persist_ops.bright;
	images[imagecount].colordither= persist_ops.colordither;
	images[imagecount].colors= persist_ops.colors;
	images[imagecount].delay= persist_ops.delay;
	images[imagecount].dither= persist_ops.dither;
	images[imagecount].gamma= persist_ops.gamma;
	images[imagecount].normalize= persist_ops.normalize;
	images[imagecount].smooth= persist_ops.smooth;
	images[imagecount].xzoom= persist_ops.xzoom;
	images[imagecount].yzoom= persist_ops.yzoom;
	imagecount++;
      }
    } else
    if (opid == OPT_SHORTOPT) {
      printf("%s: Not enough characters to identify option\n", argv[a]);
      usage(argv[0]);
      /* NOTREACHED */
    } else
    if (isGeneralOption(opid)) {

      /* process options general to everything
       */

      a += doGeneralOption(opid, &argv[a], &general_ops, &persist_ops, &images[imagecount]);

    } else
    if (isLocalOption(opid)) {

      /* process options local to an image
       */

      a += doLocalOption(opid, &argv[a], TRUE, &persist_ops, &images[imagecount]);

    } else {

      /* this should not happen!
       */

      printf("%s: Internal error parsing arguments\n", argv[0]);
      exit(1);
    }
  }

  if (_DumpCore) {	/* Don't trap signals, - dump core */
    signal(SIGSEGV, SIG_DFL);
    signal(SIGBUS, SIG_DFL);
    signal(SIGFPE, SIG_DFL);
    signal(SIGILL, SIG_DFL);
  }

  if (general_ops.fit && (general_ops.visual_class != -1)) {
    printf("-fit and -visual options are mutually exclusive (ignoring -visual)\n");
    general_ops.visual_class= -1;
  }

  if (!imagecount && !general_ops.set_default) /* NO-OP from here on */
    exit(0);

  if (general_ops.identify) {                    /* identify the named image(s) */
    for (a= 0; a < imagecount; a++)
      identifyImage(images[a].name);
    exit(0);
  }

  /* start talking to the display
   */

  if (! (Disp= disp= XOpenDisplay(general_ops.dname))) {
    printf("%s: Cannot open display\n", XDisplayName(general_ops.dname));
    exit(1);
  }
  Scrn= scrn= DefaultScreen(disp);
  XSetErrorHandler(errorHandler);

  /* background ourselves if the user asked us to
   */

  if (general_ops.do_fork)
    switch(fork()) {
    case -1:
      perror("fork");
      /* FALLTHRU */
    case 0:
      break;
    default:
      exit(0);
    }

  /* handle -default option.  this resets the colormap and loads the
   * default root weave.
   */

  if (general_ops.set_default) {
    byte *old_data;
    ImageOptions ioptions;

    INIT_IMAGE_OPTS(ioptions);
    dispimage= newBitImage(root_weave_width, root_weave_height);
    dispimage->gamma = general_ops.display_gamma;
    old_data= dispimage->data;
    dispimage->data= root_weave_bits;
    imageOnRoot(disp, scrn, general_ops.dest_window, dispimage, general_ops.display_gamma, &ioptions, FALSE);
    dispimage->data= old_data;
    freeImage(dispimage);
    if (!imagecount) { /* all done */
      XCloseDisplay(disp);
      exit(0);
    }
  }

  dispimage= NULL;

  /* load in each named image
   */

  for (a= 0; a < imagecount; a++) {
    if (! (newimage= loadImage(&images[a], general_ops.verbose)))
      continue;
    
    if (images[a].gamma != 0.0)		/* override image gamma */
      newimage->gamma = images[a].gamma;
    else if(newimage->gamma == 0.0)
      defaultgamma(newimage,&general_ops);

    /* Process any other options that need doing here */
    if (images[a].border)
      XParseColor(disp, DefaultColormap(disp, scrn), images[a].border, &images[a].bordercol);

    /* if this is the first image and we're putting it on the root window
     * in fullscreen mode, set the zoom factors to something reasonable.
     */

    if ((a == 0) && general_ops.onroot && general_ops.fullscreen &&
	(images[0].xzoom == 0.0) && (images[0].yzoom == 0.0) &&
	(images[0].atx == 0) && (images[0].aty == 0) &&
	(images[0].center == 0)) {
    
      if ((newimage->width > DisplayWidth(disp, scrn)) ||
	  (newimage->height > DisplayHeight(disp, scrn))) {
	images[0].xzoom= images[0].yzoom=
	  (newimage->width - DisplayWidth(disp, scrn) >
	   newimage->height - DisplayHeight(disp, scrn) ?
	   (float)DisplayWidth(disp, scrn) / (float)newimage->width * 100.0 :
	   (float)DisplayHeight(disp, scrn) / (float)newimage->height * 100.0);
      }
      else {
	images[0].xzoom= images[0].yzoom=
	  (DisplayWidth(disp, scrn) - newimage->width <
	   DisplayHeight(disp, scrn) - newimage->height ?
	   (float)DisplayWidth(disp, scrn) / (float)newimage->width * 100.0 :
	   (float)DisplayHeight(disp, scrn) / (float)newimage->height * 100.0);
      }
    }

    tmpimage= processImage(disp, scrn, newimage, &images[a], &general_ops);
    if (tmpimage != newimage)	/* Dont't keep un-processed image */
	freeImage(newimage);
    newimage = tmpimage;

    if (dispimage) {	/* If there is something to merge with */
      if (images[a].center) {
        images[a].atx= (int)(dispimage->width - newimage->width) / 2;
        images[a].aty= (int)(dispimage->height - newimage->height) / 2;
      }
      if (! dispimage->title) {
	dispimage->title= dupString(newimage->title);
	dispimage->gamma= newimage->gamma;
      }
      tmpimage= merge(dispimage, newimage, images[a].atx, images[a].aty,
		      &images[a], &general_ops);
      if (dispimage != tmpimage) {
	freeImage(dispimage);
	dispimage= tmpimage;
      }
      freeImage(newimage);
    }
    /* else if this is the first image on root, set its position and any border needed */
    else if (a == 0 && general_ops.onroot &&
      (winwidth || winheight || images[0].center ||
      images[0].atx || images[0].aty || general_ops.fullscreen)) {
      int atx = images[a].atx,aty = images[a].aty;
      if (!winwidth)
	winwidth= DisplayWidth(disp, scrn);
      if (!winheight)
        winheight= DisplayHeight(disp, scrn);

      if (atx == 0 && aty == 0) {	/* center image by default */
        atx= (int)(winwidth - newimage->width) / 2;
        aty= (int)(winheight - newimage->height) / 2;
      }
      /* use clip to put border around image */
      tmpimage= clip(newimage, -atx, -aty, winwidth, winheight, &images[a], &general_ops);
      if (tmpimage != newimage) {
	freeImage(newimage);
	dispimage= tmpimage;
      }
    }
    else
      dispimage= newimage;

    /* if next image is to be merged onto this one, then go on to the next image.
     */

    if (general_ops.onroot || (((a+1) < imagecount) && (images[a + 1].merge)))
      continue;

    switch(imageInWindow(disp, scrn, dispimage, general_ops.user_geometry,
                         general_ops.fullscreen, general_ops.install,
                         general_ops.private_cmap, general_ops.fit,
                         general_ops.use_pixmap, general_ops.visual_class,
                         argc, argv, general_ops.display_gamma, &images[a],
                         general_ops.verbose)) {
    case '\0': /* window got nuked by someone */
      XCloseDisplay(disp);
      exit(1);
    case '\003':
    case 'q':  /* user quit */
      cleanUpWindow(disp);
      XCloseDisplay(disp);
      exit(0);
    case ' ':
    case 'f':  /* forwards or */
    case 'n':  /* next image */
      if (a >= (imagecount -1) && general_ops.go_to != NULL) {
	int b;
	for (b= 0; b < imagecount; b++)
	  if (!strcmp(images[b].name, general_ops.go_to)) {
	    a= b - 1;
	    goto next_image;
	  }
	fprintf(stderr, "Target for -goto %s was not found\n",
		general_ops.go_to);
      next_image:
	;
      }
      break;
    case 'b':  /* backwards or */
    case 'p':  /* previous image */
      if (a > 0)
	a--;
      a--;
      break;
    }
    freeImage(dispimage);
    dispimage= NULL;
  }

  if (dispimage != NULL && general_ops.onroot)
    imageOnRoot(disp, scrn, general_ops.dest_window, dispimage, general_ops.display_gamma, &images[imagecount-1], general_ops.verbose);
  XCloseDisplay(disp);
  exit(0);
}
