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