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