1/*
2 * $Id: xtiff.c 276 2010-06-30 12:18:30Z nijtmans $
3 *
4 * xtiff - view a TIFF file in an X window
5 *
6 * Dan Sears
7 * Chris Sears
8 *
9 * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
10 *
11 *                      All Rights Reserved
12 *
13 * Permission to use, copy, modify, and distribute this software and its
14 * documentation for any purpose and without fee is hereby granted,
15 * provided that the above copyright notice appear in all copies and that
16 * both that copyright notice and this permission notice appear in
17 * supporting documentation, and that the name of Digital not be
18 * used in advertising or publicity pertaining to distribution of the
19 * software without specific, written prior permission.
20 *
21 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
22 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
23 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
24 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
25 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
26 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
27 * SOFTWARE.
28 *
29 * Revision 1.0  90/05/07
30 *      Initial release.
31 * Revision 2.0  90/12/20
32 *      Converted to use the Athena Widgets and the Xt Intrinsics.
33 *
34 * Notes:
35 *
36 * According to the TIFF 5.0 Specification, it is possible to have
37 * both a TIFFTAG_COLORMAP and a TIFFTAG_COLORRESPONSECURVE.  This
38 * doesn't make sense since a TIFFTAG_COLORMAP is 16 bits wide and
39 * a TIFFTAG_COLORRESPONSECURVE is tfBitsPerSample bits wide for each
40 * channel.  This is probably a bug in the specification.
41 * In this case, TIFFTAG_COLORRESPONSECURVE is ignored.
42 * This might make sense if TIFFTAG_COLORMAP was 8 bits wide.
43 *
44 * TIFFTAG_COLORMAP is often incorrectly written as ranging from
45 * 0 to 255 rather than from 0 to 65535.  CheckAndCorrectColormap()
46 * takes care of this.
47 *
48 * Only ORIENTATION_TOPLEFT is supported correctly.  This is the
49 * default TIFF and X orientation.  Other orientations will be
50 * displayed incorrectly.
51 *
52 * There is no support for or use of 3/3/2 DirectColor visuals.
53 * TIFFTAG_MINSAMPLEVALUE and TIFFTAG_MAXSAMPLEVALUE are not supported.
54 *
55 * Only TIFFTAG_BITSPERSAMPLE values that are 1, 2, 4 or 8 are supported.
56 */
57#include <math.h>
58#include <stdio.h>
59#include <stdlib.h>
60#include <tiffio.h>
61#include <X11/Xatom.h>
62#include <X11/Intrinsic.h>
63#include <X11/StringDefs.h>
64#include <X11/Xproto.h>
65#include <X11/Shell.h>
66#include <X11/Xaw/Form.h>
67#include <X11/Xaw/List.h>
68#include <X11/Xaw/Label.h>
69#include <X11/cursorfont.h>
70#define XK_MISCELLANY
71#include <X11/keysymdef.h>
72#include "xtifficon.h"
73
74#define TIFF_GAMMA      "2.2"     /* default gamma from the TIFF 5.0 spec */
75#define ROUND(x)        (uint16) ((x) + 0.5)
76#define SCALE(x, s)     (((x) * 65535L) / (s))
77#define MCHECK(m)       if (!m) { fprintf(stderr, "malloc failed\n"); exit(0); }
78#define MIN(a, b)       (((a) < (b)) ? (a) : (b))
79#define MAX(a, b)       (((a) > (b)) ? (a) : (b))
80#define VIEWPORT_WIDTH  700
81#define VIEWPORT_HEIGHT 500
82#define KEY_TRANSLATE   20
83
84#ifdef __STDC__
85#define PP(args)    args
86#else
87#define PP(args)    ()
88#endif
89
90int main PP((int argc, char **argv));
91void OpenTIFFFile PP((void));
92void GetTIFFHeader PP((void));
93void SetNameLabel PP((void));
94void CheckAndCorrectColormap PP((void));
95void SimpleGammaCorrection PP((void));
96void GetVisual PP((void));
97Boolean SearchVisualList PP((int image_depth,
98    int visual_class, Visual **visual));
99void GetTIFFImage PP((void));
100void CreateXImage PP((void));
101XtCallbackProc SelectProc PP((Widget w, caddr_t unused_1, caddr_t unused_2));
102void QuitProc PP((void));
103void NextProc PP((void));
104void PreviousProc PP((void));
105void PageProc PP((int direction));
106void EventProc PP((Widget widget, caddr_t unused, XEvent *event));
107void ResizeProc PP((void));
108int XTiffErrorHandler PP((Display *display, XErrorEvent *error_event));
109void Usage PP((void));
110
111int xtVersion = XtSpecificationRelease;     /* xtiff depends on R4 or higher */
112
113/*
114 * Xt data structures
115 */
116Widget shellWidget, formWidget, listWidget, labelWidget, imageWidget;
117
118enum { ButtonQuit = 0, ButtonPreviousPage = 1, ButtonNextPage = 2 };
119
120String buttonStrings[] = { "Quit", "Previous", "Next" };
121
122static XrmOptionDescRec shellOptions[] = {
123    { "-help", "*help", XrmoptionNoArg, (caddr_t) "True" },
124    { "-gamma", "*gamma", XrmoptionSepArg, NULL },
125    { "-usePixmap", "*usePixmap", XrmoptionSepArg, NULL },
126    { "-viewportWidth", "*viewportWidth", XrmoptionSepArg, NULL },
127    { "-viewportHeight", "*viewportHeight", XrmoptionSepArg, NULL },
128    { "-translate", "*translate", XrmoptionSepArg, NULL },
129    { "-verbose", "*verbose", XrmoptionSepArg, NULL }
130};
131
132typedef struct {
133    Boolean help;
134    float gamma;
135    Boolean usePixmap;
136    uint32 viewportWidth;
137    uint32 viewportHeight;
138    int translate;
139    Boolean verbose;
140} AppData, *AppDataPtr;
141
142AppData appData;
143
144XtResource clientResources[] = {
145    {
146        "help", XtCBoolean, XtRBoolean, sizeof(Boolean),
147        XtOffset(AppDataPtr, help), XtRImmediate, (XtPointer) False
148    }, {
149        "gamma", "Gamma", XtRFloat, sizeof(float),
150        XtOffset(AppDataPtr, gamma), XtRString, (XtPointer) TIFF_GAMMA
151    }, {
152        "usePixmap", "UsePixmap", XtRBoolean, sizeof(Boolean),
153        XtOffset(AppDataPtr, usePixmap), XtRImmediate, (XtPointer) True
154    }, {
155        "viewportWidth", "ViewportWidth", XtRInt, sizeof(int),
156        XtOffset(AppDataPtr, viewportWidth), XtRImmediate,
157        (XtPointer) VIEWPORT_WIDTH
158    }, {
159        "viewportHeight", "ViewportHeight", XtRInt, sizeof(int),
160        XtOffset(AppDataPtr, viewportHeight), XtRImmediate,
161        (XtPointer) VIEWPORT_HEIGHT
162    }, {
163        "translate", "Translate", XtRInt, sizeof(int),
164        XtOffset(AppDataPtr, translate), XtRImmediate, (XtPointer) KEY_TRANSLATE
165    }, {
166        "verbose", "Verbose", XtRBoolean, sizeof(Boolean),
167        XtOffset(AppDataPtr, verbose), XtRImmediate, (XtPointer) True
168    }
169};
170
171Arg formArgs[] = {
172    { XtNresizable, True }
173};
174
175Arg listArgs[] = {
176    { XtNresizable, False },
177    { XtNborderWidth, 0 },
178    { XtNdefaultColumns, 3 },
179    { XtNforceColumns, True },
180    { XtNlist, (int) buttonStrings },
181    { XtNnumberStrings, XtNumber(buttonStrings) },
182    { XtNtop, XtChainTop },
183    { XtNleft, XtChainLeft },
184    { XtNbottom, XtChainTop },
185    { XtNright, XtChainLeft }
186};
187
188Arg labelArgs[] = {
189    { XtNresizable, False },
190    { XtNwidth, 200 },
191    { XtNborderWidth, 0 },
192    { XtNjustify, XtJustifyLeft },
193    { XtNtop, XtChainTop },
194    { XtNleft, XtChainLeft },
195    { XtNbottom, XtChainTop },
196    { XtNright, XtChainLeft }
197};
198
199Arg imageArgs[] = {
200    { XtNresizable, True },
201    { XtNborderWidth, 0 },
202    { XtNtop, XtChainTop },
203    { XtNleft, XtChainLeft },
204    { XtNbottom, XtChainTop },
205    { XtNright, XtChainLeft }
206};
207
208XtActionsRec actionsTable[] = {
209    { "quit", QuitProc },
210    { "next", NextProc },
211    { "previous", PreviousProc },
212    { "notifyresize", ResizeProc }
213};
214
215char translationsTable[] = "<Key>q:      quit() \n \
216                            <Key>Q:      quit() \n \
217                            <Message>WM_PROTOCOLS: quit()\n \
218                            <Key>p:      previous() \n \
219                            <Key>P:      previous() \n \
220                            <Key>n:      next() \n \
221                            <Key>N:      next() \n \
222                            <Configure>: notifyresize()";
223
224/*
225 * X data structures
226 */
227Colormap            xColormap;
228Display *           xDisplay;
229Pixmap              xImagePixmap;
230Visual *            xVisual;
231XImage *            xImage;
232GC                  xWinGc;
233int                 xImageDepth, xScreen, xRedMask, xGreenMask, xBlueMask,
234                    xOffset = 0, yOffset = 0, grabX = -1, grabY = -1;
235unsigned char       basePixel = 0;
236
237/*
238 * TIFF data structures
239 */
240TIFF *              tfFile = NULL;
241uint32              tfImageWidth, tfImageHeight;
242uint16              tfBitsPerSample, tfSamplesPerPixel, tfPlanarConfiguration,
243                    tfPhotometricInterpretation, tfGrayResponseUnit,
244                    tfImageDepth, tfBytesPerRow;
245int                 tfDirectory = 0, tfMultiPage = False;
246double              tfUnitMap, tfGrayResponseUnitMap[] = {
247                        -1, -10, -100, -1000, -10000, -100000
248                    };
249
250/*
251 * display data structures
252 */
253double              *dRed, *dGreen, *dBlue;
254
255/*
256 * shared data structures
257 */
258uint16 *            redMap = NULL, *greenMap = NULL, *blueMap = NULL,
259                    *grayMap = NULL, colormapSize;
260char *             imageMemory;
261char *              fileName;
262
263int
264main(int argc, char **argv)
265{
266    XSetWindowAttributes window_attributes;
267    Widget widget_list[3];
268    Arg args[5];
269
270    setbuf(stdout, NULL); setbuf(stderr, NULL);
271
272    shellWidget = XtInitialize(argv[0], "XTiff", shellOptions,
273        XtNumber(shellOptions), &argc, argv);
274
275    XSetErrorHandler(XTiffErrorHandler);
276
277    XtGetApplicationResources(shellWidget, &appData,
278        (XtResourceList) clientResources, (Cardinal) XtNumber(clientResources),
279        (ArgList) NULL, (Cardinal) 0);
280
281    if ((argc <= 1) || (argc > 2) || appData.help)
282        Usage();
283
284    if (appData.verbose == False) {
285        TIFFSetErrorHandler(0);
286        TIFFSetWarningHandler(0);
287    }
288
289    fileName = argv[1];
290
291    xDisplay = XtDisplay(shellWidget);
292    xScreen = DefaultScreen(xDisplay);
293
294    OpenTIFFFile();
295    GetTIFFHeader();
296    SimpleGammaCorrection();
297    GetVisual();
298    GetTIFFImage();
299
300    /*
301     * Send visual, colormap, depth and iconPixmap to shellWidget.
302     * Sending the visual to the shell is only possible with the advent of R4.
303     */
304    XtSetArg(args[0], XtNvisual, xVisual);
305    XtSetArg(args[1], XtNcolormap, xColormap);
306    XtSetArg(args[2], XtNdepth,
307        xImageDepth == 1 ? DefaultDepth(xDisplay, xScreen) : xImageDepth);
308    XtSetArg(args[3], XtNiconPixmap,
309        XCreateBitmapFromData(xDisplay, RootWindow(xDisplay, xScreen),
310            xtifficon_bits, xtifficon_width, xtifficon_height));
311    XtSetArg(args[4], XtNallowShellResize, True);
312    XtSetValues(shellWidget, args, 5);
313
314    /*
315     * widget instance hierarchy
316     */
317    formWidget = XtCreateManagedWidget("form", formWidgetClass,
318        shellWidget, formArgs, XtNumber(formArgs));
319
320        widget_list[0] = listWidget = XtCreateWidget("list",
321            listWidgetClass, formWidget, listArgs, XtNumber(listArgs));
322
323        widget_list[1] = labelWidget = XtCreateWidget("label",
324            labelWidgetClass, formWidget, labelArgs, XtNumber(labelArgs));
325
326        widget_list[2] = imageWidget = XtCreateWidget("image",
327            widgetClass, formWidget, imageArgs, XtNumber(imageArgs));
328
329    XtManageChildren(widget_list, XtNumber(widget_list));
330
331    /*
332     * initial widget sizes - for small images let xtiff size itself
333     */
334    if (tfImageWidth >= appData.viewportWidth) {
335        XtSetArg(args[0], XtNwidth, appData.viewportWidth);
336        XtSetValues(shellWidget, args, 1);
337    }
338    if (tfImageHeight >= appData.viewportHeight) {
339        XtSetArg(args[0], XtNheight, appData.viewportHeight);
340        XtSetValues(shellWidget, args, 1);
341    }
342
343    XtSetArg(args[0], XtNwidth, tfImageWidth);
344    XtSetArg(args[1], XtNheight, tfImageHeight);
345    XtSetValues(imageWidget, args, 2);
346
347    /*
348     * formWidget uses these constraints but they are stored in the children.
349     */
350    XtSetArg(args[0], XtNfromVert, listWidget);
351    XtSetValues(imageWidget, args, 1);
352    XtSetArg(args[0], XtNfromHoriz, listWidget);
353    XtSetValues(labelWidget, args, 1);
354
355    SetNameLabel();
356
357    XtAddCallback(listWidget, XtNcallback, (XtCallbackProc) SelectProc,
358        (XtPointer) NULL);
359
360    XtAddActions(actionsTable, XtNumber(actionsTable));
361    XtSetArg(args[0], XtNtranslations,
362        XtParseTranslationTable(translationsTable));
363    XtSetValues(formWidget, &args[0], 1);
364    XtSetValues(imageWidget, &args[0], 1);
365
366    /*
367     * This is intended to be a little faster than going through
368     * the translation manager.
369     */
370    XtAddEventHandler(imageWidget, ExposureMask | ButtonPressMask
371        | ButtonReleaseMask | Button1MotionMask | KeyPressMask,
372        False, EventProc, NULL);
373
374    XtRealizeWidget(shellWidget);
375
376    window_attributes.cursor = XCreateFontCursor(xDisplay, XC_fleur);
377    XChangeWindowAttributes(xDisplay, XtWindow(imageWidget),
378        CWCursor, &window_attributes);
379
380    CreateXImage();
381
382    XtMainLoop();
383
384    return 0;
385}
386
387void
388OpenTIFFFile()
389{
390    if (tfFile != NULL)
391        TIFFClose(tfFile);
392
393    if ((tfFile = TIFFOpen(fileName, "r")) == NULL) {
394	fprintf(appData.verbose ? stderr : stdout,
395	    "xtiff: can't open %s as a TIFF file\n", fileName);
396        exit(0);
397    }
398
399    tfMultiPage = (TIFFLastDirectory(tfFile) ? False : True);
400}
401
402void
403GetTIFFHeader()
404{
405    register int i;
406
407    if (!TIFFSetDirectory(tfFile, tfDirectory)) {
408        fprintf(stderr, "xtiff: can't seek to directory %d in %s\n",
409            tfDirectory, fileName);
410        exit(0);
411    }
412
413    TIFFGetField(tfFile, TIFFTAG_IMAGEWIDTH, &tfImageWidth);
414    TIFFGetField(tfFile, TIFFTAG_IMAGELENGTH, &tfImageHeight);
415
416    /*
417     * If the following tags aren't present then use the TIFF defaults.
418     */
419    TIFFGetFieldDefaulted(tfFile, TIFFTAG_BITSPERSAMPLE, &tfBitsPerSample);
420    TIFFGetFieldDefaulted(tfFile, TIFFTAG_SAMPLESPERPIXEL, &tfSamplesPerPixel);
421    TIFFGetFieldDefaulted(tfFile, TIFFTAG_PLANARCONFIG, &tfPlanarConfiguration);
422    TIFFGetFieldDefaulted(tfFile, TIFFTAG_GRAYRESPONSEUNIT, &tfGrayResponseUnit);
423
424    tfUnitMap = tfGrayResponseUnitMap[tfGrayResponseUnit];
425    colormapSize = 1 << tfBitsPerSample;
426    tfImageDepth = tfBitsPerSample * tfSamplesPerPixel;
427
428    dRed = (double *) malloc(colormapSize * sizeof(double));
429    dGreen = (double *) malloc(colormapSize * sizeof(double));
430    dBlue = (double *) malloc(colormapSize * sizeof(double));
431    MCHECK(dRed); MCHECK(dGreen); MCHECK(dBlue);
432
433    /*
434     * If TIFFTAG_PHOTOMETRIC is not present then assign a reasonable default.
435     * The TIFF 5.0 specification doesn't give a default.
436     */
437    if (!TIFFGetField(tfFile, TIFFTAG_PHOTOMETRIC,
438            &tfPhotometricInterpretation)) {
439        if (tfSamplesPerPixel != 1)
440            tfPhotometricInterpretation = PHOTOMETRIC_RGB;
441        else if (tfBitsPerSample == 1)
442            tfPhotometricInterpretation = PHOTOMETRIC_MINISBLACK;
443        else if (TIFFGetField(tfFile, TIFFTAG_COLORMAP,
444                &redMap, &greenMap, &blueMap)) {
445            tfPhotometricInterpretation = PHOTOMETRIC_PALETTE;
446            redMap = greenMap = blueMap = NULL;
447        } else
448            tfPhotometricInterpretation = PHOTOMETRIC_MINISBLACK;
449    }
450
451    /*
452     * Given TIFFTAG_PHOTOMETRIC extract or create the response curves.
453     */
454    switch (tfPhotometricInterpretation) {
455    case PHOTOMETRIC_RGB:
456	redMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
457	greenMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
458	blueMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
459	MCHECK(redMap); MCHECK(greenMap); MCHECK(blueMap);
460	for (i = 0; i < colormapSize; i++)
461	    dRed[i] = dGreen[i] = dBlue[i]
462		= (double) SCALE(i, colormapSize - 1);
463        break;
464    case PHOTOMETRIC_PALETTE:
465        if (!TIFFGetField(tfFile, TIFFTAG_COLORMAP,
466                &redMap, &greenMap, &blueMap)) {
467            redMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
468            greenMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
469            blueMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
470            MCHECK(redMap); MCHECK(greenMap); MCHECK(blueMap);
471            for (i = 0; i < colormapSize; i++)
472                dRed[i] = dGreen[i] = dBlue[i]
473                    = (double) SCALE(i, colormapSize - 1);
474        } else {
475            CheckAndCorrectColormap();
476            for (i = 0; i < colormapSize; i++) {
477                dRed[i] = (double) redMap[i];
478                dGreen[i] = (double) greenMap[i];
479                dBlue[i] = (double) blueMap[i];
480            }
481        }
482        break;
483    case PHOTOMETRIC_MINISWHITE:
484        redMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
485        greenMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
486        blueMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
487        MCHECK(redMap); MCHECK(greenMap); MCHECK(blueMap);
488	for (i = 0; i < colormapSize; i++)
489	    dRed[i] = dGreen[i] = dBlue[i] = (double)
490		 SCALE(colormapSize-1-i, colormapSize-1);
491        break;
492    case PHOTOMETRIC_MINISBLACK:
493        redMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
494        greenMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
495        blueMap = (uint16 *) malloc(colormapSize * sizeof(uint16));
496        MCHECK(redMap); MCHECK(greenMap); MCHECK(blueMap);
497	for (i = 0; i < colormapSize; i++)
498	    dRed[i] = dGreen[i] = dBlue[i] = (double) SCALE(i, colormapSize-1);
499        break;
500    default:
501        fprintf(stderr,
502            "xtiff: can't display photometric interpretation type %d\n",
503            tfPhotometricInterpretation);
504        exit(0);
505    }
506}
507
508void
509SetNameLabel()
510{
511    char buffer[BUFSIZ];
512    Arg args[1];
513
514    if (tfMultiPage)
515        sprintf(buffer, "%s - page %d", fileName, tfDirectory);
516    else
517        strcpy(buffer, fileName);
518    XtSetArg(args[0], XtNlabel, buffer);
519    XtSetValues(labelWidget, args, 1);
520}
521
522/*
523 * Many programs get TIFF colormaps wrong.  They use 8-bit colormaps instead of
524 * 16-bit colormaps.  This function is a heuristic to detect and correct this.
525 */
526void
527CheckAndCorrectColormap()
528{
529    register int i;
530
531    for (i = 0; i < colormapSize; i++)
532        if ((redMap[i] > 255) || (greenMap[i] > 255) || (blueMap[i] > 255))
533            return;
534
535    for (i = 0; i < colormapSize; i++) {
536        redMap[i] = SCALE(redMap[i], 255);
537        greenMap[i] = SCALE(greenMap[i], 255);
538        blueMap[i] = SCALE(blueMap[i], 255);
539    }
540    TIFFWarning(fileName, "Assuming 8-bit colormap");
541}
542
543void
544SimpleGammaCorrection()
545{
546    register int i;
547    register double i_gamma = 1.0 / appData.gamma;
548
549    for (i = 0; i < colormapSize; i++) {
550        if (((tfPhotometricInterpretation == PHOTOMETRIC_MINISWHITE)
551            && (i == colormapSize - 1))
552            || ((tfPhotometricInterpretation == PHOTOMETRIC_MINISBLACK)
553            && (i == 0)))
554            redMap[i] = greenMap[i] = blueMap[i] = 0;
555        else {
556            redMap[i] = ROUND((pow(dRed[i] / 65535.0, i_gamma) * 65535.0));
557            greenMap[i] = ROUND((pow(dGreen[i] / 65535.0, i_gamma) * 65535.0));
558            blueMap[i] = ROUND((pow(dBlue[i] / 65535.0, i_gamma) * 65535.0));
559        }
560    }
561
562    free(dRed); free(dGreen); free(dBlue);
563}
564
565static char* classNames[] = {
566    "StaticGray",
567    "GrayScale",
568    "StaticColor",
569    "PseudoColor",
570    "TrueColor",
571    "DirectColor"
572};
573
574/*
575 * Current limitation: the visual is set initially by the first file.
576 * It cannot be changed.
577 */
578void
579GetVisual()
580{
581    XColor *colors = NULL;
582    unsigned long *pixels = NULL;
583    unsigned long i;
584
585    switch (tfImageDepth) {
586    /*
587     * X really wants a 32-bit image with the fourth channel unused,
588     * but the visual structure thinks it's 24-bit.  bitmap_unit is 32.
589     */
590    case 32:
591    case 24:
592        if (SearchVisualList(24, DirectColor, &xVisual) == False) {
593            fprintf(stderr, "xtiff: 24-bit DirectColor visual not available\n");
594            exit(0);
595        }
596
597        colors = (XColor *) malloc(3 * colormapSize * sizeof(XColor));
598        MCHECK(colors);
599
600        for (i = 0; i < colormapSize; i++) {
601            colors[i].pixel = (i << 16) + (i << 8) + i;
602            colors[i].red = redMap[i];
603            colors[i].green = greenMap[i];
604            colors[i].blue = blueMap[i];
605            colors[i].flags = DoRed | DoGreen | DoBlue;
606        }
607
608        xColormap = XCreateColormap(xDisplay, RootWindow(xDisplay, xScreen),
609            xVisual, AllocAll);
610        XStoreColors(xDisplay, xColormap, colors, colormapSize);
611        break;
612    case 8:
613    case 4:
614    case 2:
615        /*
616         * We assume that systems with 24-bit visuals also have 8-bit visuals.
617         * We don't promote from 8-bit PseudoColor to 24/32 bit DirectColor.
618         */
619        switch (tfPhotometricInterpretation) {
620        case PHOTOMETRIC_MINISWHITE:
621        case PHOTOMETRIC_MINISBLACK:
622            if (SearchVisualList((int) tfImageDepth, GrayScale, &xVisual) == True)
623                break;
624        case PHOTOMETRIC_PALETTE:
625            if (SearchVisualList((int) tfImageDepth, PseudoColor, &xVisual) == True)
626                break;
627        default:
628            fprintf(stderr, "xtiff: Unsupported TIFF/X configuration\n");
629            exit(0);
630        }
631
632        colors = (XColor *) malloc(colormapSize * sizeof(XColor));
633        MCHECK(colors);
634
635        for (i = 0; i < colormapSize; i++) {
636            colors[i].pixel = i;
637            colors[i].red = redMap[i];
638            colors[i].green = greenMap[i];
639            colors[i].blue = blueMap[i];
640            colors[i].flags = DoRed | DoGreen | DoBlue;
641        }
642
643        /*
644         * xtiff's colormap allocation is private.  It does not attempt
645         * to detect whether any existing colormap entries are suitable
646         * for its use.  This will cause colormap flashing.  Furthermore,
647         * background and foreground are taken from the environment.
648         * For example, the foreground color may be red when the visual
649         * is GrayScale.  If the colormap is completely populated,
650         * Xt will not be able to allocate fg and bg.
651         */
652        if (tfImageDepth == 8)
653            xColormap = XCreateColormap(xDisplay, RootWindow(xDisplay, xScreen),
654                xVisual, AllocAll);
655        else {
656            xColormap = XCreateColormap(xDisplay, RootWindow(xDisplay, xScreen),
657                xVisual, AllocNone);
658            pixels = (unsigned long *)
659                malloc(colormapSize * sizeof(unsigned long));
660            MCHECK(pixels);
661            (void) XAllocColorCells(xDisplay, xColormap, True,
662                NULL, 0, pixels, colormapSize);
663            basePixel = (unsigned char) pixels[0];
664            free(pixels);
665        }
666        XStoreColors(xDisplay, xColormap, colors, colormapSize);
667        break;
668    case 1:
669        xImageDepth = 1;
670        xVisual = DefaultVisual(xDisplay, xScreen);
671        xColormap = DefaultColormap(xDisplay, xScreen);
672        break;
673    default:
674        fprintf(stderr, "xtiff: unsupported image depth %d\n", tfImageDepth);
675        exit(0);
676    }
677
678    if (appData.verbose == True)
679	fprintf(stderr, "%s: Using %d-bit %s visual.\n",
680	    fileName, xImageDepth, classNames[xVisual->class]);
681
682    if (colors != NULL)
683        free(colors);
684    if (grayMap != NULL)
685        free(grayMap);
686    if (redMap != NULL)
687        free(redMap);
688    if (greenMap != NULL)
689        free(greenMap);
690    if (blueMap != NULL)
691        free(blueMap);
692
693    colors = NULL; grayMap = redMap = greenMap = blueMap = NULL;
694}
695
696/*
697 * Search for an appropriate visual.  Promote where necessary.
698 * Check to make sure that ENOUGH colormap entries are writeable.
699 * basePixel was determined when XAllocColorCells() contiguously
700 * allocated enough entries.  basePixel is used below in GetTIFFImage.
701 */
702Boolean
703SearchVisualList(image_depth, visual_class, visual)
704    int image_depth, visual_class;
705    Visual **visual;
706{
707    XVisualInfo template_visual, *visual_list, *vl;
708    int i, n_visuals;
709
710    template_visual.screen = xScreen;
711    vl = visual_list = XGetVisualInfo(xDisplay, VisualScreenMask,
712        &template_visual, &n_visuals);
713
714    if (n_visuals == 0) {
715        fprintf(stderr, "xtiff: visual list not available\n");
716        exit(0);
717    }
718
719    for (i = 0; i < n_visuals; vl++, i++) {
720        if ((vl->class == visual_class) && (vl->depth >= image_depth)
721            && (vl->visual->map_entries >= (1 << vl->depth))) {
722            *visual = vl->visual;
723            xImageDepth = vl->depth;
724            xRedMask = vl->red_mask;
725            xGreenMask = vl->green_mask;
726            xBlueMask = vl->blue_mask;
727            XFree((char *) visual_list);
728            return True;
729        }
730    }
731
732    XFree((char *) visual_list);
733    return False;
734}
735
736void
737GetTIFFImage()
738{
739    int pixel_map[3], red_shift, green_shift, blue_shift;
740    char *scan_line, *output_p, *input_p;
741    uint32 i, j;
742    uint16 s;
743
744    scan_line = (char *) malloc(tfBytesPerRow = TIFFScanlineSize(tfFile));
745    MCHECK(scan_line);
746
747    if ((tfImageDepth == 32) || (tfImageDepth == 24)) {
748        output_p = imageMemory = (char *)
749            malloc(tfImageWidth * tfImageHeight * 4);
750        MCHECK(imageMemory);
751
752        /*
753         * Handle different color masks for different frame buffers.
754         */
755        if (ImageByteOrder(xDisplay) == LSBFirst) { /* DECstation 5000 */
756            red_shift = pixel_map[0] = xRedMask == 0xFF000000 ? 3
757                : (xRedMask == 0xFF0000 ? 2 : (xRedMask == 0xFF00 ? 1 : 0));
758            green_shift = pixel_map[1] = xGreenMask == 0xFF000000 ? 3
759                : (xGreenMask == 0xFF0000 ? 2 : (xGreenMask == 0xFF00 ? 1 : 0));
760            blue_shift = pixel_map[2] = xBlueMask == 0xFF000000 ? 3
761                : (xBlueMask == 0xFF0000 ? 2 : (xBlueMask == 0xFF00 ? 1 : 0));
762        } else { /* Ardent */
763            red_shift = pixel_map[0] = xRedMask == 0xFF000000 ? 0
764                : (xRedMask == 0xFF0000 ? 1 : (xRedMask == 0xFF00 ? 2 : 3));
765            green_shift = pixel_map[0] = xGreenMask == 0xFF000000 ? 0
766                : (xGreenMask == 0xFF0000 ? 1 : (xGreenMask == 0xFF00 ? 2 : 3));
767            blue_shift = pixel_map[0] = xBlueMask == 0xFF000000 ? 0
768                : (xBlueMask == 0xFF0000 ? 1 : (xBlueMask == 0xFF00 ? 2 : 3));
769        }
770
771        if (tfPlanarConfiguration == PLANARCONFIG_CONTIG) {
772            for (i = 0; i < tfImageHeight; i++) {
773                if (TIFFReadScanline(tfFile, scan_line, i, 0) < 0)
774                    break;
775                for (input_p = scan_line, j = 0; j < tfImageWidth; j++) {
776                    *(output_p + red_shift) = *input_p++;
777                    *(output_p + green_shift) = *input_p++;
778                    *(output_p + blue_shift) = *input_p++;
779                    output_p += 4;
780                    if (tfSamplesPerPixel == 4) /* skip the fourth channel */
781                        input_p++;
782                }
783            }
784        } else {
785            for (s = 0; s < tfSamplesPerPixel; s++) {
786                if (s == 3)             /* skip the fourth channel */
787                    continue;
788                for (i = 0; i < tfImageHeight; i++) {
789                    if (TIFFReadScanline(tfFile, scan_line, i, s) < 0)
790                        break;
791                    input_p = scan_line;
792                    output_p = imageMemory + (i*tfImageWidth*4) + pixel_map[s];
793                    for (j = 0; j < tfImageWidth; j++, output_p += 4)
794                        *output_p = *input_p++;
795                }
796            }
797        }
798    } else {
799        if (xImageDepth == tfImageDepth) {
800            output_p = imageMemory = (char *)
801                malloc(tfBytesPerRow * tfImageHeight);
802            MCHECK(imageMemory);
803
804            for (i = 0; i < tfImageHeight; i++, output_p += tfBytesPerRow)
805                if (TIFFReadScanline(tfFile, output_p, i, 0) < 0)
806                    break;
807        } else if ((xImageDepth == 8) && (tfImageDepth == 4)) {
808            output_p = imageMemory = (char *)
809                malloc(tfBytesPerRow * 2 * tfImageHeight + 2);
810            MCHECK(imageMemory);
811
812            /*
813             * If a scanline is of odd size the inner loop below will overshoot.
814             * This is handled very simply by recalculating the start point at
815             * each scanline and padding imageMemory a little at the end.
816             */
817            for (i = 0; i < tfImageHeight; i++) {
818                if (TIFFReadScanline(tfFile, scan_line, i, 0) < 0)
819                    break;
820                output_p = &imageMemory[i * tfImageWidth];
821                input_p = scan_line;
822                for (j = 0; j < tfImageWidth; j += 2, input_p++) {
823                    *output_p++ = (*input_p >> 4) + basePixel;
824                    *output_p++ = (*input_p & 0xf) + basePixel;
825                }
826            }
827        } else if ((xImageDepth == 8) && (tfImageDepth == 2)) {
828            output_p = imageMemory = (char *)
829                malloc(tfBytesPerRow * 4 * tfImageHeight + 4);
830            MCHECK(imageMemory);
831
832            for (i = 0; i < tfImageHeight; i++) {
833                if (TIFFReadScanline(tfFile, scan_line, i, 0) < 0)
834                    break;
835                output_p = &imageMemory[i * tfImageWidth];
836                input_p = scan_line;
837                for (j = 0; j < tfImageWidth; j += 4, input_p++) {
838                    *output_p++ = (*input_p >> 6) + basePixel;
839                    *output_p++ = ((*input_p >> 4) & 3) + basePixel;
840                    *output_p++ = ((*input_p >> 2) & 3) + basePixel;
841                    *output_p++ = (*input_p & 3) + basePixel;
842                }
843            }
844        } else if ((xImageDepth == 4) && (tfImageDepth == 2)) {
845            output_p = imageMemory = (char *)
846                malloc(tfBytesPerRow * 2 * tfImageHeight + 2);
847            MCHECK(imageMemory);
848
849            for (i = 0; i < tfImageHeight; i++) {
850                if (TIFFReadScanline(tfFile, scan_line, i, 0) < 0)
851                    break;
852                output_p = &imageMemory[i * tfBytesPerRow * 2];
853                input_p = scan_line;
854                for (j = 0; j < tfImageWidth; j += 4, input_p++) {
855                    *output_p++ = (((*input_p>>6) << 4)
856                        | ((*input_p >> 4) & 3)) + basePixel;
857                    *output_p++ = ((((*input_p>>2) & 3) << 4)
858                        | (*input_p & 3)) + basePixel;
859                }
860            }
861        } else {
862            fprintf(stderr,
863                "xtiff: can't handle %d-bit TIFF file on an %d-bit display\n",
864                tfImageDepth, xImageDepth);
865            exit(0);
866        }
867    }
868
869    free(scan_line);
870}
871
872void
873CreateXImage()
874{
875    XGCValues gc_values;
876    GC bitmap_gc;
877
878    xOffset = yOffset = 0;
879    grabX = grabY = -1;
880
881    xImage = XCreateImage(xDisplay, xVisual, xImageDepth,
882        xImageDepth == 1 ? XYBitmap : ZPixmap, /* offset */ 0,
883        (char *) imageMemory, tfImageWidth, tfImageHeight,
884        /* bitmap_pad */ 8, /* bytes_per_line */ 0);
885
886    /*
887     * libtiff converts LSB data into MSB but doesn't change the FillOrder tag.
888     */
889    if (xImageDepth == 1)
890        xImage->bitmap_bit_order = MSBFirst;
891    if (xImageDepth <= 8)
892        xImage->byte_order = MSBFirst;
893
894    /*
895     * create an appropriate GC
896     */
897    gc_values.function = GXcopy;
898    gc_values.plane_mask = AllPlanes;
899    if (tfPhotometricInterpretation == PHOTOMETRIC_MINISBLACK) {
900        gc_values.foreground = XWhitePixel(xDisplay, xScreen);
901        gc_values.background = XBlackPixel(xDisplay, xScreen);
902    } else {
903        gc_values.foreground = XBlackPixel(xDisplay, xScreen);
904        gc_values.background = XWhitePixel(xDisplay, xScreen);
905    }
906    xWinGc = XCreateGC(xDisplay, XtWindow(shellWidget),
907        GCFunction | GCPlaneMask | GCForeground | GCBackground, &gc_values);
908
909    /*
910     * create the pixmap and load the image
911     */
912    if (appData.usePixmap == True) {
913        xImagePixmap = XCreatePixmap(xDisplay, RootWindow(xDisplay, xScreen),
914            xImage->width, xImage->height, xImageDepth);
915
916        /*
917         * According to the O'Reilly X Protocol Reference Manual, page 53,
918         * "A pixmap depth of one is always supported and listed, but windows
919         * of depth one might not be supported."  Therefore we create a pixmap
920         * of depth one and use XCopyPlane().  This is idiomatic.
921         */
922        if (xImageDepth == 1) {         /* just pass the bits through */
923            gc_values.foreground = 1;   /* foreground describes set bits */
924            gc_values.background = 0;   /* background describes clear bits */
925            bitmap_gc = XCreateGC(xDisplay, xImagePixmap,
926                GCForeground | GCBackground, &gc_values);
927            XPutImage(xDisplay, xImagePixmap, bitmap_gc, xImage,
928                0, 0, 0, 0, xImage->width, xImage->height);
929        } else
930            XPutImage(xDisplay, xImagePixmap, xWinGc, xImage,
931                0, 0, 0, 0, xImage->width, xImage->height);
932        XDestroyImage(xImage);
933        free(imageMemory);
934    }
935}
936
937XtCallbackProc
938SelectProc(w, unused_1, unused_2)
939    Widget w;
940    caddr_t unused_1;
941    caddr_t unused_2;
942{
943    XawListReturnStruct *list_return;
944
945    list_return = XawListShowCurrent(w);
946
947    switch (list_return->list_index) {
948    case ButtonQuit:
949        QuitProc();
950        break;
951    case ButtonPreviousPage:
952        PreviousProc();
953        break;
954    case ButtonNextPage:
955        NextProc();
956        break;
957    default:
958        fprintf(stderr, "error in SelectProc\n");
959        exit(0);
960    }
961    XawListUnhighlight(w);
962}
963
964void
965QuitProc(void)
966{
967    exit(0);
968}
969
970void
971NextProc()
972{
973    PageProc(ButtonNextPage);
974}
975
976void
977PreviousProc()
978{
979    PageProc(ButtonPreviousPage);
980}
981
982void
983PageProc(direction)
984    int direction;
985{
986    XEvent fake_event;
987    Arg args[4];
988
989    switch (direction) {
990    case ButtonPreviousPage:
991        if (tfDirectory > 0)
992            TIFFSetDirectory(tfFile, --tfDirectory);
993        else
994            return;
995        break;
996    case ButtonNextPage:
997        if (TIFFReadDirectory(tfFile) == True)
998            tfDirectory++;
999        else
1000            return;
1001        break;
1002    default:
1003        fprintf(stderr, "error in PageProc\n");
1004        exit(0);
1005    }
1006
1007    xOffset = yOffset = 0;
1008    grabX = grabY = -1;
1009
1010    GetTIFFHeader();
1011    SetNameLabel();
1012    GetTIFFImage();
1013
1014    if (appData.usePixmap == True)
1015        XFreePixmap(xDisplay, xImagePixmap);
1016    else
1017        XDestroyImage(xImage);
1018
1019    CreateXImage();
1020
1021    /*
1022     * Using XtSetValues() to set the widget size causes a resize.
1023     * This resize gets propagated up to the parent shell.
1024     * In order to disable this visually disconcerting effect,
1025     * shell resizing is temporarily disabled.
1026     */
1027    XtSetArg(args[0], XtNallowShellResize, False);
1028    XtSetValues(shellWidget, args, 1);
1029
1030    XtSetArg(args[0], XtNwidth, tfImageWidth);
1031    XtSetArg(args[1], XtNheight, tfImageHeight);
1032    XtSetValues(imageWidget, args, 2);
1033
1034    XtSetArg(args[0], XtNallowShellResize, True);
1035    XtSetValues(shellWidget, args, 1);
1036
1037    XClearWindow(xDisplay, XtWindow(imageWidget));
1038
1039    fake_event.type = Expose;
1040    fake_event.xexpose.x = fake_event.xexpose.y = 0;
1041    fake_event.xexpose.width = tfImageWidth;    /* the window will clip */
1042    fake_event.xexpose.height = tfImageHeight;
1043    EventProc(imageWidget, NULL, &fake_event);
1044}
1045
1046void
1047EventProc(widget, unused, event)
1048    Widget widget;
1049    caddr_t unused;
1050    XEvent *event;
1051{
1052    int ih, iw, ww, wh, sx, sy, w, h, dx, dy;
1053    Dimension w_width, w_height;
1054    XEvent next_event;
1055    Arg args[2];
1056
1057    if (event->type == MappingNotify) {
1058        XRefreshKeyboardMapping((XMappingEvent *) event);
1059        return;
1060    }
1061
1062    if (!XtIsRealized(widget))
1063        return;
1064
1065    if ((event->type == ButtonPress) || (event->type == ButtonRelease))
1066        if (event->xbutton.button != Button1)
1067            return;
1068
1069    iw = tfImageWidth;  /* avoid sign problems */
1070    ih = tfImageHeight;
1071
1072    /*
1073     * The grabX and grabY variables record where the user grabbed the image.
1074     * They also record whether the mouse button is down or not.
1075     */
1076    if (event->type == ButtonPress) {
1077        grabX = event->xbutton.x;
1078        grabY = event->xbutton.y;
1079        return;
1080    }
1081
1082    /*
1083     * imageWidget is a Core widget and doesn't get resized.
1084     * So we calculate the size of its viewport here.
1085     */
1086    XtSetArg(args[0], XtNwidth, &w_width);
1087    XtSetArg(args[1], XtNheight, &w_height);
1088    XtGetValues(shellWidget, args, 2);
1089    ww = w_width;
1090    wh = w_height;
1091    XtGetValues(listWidget, args, 2);
1092    wh -= w_height;
1093
1094    switch (event->type) {
1095    case Expose:
1096        dx = event->xexpose.x;
1097        dy = event->xexpose.y;
1098        sx = dx + xOffset;
1099        sy = dy + yOffset;
1100        w = MIN(event->xexpose.width, iw);
1101        h = MIN(event->xexpose.height, ih);
1102        break;
1103    case KeyPress:
1104        if ((grabX >= 0) || (grabY >= 0)) /* Mouse button is still down */
1105            return;
1106        switch (XLookupKeysym((XKeyEvent *) event, /* KeySyms index */ 0)) {
1107        case XK_Up:
1108            if (ih < wh)    /* Don't scroll if the window fits the image. */
1109                return;
1110            sy = yOffset + appData.translate;
1111            sy = MIN(ih - wh, sy);
1112            if (sy == yOffset)  /* Filter redundant stationary refreshes. */
1113                return;
1114            yOffset = sy;
1115            sx = xOffset;
1116            dx = dy = 0;
1117            w = ww; h = wh;
1118            break;
1119        case XK_Down:
1120            if (ih < wh)
1121                return;
1122            sy = yOffset - appData.translate;
1123            sy = MAX(sy, 0);
1124            if (sy == yOffset)
1125                return;
1126            yOffset = sy;
1127            sx = xOffset;
1128            dx = dy = 0;
1129            w = ww; h = wh;
1130            break;
1131        case XK_Left:
1132            if (iw < ww)
1133                return;
1134            sx = xOffset + appData.translate;
1135            sx = MIN(iw - ww, sx);
1136            if (sx == xOffset)
1137                return;
1138            xOffset = sx;
1139            sy = yOffset;
1140            dx = dy = 0;
1141            w = ww; h = wh;
1142            break;
1143        case XK_Right:
1144            if (iw < ww)
1145                return;
1146            sx = xOffset - appData.translate;
1147            sx = MAX(sx, 0);
1148            if (sx == xOffset)
1149                return;
1150            xOffset = sx;
1151            sy = yOffset;
1152            dx = dy = 0;
1153            w = ww; h = wh;
1154            break;
1155        default:
1156            return;
1157        }
1158        break;
1159    case MotionNotify:
1160        /*
1161         * MotionEvent compression.  Ignore multiple motion events.
1162         * Ignore motion events if the mouse button is up.
1163         */
1164        if (XPending(xDisplay)) /* Xlib doesn't flush the output buffer */
1165            if (XtPeekEvent(&next_event))
1166                if (next_event.type == MotionNotify)
1167                    return;
1168        if ((grabX < 0) || (grabY < 0))
1169            return;
1170        sx = xOffset + grabX - (int) event->xmotion.x;
1171        if (sx >= (iw - ww))    /* clamp x motion but allow y motion */
1172            sx = iw - ww;
1173        sx = MAX(sx, 0);
1174        sy = yOffset + grabY - (int) event->xmotion.y;
1175        if (sy >= (ih - wh)) /* clamp y motion but allow x motion */
1176            sy = ih - wh;
1177        sy = MAX(sy, 0);
1178        if ((sx == xOffset) && (sy == yOffset))
1179            return;
1180        dx = dy = 0;
1181        w = ww; h = wh;
1182        break;
1183    case ButtonRelease:
1184        xOffset = xOffset + grabX - (int) event->xbutton.x;
1185        xOffset = MIN(iw - ww, xOffset);
1186        xOffset = MAX(xOffset, 0);
1187        yOffset = yOffset + grabY - (int) event->xbutton.y;
1188        yOffset = MIN(ih - wh, yOffset);
1189        yOffset = MAX(yOffset, 0);
1190        grabX = grabY = -1;
1191    default:
1192        return;
1193    }
1194
1195    if (appData.usePixmap == True) {
1196        if (xImageDepth == 1)
1197            XCopyPlane(xDisplay, xImagePixmap, XtWindow(widget),
1198                xWinGc, sx, sy, w, h, dx, dy, 1);
1199        else
1200            XCopyArea(xDisplay, xImagePixmap, XtWindow(widget),
1201                xWinGc, sx, sy, w, h, dx, dy);
1202    } else
1203        XPutImage(xDisplay, XtWindow(widget), xWinGc, xImage,
1204            sx, sy, dx, dy, w, h);
1205}
1206
1207void
1208ResizeProc()
1209{
1210    Dimension w_width, w_height;
1211    int xo, yo, ww, wh;
1212    XEvent fake_event;
1213    Arg args[2];
1214
1215    if ((xOffset == 0) && (yOffset == 0))
1216        return;
1217
1218    XtSetArg(args[0], XtNwidth, &w_width);
1219    XtSetArg(args[1], XtNheight, &w_height);
1220    XtGetValues(shellWidget, args, 2);
1221    ww = w_width;
1222    wh = w_height;
1223    XtGetValues(listWidget, args, 2);
1224    wh -= w_height;
1225
1226    xo = xOffset; yo = yOffset;
1227
1228    if ((xOffset + ww) >= tfImageWidth)
1229        xOffset = MAX((int) tfImageWidth - ww, 0);
1230    if ((yOffset + wh) >= tfImageHeight)
1231        yOffset = MAX((int) tfImageHeight - wh, 0);
1232
1233    /*
1234     * Send an ExposeEvent if the origin changed.
1235     * We have to do this because of the use and semantics of bit gravity.
1236     */
1237    if ((xo != xOffset) || (yo != yOffset)) {
1238        fake_event.type = Expose;
1239        fake_event.xexpose.x = fake_event.xexpose.y = 0;
1240        fake_event.xexpose.width = tfImageWidth;
1241        fake_event.xexpose.height = tfImageHeight;
1242        EventProc(imageWidget, NULL, &fake_event);
1243    }
1244}
1245
1246int
1247XTiffErrorHandler(display, error_event)
1248    Display *display;
1249    XErrorEvent *error_event;
1250{
1251    char message[80];
1252
1253    /*
1254     * Some X servers limit the size of pixmaps.
1255     */
1256    if ((error_event->error_code == BadAlloc)
1257            && (error_event->request_code == X_CreatePixmap))
1258        fprintf(stderr, "xtiff: requested pixmap too big for display\n");
1259    else {
1260        XGetErrorText(display, error_event->error_code, message, 80);
1261        fprintf(stderr, "xtiff: error code %s\n", message);
1262    }
1263
1264    exit(0);
1265}
1266
1267void
1268Usage()
1269{
1270    fprintf(stderr, "Usage xtiff: [options] tiff-file\n");
1271    fprintf(stderr, "\tstandard Xt options\n");
1272    fprintf(stderr, "\t[-help]\n");
1273    fprintf(stderr, "\t[-gamma gamma]\n");
1274    fprintf(stderr, "\t[-usePixmap (True | False)]\n");
1275    fprintf(stderr, "\t[-viewportWidth pixels]\n");
1276    fprintf(stderr, "\t[-viewportHeight pixels]\n");
1277    fprintf(stderr, "\t[-translate pixels]\n");
1278    fprintf(stderr, "\t[-verbose (True | False)]\n");
1279    exit(0);
1280}
1281
1282/* vim: set ts=8 sts=8 sw=8 noet: */
1283
1284/*
1285 * Local Variables:
1286 * mode: c
1287 * c-basic-offset: 8
1288 * fill-column: 78
1289 * End:
1290 */
1291