1 /*
2 * FIG : Facility for Interactive Generation of figures
3 * Copyright (c) 1985-1988 by Supoj Sutanthavibul
4 * Parts Copyright (c) 1989-2015 by Brian V. Smith
5 * Parts Copyright (c) 1991 by Paul King
6 * Parts Copyright (c) 2016-2020 by Thomas Loimer
7 *
8 * Copyright (c) 1995 Jim Daley (jdaley@cix.compulink.co.uk)
9 *
10 * Any party obtaining a copy of these files is granted, free of charge, a
11 * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
12 * nonexclusive right and license to deal in this software and documentation
13 * files (the "Software"), including without limitation the rights to use,
14 * copy, modify, merge, publish, distribute, sublicense and/or sell copies of
15 * the Software, and to permit persons who receive copies from any such
16 * party to do so, with the only requirement being that the above copyright
17 * and this permission notice remain intact.
18 *
19 */
20
21 /*
22 Screen capture functions - let user draw rectangle on screen
23 and write a png file of the contents of that area.
24 */
25
26 #include "fig.h"
27 #include "resources.h"
28 #include "object.h"
29 #include "w_capture.h"
30 #include "w_msgpanel.h"
31 #include "f_util.h"
32 #include "w_drawprim.h"
33 #include "w_util.h"
34
35 #ifdef HAVE_PNG
36 extern Boolean write_png(FILE *file, unsigned char *data, int type,
37 unsigned char *Red, unsigned char *Green,
38 unsigned char *Blue, int numcols, int width,int height);
39 #endif
40
41 static Boolean getImageData(unsigned int *w, unsigned int *h, int *type,
42 int *nc, unsigned char *Red, unsigned char *Green, unsigned char *Blue); /* returns zero on failure */
43 static Boolean selectedRootArea(int *x_r, int *y_r, unsigned int *w_r, unsigned int *h_r, Window *cw); /* returns zero on failure */
44 static void drawRect(int x, int y, int w, int h, int draw);
45 static int getCurrentColors(Window w, XColor *colors); /* returns number of colors in map */
46
47 static unsigned char *data; /* pointer to captured & converted data */
48
49 /*
50 statics which need to be set up before we can call
51 drawRect - drawRect relies on GC being an xor so
52 a second XDrawRectangle will erase the first
53 */
54 static Window rectWindow;
55 static GC rectGC;
56
57
58
59 Boolean
captureImage(Widget window,char * filename)60 captureImage(Widget window, char *filename) /* returns True on success */
61 {
62 #ifndef HAVE_PNG
63 (void) window, filename;
64 file_msg("Screen capture not possible without png support.");
65 return False;
66 #else
67 unsigned char Red[MAX_COLORMAP_SIZE],
68 Green[MAX_COLORMAP_SIZE],
69 Blue[MAX_COLORMAP_SIZE];
70 int numcols;
71 int captured;
72 unsigned int width, height;
73 Boolean status;
74
75 FILE *pngfile;
76 int type;
77
78 if (!ok_to_write(filename, "EXPORT") )
79 return(False);
80
81 /* unmap the xfig windows, capture a png then remap our windows */
82
83 XtUnmapWidget(tool);
84 XtUnmapWidget(window);
85 app_flush();
86
87 /* capture the screen area */
88 status = getImageData(&width, &height, &type, &numcols, Red, Green, Blue);
89
90 /* make sure server is ungrabbed if we're debugging */
91 app_flush();
92 /* map our windows again */
93 XtMapWidget(tool);
94 XtMapWidget(window);
95
96 if ( status == False ) {
97 put_msg("Nothing Captured.");
98 app_flush();
99 captured = False;
100 } else {
101 /* encode the image and write to the file */
102 put_msg("Writing screenshot to PNG file...");
103
104 app_flush();
105
106 if ((pngfile = fopen(filename,"wb"))==0) {
107 file_msg("Cannot open PNG file %s for writing",filename);
108 put_msg("Cannot open PNG file %s for writing",filename);
109 captured = False;
110 } else {
111 /* write the png file */
112 if (!write_png(pngfile, data, type, Red, Green, Blue, numcols, width, height))
113 file_msg("Problem writing PNG file from screen capture");
114 fclose(pngfile);
115 captured = True;
116 }
117
118 free(data);
119 }
120
121 return ( captured );
122 #endif /* HAVE_PNG */
123 }
124
125 /*
126 * Get the image data from the screen
127 * width returned in w, height in h
128 * image type (IMAGE_RGB or IMAGE_PALETTE) stored in type
129 * colormap for IMAGE_PALETTE stored in Red, Green, Blue with
130 * number of colors stored in nc
131 *
132 * Returns False on failure
133 */
134
135 /* count how many bits to shift mask to the right to end up with a "1" in the lsb */
136
137 int
rshift(int mask)138 rshift(int mask)
139 {
140 register int i;
141
142 for (i=0; i<32; i++) {
143 if (mask&1)
144 break;
145 mask >>= 1;
146 }
147 return i;
148 }
149
150 static Boolean
getImageData(unsigned int * w,unsigned int * h,int * type,int * nc,unsigned char * Red,unsigned char * Green,unsigned char * Blue)151 getImageData(unsigned int *w, unsigned int *h, int *type, int *nc,
152 unsigned char *Red, unsigned char *Green, unsigned char *Blue)
153 {
154 XColor colors[MAX_COLORMAP_SIZE];
155 int colused[MAX_COLORMAP_SIZE];
156 int mapcols[MAX_COLORMAP_SIZE];
157 int red, green, blue;
158 int red_mask, green_mask, blue_mask;
159 int red_shift, green_shift, blue_shift;
160
161 int x, y;
162 unsigned int width, height;
163 Window cw;
164 static XImage *image;
165
166 int i, j;
167 int numcols;
168 int bytes_per_pixel, bit_order, byte_order;
169 int byte_inc;
170 int pix;
171 unsigned char *iptr, *rowptr, *dptr;
172
173 sleep(1); /* in case he'd like to click on something */
174 beep(); /* signal user */
175 if ( selectedRootArea(&x, &y, &width, &height, &cw ) == False )
176 return False;
177
178 image = XGetImage(tool_d, XDefaultRootWindow(tool_d),
179 x, y, width, height, AllPlanes, ZPixmap);
180 if (!image || !image->data) {
181 file_msg("Cannot capture %dx%d area - memory problems?",
182 width,height);
183 return False;
184 }
185
186
187 /* if we get here we got an image! */
188 *w = width = image->width;
189 *h = height = image->height;
190
191 if (tool_vclass == TrueColor) {
192 *type = IMAGE_RGB;
193 bytes_per_pixel = 3;
194 } else {
195 /* PseudoColor, get color table */
196 *type = IMAGE_PALETTE;
197 bytes_per_pixel = 1;
198 numcols = getCurrentColors(XDefaultRootWindow(tool_d), colors);
199 if ( numcols <= 0 ) { /* ought not to get here as capture button
200 should not appear for these displays */
201 file_msg("Cannot handle a display without a colormap.");
202 XDestroyImage( image );
203 return False;
204 }
205 }
206
207 iptr = rowptr = (unsigned char *) image->data;
208 dptr = data = (unsigned char *) malloc(height*width*bytes_per_pixel);
209 if ( !dptr ) {
210 file_msg("Insufficient memory to convert image.");
211 XDestroyImage(image);
212 return False;
213 }
214
215 if (tool_vclass == TrueColor) {
216 byte_order = image->byte_order; /* MSBFirst or LSBFirst */
217 bit_order = image->bitmap_bit_order; /* MSBFirst or LSBFirst */
218 red_mask = image->red_mask;
219 green_mask = image->green_mask;
220 blue_mask = image->blue_mask;
221 /* find how many bits we need to shift values */
222 red_shift = rshift(red_mask);
223 green_shift = rshift(green_mask);
224 blue_shift = rshift(blue_mask);
225 switch (image->bits_per_pixel) {
226 case 8: byte_inc = 1;
227 break;
228 case 16: byte_inc = 2;
229 break;
230 case 24: byte_inc = 3;
231 break;
232 case 32: byte_inc = 4;
233 break;
234 default: byte_inc = 4;
235 break;
236 }
237
238 for (i=0; i<image->height; i++) {
239 for (j=0; j<image->width; j++) {
240 if (byte_order == MSBFirst) {
241 switch (byte_inc) {
242 case 1:
243 pix = (unsigned char) *iptr;
244 break;
245 case 2:
246 pix = (unsigned short) (*iptr << 8);
247 pix += (unsigned char) *(iptr+1);
248 break;
249 case 3:
250 pix = (unsigned int) (*(iptr) << 16);
251 pix += (unsigned short) (*(iptr+1) << 8);
252 pix += (unsigned char) (*(iptr+2));
253 break;
254 case 4:
255 pix = (unsigned int) (*(iptr) << 24);
256 pix += (unsigned int) (*(iptr+1) << 16);
257 pix += (unsigned short) (*(iptr+2) << 8);
258 pix += (unsigned char) (*(iptr+3));
259 break;
260 }
261 } else {
262 /* LSBFirst */
263 switch (byte_inc) {
264 case 1:
265 pix = (unsigned char) *iptr;
266 break;
267 case 2:
268 pix = (unsigned char) *iptr;
269 pix += (unsigned short) (*(iptr+1) << 8);
270 break;
271 case 3:
272 pix = (unsigned char) *iptr;
273 pix += (unsigned short) (*(iptr+1) << 8);
274 pix += (unsigned int) (*(iptr+2) << 16);
275 break;
276 case 4:
277 pix = (unsigned char) *iptr;
278 pix += (unsigned short) (*(iptr+1) << 8);
279 pix += (unsigned int) (*(iptr+2) << 16);
280 pix += (unsigned int) (*(iptr+3) << 24);
281 break;
282 }
283 } /* if (byte_order ...) */
284
285 /* increment pixel pointer */
286 iptr += byte_inc;
287
288 /* now extract the red, green and blue values using the masks and shifting */
289
290 red = (pix & red_mask) >> red_shift;
291 green = (pix & green_mask) >> green_shift;
292 blue = (pix & blue_mask) >> blue_shift;
293 /* store in output data */
294 *(dptr++) = (unsigned char) red;
295 *(dptr++) = (unsigned char) green;
296 *(dptr++) = (unsigned char) blue;
297 } /* for (j=0; j<image->width ... */
298
299 /* advance to next scanline row */
300 rowptr += image->bytes_per_line;
301 iptr = rowptr;
302 } /* for (i=0; i<image->height ... */
303
304 } else if (tool_cells > 2) {
305 /* color image with color table (PseudoColor) */
306 for (i=0; i<numcols; i++) {
307 colused[i] = 0;
308 }
309
310 /* now map the pixel values to 0..numcolors */
311 x = 0;
312 for (i=0; i<image->bytes_per_line*height; i++, iptr++) {
313 if (x >= image->bytes_per_line)
314 x=0;
315 if (x < width) {
316 colused[*iptr] = 1; /* mark this color as used */
317 *dptr++ = *iptr;
318 }
319 x++;
320 }
321
322 /* count the number of colors used */
323 *nc = numcols;
324 numcols = 0;
325 /* and put them in the Red, Green and Blue arrays */
326 for (i=0; i< *nc; i++) {
327 if (colused[i]) {
328 mapcols[i] = numcols;
329 Red[numcols] = colors[i].red >> 8;
330 Green[numcols] = colors[i].green >> 8;
331 Blue[numcols] = colors[i].blue >> 8;
332 numcols++;
333 }
334 }
335 /* remap the pixels */
336 for (i=0, dptr = data; i < width*height; i++, dptr++) {
337 *dptr = mapcols[*dptr];
338 }
339 *nc = numcols;
340
341 /* monochrome, copy bits to bytes */
342 } else {
343 int bitp;
344 x = 0;
345 for (i=0; i<image->bytes_per_line*height; i++, iptr++) {
346 if (x >= image->bytes_per_line*8)
347 x=0;
348 if (image->bitmap_bit_order == LSBFirst) {
349 for (bitp=1; bitp<256; bitp<<=1) {
350 if (x < width) {
351 if (*iptr & bitp)
352 *dptr = 1;
353 else
354 *dptr = 0;
355 dptr++;
356 }
357 x++;
358 }
359 } else {
360 for (bitp=128; bitp>0; bitp>>=1) {
361 if (x < width) {
362 if (*iptr & bitp)
363 *dptr = 1;
364 else
365 *dptr = 0;
366 dptr++;
367 }
368 x++;
369 }
370 }
371 }
372 for (i=0; i<2; i++) {
373 Red[i] = colors[i].red >> 8;
374 Green[i] = colors[i].green >> 8;
375 Blue[i] = colors[i].blue >> 8;
376 }
377 numcols = 2;
378 *nc = numcols;
379 }
380 /* free the image structure */
381 XDestroyImage(image);
382 return True;
383 }
384
385
386 #define PTR_BUTTON_STATE( wx, wy, msk ) \
387 ( XQueryPointer(tool_d, rectWindow, &root_r, &child_r, &root_x, &root_y, \
388 &wx, &wy, &msk), \
389 msk & (Button1Mask | Button2Mask | Button3Mask) )
390
391
392 /*
393 let user mark which bit of the window we want, UI follows xfig:
394 button1 marks start point, any other cancels
395 button1 again marks end point - any other cancels
396 */
397
398 static Boolean
selectedRootArea(int * x_r,int * y_r,unsigned int * w_r,unsigned int * h_r,Window * cw)399 selectedRootArea(int *x_r, int *y_r, unsigned int *w_r, unsigned int *h_r, Window *cw)
400 {
401 int x1, y1; /* start point of user rect */
402 int x, y, width, height; /* current values for rect */
403
404 Window root_r, child_r; /* parameters for xQueryPointer */
405 int root_x, root_y;
406 int last_x, last_y;
407 int win_x, win_y;
408 unsigned int dum;
409 unsigned int mask;
410 XGCValues gcv;
411 unsigned long gcmask;
412
413 /* set up our local globals for drawRect */
414 rectWindow = XDefaultRootWindow(tool_d);
415
416 XGrabPointer(tool_d, rectWindow, False, 0L,
417 GrabModeAsync, GrabModeSync, None,
418 crosshair_cursor, CurrentTime);
419 while (PTR_BUTTON_STATE( win_x, win_y, mask ) == 0)
420 ;
421
422 /* button 1 pressed, get whole window under pointer */
423 if ( (mask & Button1Mask ) ) {
424 /* after user releases button */
425 while (PTR_BUTTON_STATE( win_x, win_y, mask ) != 0)
426 ;
427 XUngrabPointer(tool_d, CurrentTime);
428 if (child_r == None)
429 child_r = root_r;
430 /* get the geometry right into the return vars */
431 XGetGeometry(tool_d, child_r, &root_r, x_r, y_r, w_r, h_r, &dum, &dum);
432 /* make sure area is on screen */
433 if (*x_r < 0)
434 *x_r = 0;
435 else if (*x_r + *w_r > WidthOfScreen(tool_s))
436 *w_r = WidthOfScreen(tool_s)-*x_r;
437 if (*y_r < 0)
438 *y_r = 0;
439 else if (*y_r + *h_r > HeightOfScreen(tool_s))
440 *h_r = HeightOfScreen(tool_s)-*y_r;
441 *cw = child_r;
442 return True;
443 }
444
445
446 /* button 2 pressed, wait for release */
447 if ( !(mask & Button2Mask ) ) {
448 XUngrabPointer(tool_d, CurrentTime);
449 return False;
450 } else {
451 while (PTR_BUTTON_STATE( win_x, win_y, mask ) != 0)
452 ;
453 }
454
455 /* if we're here we got a button 2 press & release */
456 /* so initialise for tracking box across display */
457
458 last_x = x1 = x = win_x;
459 last_y = y1 = y = win_y;
460 width = 0;
461 height = 0;
462
463 /* Nobble our GC to let us draw a box over everything */
464 gcv.foreground = x_color(BLACK) ^ x_color(WHITE);
465 gcv.background = x_color(WHITE);
466 gcv.function = GXxor;
467 gcmask = GCFunction | GCForeground | GCBackground;
468 rectGC = XCreateGC(tool_d, XtWindow(canvas_sw), gcmask, &gcv);
469 XSetSubwindowMode(tool_d, rectGC, IncludeInferiors);
470
471 /* Wait for button press while tracking rectangle on screen */
472 while ( PTR_BUTTON_STATE( win_x, win_y, mask ) == 0 ) {
473 if (win_x != last_x || win_y != last_y) {
474 drawRect(x, y, width, height, False); /* remove any existing rectangle */
475
476 x = min2(x1, win_x);
477 y = min2(y1, win_y);
478 width = abs(win_x - x1);
479 height = abs(win_y - y1);
480
481 last_x = win_x;
482 last_y = win_y;
483
484 if ((width > 1) && (height > 1))
485 drawRect(x, y, width, height, True); /* display rectangle */
486 }
487 }
488
489 drawRect(x, y, width, height, False); /* remove any remaining rect */
490 XUngrabPointer(tool_d, CurrentTime); /* & let go the pointer */
491
492 /* put GC back to normal */
493 XSetFunction(tool_d, rectGC, GXcopy);
494 XSetSubwindowMode(tool_d, rectGC, ClipByChildren);
495
496 if (width == 0 || height == 0 || !(mask & Button2Mask) )
497 return False; /* cancelled or selected nothing */
498
499 /* we have a rectangle - set up the return parameters */
500 *x_r = x; *y_r = y;
501 *w_r = width; *h_r = height;
502 if ( child_r == None )
503 *cw = root_r;
504 else
505 *cw = child_r;
506
507 return True;
508 }
509
510
511 /*
512 draw or erase an on screen rectangle - dependant on value of draw
513 */
514
515 static void
drawRect(int x,int y,int w,int h,int draw)516 drawRect(int x, int y, int w, int h, int draw)
517 {
518 static int onscreen = False;
519
520 if ( onscreen != draw )
521 {
522 if ((w>1) && (h >1))
523 {
524 XDrawRectangle( tool_d, rectWindow, rectGC, x, y, w-1, h-1 );
525 onscreen = draw;
526 }
527 }
528 }
529
530 /*
531 in picking up the color map I'm making the assumption that the user
532 has arranged the captured screen to appear as he wishes - ie that
533 whatever colors he wants are displayed - this means that if the
534 chosen window color map is not installed then we need to pick
535 the one that is - rather than the one appropriate to the window
536 The catch is that there may be several installed maps
537 so we do need to check the window - rather than pick up
538 the current installed map.
539
540 **************** This code based on xwd.c *****************
541 ********* Here is the relevant copyright notice: ***********
542
543 The following copyright and permission notice outlines the
544 rights and restrictions covering most parts of the standard
545 distribution of the X Window System from MIT. Other parts
546 have additional or different copyrights and permissions; see
547 the individual source files.
548
549 Copyright 1984, 1985, 1986, 1987, 1988, Massachusetts Insti-
550 tute of Technology.
551
552 Permission to use, copy, modify, and distribute this
553 software and its documentation for any purpose and without
554 fee is hereby granted, provided that the above copyright
555 notice appear in all copies and that both that copyright
556 notice and this permission notice appear in supporting docu-
557 mentation, and that the name of M.I.T. not be used in
558 advertising or publicity pertaining to distribution of the
559 software without specific, written prior permission. M.I.T.
560 makes no representations about the suitability of this
561 software for any purpose. It is provided "as is" without
562 express or implied warranty.
563
564 This software is not subject to any license of the American
565 Telephone and Telegraph Company or of the Regents of the
566 University of California.
567
568 */
569
570 #define lowbit(x) ((x) & (~(x) + 1))
571
572 static int
getCurrentColors(Window w,XColor * colors)573 getCurrentColors(Window w, XColor *colors)
574 {
575 XWindowAttributes xwa;
576 int i, ncolors;
577 Colormap map;
578
579 XGetWindowAttributes(tool_d, w, &xwa);
580
581 if (xwa.visual->class == TrueColor) {
582 file_msg("TrueColor visual No colormap.");
583 return 0;
584 }
585
586 else if (!xwa.colormap) {
587 XGetWindowAttributes(tool_d, XDefaultRootWindow(tool_d), &xwa);
588 if (!xwa.colormap) {
589 file_msg("no Colormap available.");
590 return 0;
591 }
592 }
593
594 ncolors = xwa.visual->map_entries;
595
596 if (xwa.visual->class == DirectColor) {
597 Pixel red, green, blue, red1, green1, blue1;
598
599
600 red = green = blue = 0;
601 red1 = lowbit(xwa.visual->red_mask);
602 green1 = lowbit(xwa.visual->green_mask);
603 blue1 = lowbit(xwa.visual->blue_mask);
604 for (i=0; i<ncolors; i++) {
605 colors[i].pixel = red|green|blue;
606 colors[i].pad = 0;
607 red += red1;
608 if (red > xwa.visual->red_mask) red = 0;
609 green += green1;
610 if (green > xwa.visual->green_mask) green = 0;
611 blue += blue1;
612 if (blue > xwa.visual->blue_mask) blue = 0;
613 }
614 }
615 else {
616 for (i=0; i<ncolors; i++) {
617 colors[i].pixel = i;
618 colors[i].pad = 0;
619 }
620 }
621
622 if ( ( xwa.colormap ) && ( xwa.map_installed ) )
623 map = xwa.colormap;
624
625 else
626 {
627 Colormap *maps;
628 int count;
629
630 maps = XListInstalledColormaps(tool_d, XDefaultRootWindow(tool_d), &count);
631 if ( count > 0 ) map = maps[0];
632 else map = tool_cm; /* last resort! */
633 XFree( maps );
634 }
635 XQueryColors(tool_d, map, colors, ncolors);
636
637 return(ncolors);
638 }
639
640
641 /*
642 returns True if we can handle XImages from the visual class
643 The current Image write functions & our image conversion routines
644 require us to produce a colormapped byte per pixel image
645 pointed to by data
646 */
647
648 Boolean
canHandleCapture(Display * d)649 canHandleCapture(Display *d)
650 {
651 XWindowAttributes xwa;
652
653 XGetWindowAttributes(d, XDefaultRootWindow(d), &xwa);
654
655 if (!xwa.colormap) {
656 file_msg("Can't capture screen because no colormap found");
657 return False;
658 } else if (MAX_COLORMAP_SIZE < xwa.visual->map_entries) {
659 file_msg("Can't capture screen because colormap (%d) is larger than %d",
660 xwa.visual->map_entries, MAX_COLORMAP_SIZE);
661 return False;
662 } else {
663 return True;
664 }
665 }
666