1 /* xloadimage.c:
2  *
3  * generic image loader for X11
4  *
5  * jim frost 09.27.89
6  *
7  * Copyright 1989, 1990, 1991 Jim Frost.
8  * See included file "copyright.h" for complete copyright information.
9  */
10 
11 #include "copyright.h"
12 #include "xloadimage.h"
13 #include "options.h"
14 #include "misc.h"
15 #ifdef VMS
16 #include "patchlevel."
17 #define NO_FORK
18 #else
19 #include "patchlevel"
20 #endif
21 #ifdef HAVE_UNISTD_H
22 #include <unistd.h>
23 #endif
24 #include <signal.h>
25 
26 char *ProgramName= "xloadimage";
27 
28 /* if an image loader needs to have our display and screen, it will get
29  * them from here.  this is done to keep most of the image routines
30  * clean
31  */
32 
33 Display      *Disp= NULL;
34 int           Scrn= 0;
35 
36 /* used for the -default option.  this is the root weave bitmap with
37  * the bits in the order that xloadimage likes.
38  */
39 
40 #define root_weave_width 4
41 #define root_weave_height 4
42 static byte root_weave_bits[] = {
43   0xe0, 0xb0, 0xd0, 0x70
44 };
45 
doProcessOnImage(image,option,verbose)46 static Image *doProcessOnImage(image, option, verbose)
47      Image *image;
48      Option *option;
49 { Image  *retimage= image;
50   XColor  xcolor; /* color for foreground/background */
51 
52   switch (option->type) {
53   case BACKGROUND:
54     if (image->depth > 1)
55       break;
56     XParseColor(Disp, DefaultColormap(Disp, Scrn), option->info.background,
57 		&xcolor);
58     image->rgb.red[0]= xcolor.red;
59     image->rgb.green[0]= xcolor.green;
60     image->rgb.blue[0]= xcolor.blue;
61     break;
62 
63   case BRIGHT:
64     brighten(image, option->info.bright, verbose);
65     break;
66 
67   case CLIP:
68     retimage= clip(image, option->info.clip.x, option->info.clip.y,
69 		   option->info.clip.w, option->info.clip.h, verbose);
70     break;
71 
72   case COLORS:
73     retimage= reduce(image, option->info.colors, verbose);
74     break;
75 
76   case DITHER:
77     retimage= dither(image, verbose);
78     break;
79 
80   case FOREGROUND:
81     if (image->depth > 1)
82       break;
83     XParseColor(Disp, DefaultColormap(Disp, Scrn), option->info.foreground,
84 		&xcolor);
85     image->rgb.red[1]= xcolor.red;
86     image->rgb.green[1]= xcolor.green;
87     image->rgb.blue[1]= xcolor.blue;
88     break;
89 
90   case GAMMA:
91     gammacorrect(image, option->info.gamma, verbose);
92     break;
93 
94   case GRAY:
95     if (BITMAPP(image))
96       retimage= undither(image, verbose);
97     else
98       gray(image, verbose);
99     break;
100 
101   case HALFTONE:
102     retimage= halftone(image, verbose);
103     break;
104 
105   case NORMALIZE:
106     retimage= normalize(image, verbose);
107     break;
108 
109   case ROTATE:
110     retimage= rotate(image, option->info.rotate, verbose);
111     break;
112 
113   case SMOOTH:
114     retimage= smooth(image, 1, verbose);
115     break;
116 
117   case TITLE:
118     if (image->title)
119       lfree((byte *)image->title);
120     image->title= dupString(option->info.title);
121     break;
122 
123   case ZOOM:
124     retimage= zoom(image, option->info.zoom.x, option->info.zoom.y, verbose);
125     break;
126 
127   default:
128     /* Nothing to do */
129     break;
130   }
131   return(retimage);
132 }
133 
134 /* process a list of options on an image
135  */
processImage(image,global_options,image_options)136 static Image *processImage(image, global_options, image_options)
137      Image *image;
138      OptionSet *global_options;
139      OptionSet *image_options;
140 { Option       *opt;
141   Image        *tmpimage;
142   unsigned int  verbose;
143 
144   verbose= (getOption(global_options, VERBOSE) != NULL);
145 
146   /* go through the global options and process them
147    */
148   for (opt= global_options->options; opt; opt= opt->next) {
149 
150     /* if option already exists locally for this image, ignore it
151      */
152     if (getOption(image_options, opt->type))
153       continue;
154     tmpimage= doProcessOnImage(image, opt, verbose);
155     if (tmpimage != image) {
156       freeImage(image);
157       image= tmpimage;
158     }
159   }
160 
161   /* go through local options
162    */
163   for (opt= image_options->options; opt; opt= opt->next) {
164     tmpimage= doProcessOnImage(image, opt, verbose);
165     if (tmpimage != image) {
166       freeImage(image);
167       image= tmpimage;
168     }
169   }
170   return(image);
171 }
172 
173 /* the real thing
174  */
175 
main(argc,argv)176 int main(argc, argv)
177      int argc;
178      char *argv[];
179 { Option *opt;
180   char         *dname;
181   Image        *dispimage;      /* image that will be sent to the display */
182   Image        *newimage;       /* new image we're loading */
183   Image        *tmpimage;
184   Display      *disp;           /* display we're sending to */
185   int           scrn;           /* screen we're sending to */
186   char         *border;         /* name of border color */
187   XColor        xcolor;         /* color for border option */
188   OptionSet    *global_options; /* set of global options */
189   OptionSet    *image_options;  /* set of image options */
190   OptionSet    *optset, *tmpset;
191   Option       *dump;
192   unsigned int  fullscreen;
193   unsigned int  onroot;
194   unsigned int  verbose;
195   unsigned int  winwidth, winheight; /* geometry of image */
196   unsigned int  shrinktofit;
197 
198   /* set up internal error handlers
199    */
200 
201   signal(SIGSEGV, internalError);
202 #ifdef	SIGBUS
203   signal(SIGBUS, internalError);
204 #endif
205   signal(SIGFPE, internalError);
206   signal(SIGILL, internalError);
207 #if defined(_AIX) && defined(_IBMR2)
208   /* the RS/6000 (AIX 3.1) has a new signal, SIGDANGER, which you get
209    * when memory is exhausted.  since malloc() can overcommit, it's a good
210    * idea to trap this one.
211    */
212   signal(SIGDANGER, memoryExhausted);
213 #endif
214 
215   ProgramName= argv[0];
216   if (argc < 2)
217     usage();
218 
219   /* defaults and other initial settings.  some of these depend on what
220    * our name was when invoked.
221    */
222 
223   loadPathsAndExts();
224 
225   processOptions(argc, argv, &global_options, &image_options);
226 
227   verbose= (getOption(global_options, VERBOSE) != NULL);
228 
229   /* if no images are specified and we're not setting the default root,
230    * this invocation is a no-op
231    */
232   if ((image_options->next == NULL) &&
233       (getOption(image_options, NAME) == NULL) &&
234       (getOption(global_options, DEFAULT) == NULL)) {
235     fprintf(stderr, "%s: No images were specified.\n", argv[0]);
236     usageHelp();
237     /* NOTREACHED */
238   }
239 
240   if (getOption(global_options, IDENTIFY)) {
241     for (optset= image_options; optset; optset= optset->next) {
242       if ((opt= getOption(optset, NAME)))
243 	identifyImage(opt->info.name);
244     }
245     exit(0);
246   }
247 
248   /* start talking to the display
249    */
250 
251   opt= getOption(global_options, DISPLAY);
252   dname= (opt ? opt->info.display : NULL);
253   if (! (Disp= disp= XOpenDisplay(dname))) {
254     printf("%s: Cannot open display\n", XDisplayName(dname));
255     exit(1);
256   }
257   Scrn= scrn= DefaultScreen(disp);
258   XSetErrorHandler(errorHandler);
259 
260   /* background ourselves if the user asked us to
261    */
262 
263 #ifndef NO_FORK
264   if (getOption(global_options, FORK))
265     switch(fork()) {
266     case -1:
267       perror("fork");
268       /* FALLTHRU */
269     case 0:
270       break;
271     default:
272       exit(0);
273     }
274 #endif /* !NO_FORK */
275 
276   dispimage= NULL;
277 
278   onroot= (getOption(global_options, ONROOT) != NULL);
279   fullscreen= (getOption(global_options, FULLSCREEN) != NULL);
280   shrinktofit= (getOption(global_options, SHRINKTOFIT) != NULL);
281   if ((opt= getOption(global_options, GEOMETRY))) {
282     winwidth= opt->info.geometry.w;
283     winheight= opt->info.geometry.h;
284   }
285   else {
286     winwidth= 0;
287     winheight= 0;
288   }
289 
290   /* find out if we're supposed to dump this silly thing
291    */
292   dump= getOption(global_options, DUMP);
293 
294   if (!getOption(global_options, DEFAULT) &&
295       (dump || onroot) && (winwidth || winheight ||
296 			   getOption(image_options, CENTER) ||
297 			   getOption(image_options, AT) ||
298 			   fullscreen)) {
299     if (!winwidth)
300       winwidth= DisplayWidth(disp, scrn);
301     if (!winheight)
302       winheight= DisplayHeight(disp, scrn);
303     opt= getOption(global_options, BORDER);
304     border= (opt ? opt->info.border : NULL);
305     if (border)
306       XParseColor(disp, DefaultColormap(disp, scrn), border, &xcolor);
307     else
308       xcolor.red= xcolor.green= xcolor.blue = 65535;
309     if (DefaultDepth(disp, scrn) == 1) {
310       dispimage= newBitImage(winwidth, winheight);
311       *(dispimage->rgb.red)= xcolor.red;
312       *(dispimage->rgb.green)= xcolor.green;
313       *(dispimage->rgb.blue)= xcolor.blue;
314       if (xcolor.red || xcolor.blue || xcolor.green) {
315 	*(dispimage->rgb.red + 1)= 0;
316 	*(dispimage->rgb.green + 1)= 0;
317 	*(dispimage->rgb.blue + 1)= 0;
318       }
319       else {
320 	*(dispimage->rgb.red + 1)= 65535;
321 	*(dispimage->rgb.green + 1)= 65535;
322 	*(dispimage->rgb.blue + 1)= 65535;
323       }
324       fill(dispimage, 0, 0, winwidth, winheight, 0);
325     }
326     else {
327       dispimage= newTrueImage(winwidth, winheight);
328       dispimage->rgb.used= 1;
329       fill(dispimage, 0, 0, winwidth, winheight,
330 	   RGB_TO_TRUE(xcolor.red, xcolor.green, xcolor.blue));
331     }
332     dispimage->title= dupString("Root Image");
333   }
334 
335   /* load in each named image
336    */
337 
338   for (optset= image_options; optset; optset= optset->next) {
339   get_another_image:
340 
341     /* handle -default option.  this creates a base image using the
342      * default tile weave.
343      */
344     if (getOption(image_options, DEFAULT)) {
345       newimage= newBitImage(root_weave_width, root_weave_height);
346       bcopy(root_weave_bits, newimage->data,
347 	    ((root_weave_width / 8) + (root_weave_width % 8 ? 1 : 0)) *
348 	    root_weave_height);
349     }
350     else if (! (opt= getOption(optset, NAME))) {
351 
352       /* this gets post-processing accomplished for -dump and -onroot.
353        */
354       if (dispimage)
355 	dispimage= processImage(dispimage, global_options, optset);
356       continue;
357     }
358     else if (! (newimage= loadImage(global_options, optset, opt->info.name, verbose)))
359       continue;
360 
361     /* retitle the image if we were asked to
362      */
363     if ((opt= getOption(optset, TITLE))) {
364       if (newimage->title)
365 	lfree((byte *)newimage->title);
366       newimage->title= dupString(opt->info.title);
367     }
368 
369     /* if this is the first image and we're putting it on the root window
370      * in fullscreen mode, set the zoom factors and
371      * location to something reasonable.
372      */
373 
374     if ((optset == image_options) && onroot && fullscreen &&
375 	!getOption(optset, ZOOM) && !getOption(optset, AT) &&
376 	!getOption(optset, CENTER)) {
377 
378       opt= newOption(ZOOM);
379       if ((newimage->width > DisplayWidth(disp, scrn)) ||
380 	  (newimage->height > DisplayHeight(disp, scrn))) {
381 	opt->info.zoom.x= opt->info.zoom.y=
382 	  ((int)newimage->width - DisplayWidth(disp, scrn) >
383 	   (int)newimage->height - DisplayHeight(disp, scrn) ?
384 	   (float)DisplayWidth(disp, scrn) / (float)newimage->width * 100.0 :
385 	   (float)DisplayHeight(disp, scrn) / (float)newimage->height * 100.0);
386       }
387       else {
388 	opt->info.zoom.x= opt->info.zoom.y=
389 	  (DisplayWidth(disp, scrn) - newimage->width <
390 	   DisplayHeight(disp, scrn) - newimage->height ?
391 	   (float)DisplayWidth(disp, scrn) / (float)newimage->width * 100.0 :
392 	   (float)DisplayHeight(disp, scrn) / (float)newimage->height * 100.0);
393       }
394       addOption(optset, opt);
395       opt= newOption(CENTER);
396       addOption(optset, opt);
397     }
398 
399     if ((optset == image_options) && shrinktofit && !onroot &&
400 	!getOption(optset, ZOOM)) {
401 
402       opt= newOption(ZOOM);
403 
404       opt->info.zoom.x= opt->info.zoom.y=
405 	(newimage->width - (DisplayWidth(disp, scrn) * 0.9) >
406 	 newimage->height - (DisplayHeight(disp, scrn) * 0.9) ?
407 	 ((float)DisplayWidth(disp, scrn) * 0.9)
408 	 / (float)newimage->width * 100.0 :
409 	 ((float)DisplayHeight(disp, scrn) * 0.9)
410 	 / (float)newimage->height * 100.0);
411       if ((opt->info.zoom.x > 100) || (opt->info.zoom.y > 100))
412         opt->info.zoom.x=opt->info.zoom.y=100;
413 
414       addOption(optset, opt);
415     }
416 
417     newimage= processImage(newimage, global_options, optset);
418 
419     /* handle -center
420      */
421     if (dispimage && getOption(optset, CENTER)) {
422       tmpimage= merge(dispimage, newimage,
423 		      (int)(dispimage->width - newimage->width) / 2,
424 		      (int)(dispimage->height - newimage->height) / 2,
425 		      verbose);
426       if (dispimage != tmpimage) {
427 	freeImage(dispimage);
428 	dispimage= tmpimage;
429       }
430     }
431 
432     /* merge onto previous image
433      */
434     else if (dispimage) {
435       if (! dispimage->title)
436 	dispimage->title= dupString(newimage->title);
437 
438       /* handle -at
439        */
440       if ((opt= getOption(optset, AT)))
441 	tmpimage= merge(dispimage, newimage,
442 			opt->info.at.x, opt->info.at.y, verbose);
443       else
444 	tmpimage= merge(dispimage, newimage, 0, 0, verbose);
445       if (dispimage != tmpimage) {
446 	freeImage(dispimage);
447 	dispimage= tmpimage;
448       }
449       freeImage(newimage);
450     }
451     else
452       dispimage= newimage;
453 
454     /* if the user asked for tiling we tile the image now
455      */
456     if (getOption(optset, TILE)) {
457       if (!winwidth)
458 	winwidth= DisplayWidth(disp, scrn);
459       if (!winheight)
460 	winheight= DisplayHeight(disp, scrn);
461       dispimage= tile(newimage, 0, 0, winwidth, winheight, verbose);
462     }
463 
464     /* if next image is to be merged onto this one, read it
465      */
466     if (dump || onroot || (getOption(optset->next, MERGE)))
467       continue;
468 
469   redisplay_in_window:
470     switch(imageInWindow(disp, scrn, dispimage, global_options,
471 			 optset, argc, argv, verbose)) {
472     case '\0': /* window got nuked by someone */
473       XCloseDisplay(disp);
474       exit(1);
475     case '\003':
476     case 'q':  /* user quit */
477       cleanUpWindow(disp);
478       XCloseDisplay(disp);
479       exit(0);
480     case ' ':
481     case 'n':  /* next image */
482       if ((opt= getOption(optset->next, GOTO))) {
483 	char *tag= opt->info.go_to;
484 
485 	for (tmpset= image_options; tmpset; tmpset= tmpset->next) {
486 	  if ((opt= getOption(tmpset, NAME)) &&
487 	      !strcmp(tag, opt->info.name)) {
488 	    optset= tmpset;
489 	    freeImage(dispimage);
490 	    dispimage= NULL;
491 	    goto get_another_image; /* ick */
492 	  }
493 	}
494 	fprintf(stderr, "Target for -goto %s was not found\n", tag);
495       }
496       break;
497     case 'p':  /* previous image */
498       for (tmpset= image_options; tmpset && (tmpset->next != optset);
499 	   tmpset= tmpset->next)
500 	/* EMPTY */
501 	;
502       if (!tmpset)
503 	goto redisplay_in_window; /* ick */
504       optset= tmpset;
505       freeImage(dispimage);
506       dispimage= NULL;
507       goto get_another_image; /* ick */
508     case '<':
509       if ((opt = getOption(optset,ZOOM)) == NULL) {
510 	opt= newOption(ZOOM);
511 	opt->info.zoom.x= opt->info.zoom.y= 50.0;
512 	addOption(optset, opt);
513       } else {
514 	opt->info.zoom.x= opt->info.zoom.x ? opt->info.zoom.x * 0.5 : 50;
515 	opt->info.zoom.y= opt->info.zoom.y ? opt->info.zoom.y * 0.5 : 50;
516       }
517       tmpimage= dispimage;
518       dispimage=
519 	zoom(dispimage, 50, 50,
520 	     (getOption(global_options, VERBOSE) != NULL));
521       if (tmpimage != dispimage)
522 	free(tmpimage);
523       goto redisplay_in_window; /* ick */
524     case '>':
525       if ((opt = getOption(optset,ZOOM)) == NULL) {
526 	opt= newOption(ZOOM);
527 	opt->info.zoom.x= opt->info.zoom.y= 200.0;
528 	addOption(optset, opt);
529       } else {
530 	opt->info.zoom.x= opt->info.zoom.x ? opt->info.zoom.x * 2.0 : 200;
531 	opt->info.zoom.y= opt->info.zoom.y ? opt->info.zoom.y * 2.0 : 200;
532       }
533       tmpimage= dispimage;
534       dispimage=
535 	zoom(dispimage, 200, 200,
536 	     (getOption(global_options, VERBOSE) != NULL));
537       if (tmpimage != dispimage)
538 	free(tmpimage);
539       goto redisplay_in_window; /* ick */
540     }
541     freeImage(dispimage);
542     dispimage= NULL;
543   }
544 
545   /* dump image into a NIFF file rather than displaying
546    */
547   if (dump && dispimage) {
548     for (optset= image_options; optset && optset->next; optset= optset->next)
549       /* EMPTY */
550       ;
551     if ((opt= getOption(optset, NAME))) {
552       if (dispimage->title)
553 	lfree((byte *)dispimage->title);
554       dispimage->title= dupString(opt->info.title);
555     }
556     dumpImage(dispimage, dump->info.dump.type, dump->info.dump.file, verbose);
557     freeImage(dispimage);
558     dispimage= NULL;
559     exit(0);
560   }
561 
562   /* display image on root
563    */
564   if (onroot && dispimage)
565     imageOnRoot(disp, scrn, dispimage, global_options, verbose);
566 
567   /* shut down
568    */
569   XCloseDisplay(disp);
570   exit(0);
571 }
572