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