10bbfda8aSnia/*
20bbfda8aSnia * Image handling functions
30bbfda8aSnia *
40bbfda8aSnia * This provides some general and hub stuff.  Details of different image
50bbfda8aSnia * type functions, and generation of builtins, go in their own files.
60bbfda8aSnia */
70bbfda8aSnia
80bbfda8aSnia#include "ctwm.h"
90bbfda8aSnia
100bbfda8aSnia#include <stdio.h>
110bbfda8aSnia#include <string.h>
120bbfda8aSnia#include <unistd.h>
130bbfda8aSnia
140bbfda8aSnia#include "list.h"
150bbfda8aSnia#include "screen.h"
160bbfda8aSnia
170bbfda8aSnia#include "image.h"
180bbfda8aSnia#include "image_bitmap.h"
190bbfda8aSnia#include "image_bitmap_builtin.h"
200bbfda8aSnia#include "image_xwd.h"
210bbfda8aSnia#ifdef JPEG
220bbfda8aSnia#include "image_jpeg.h"
230bbfda8aSnia#endif
240bbfda8aSnia#if defined (XPM)
250bbfda8aSnia#include "image_xpm.h"
260bbfda8aSnia#endif
270bbfda8aSnia
280bbfda8aSnia/* Flag (maybe should be retired */
290bbfda8aSniabool reportfilenotfound = false;
300bbfda8aSniaColormap AlternateCmap = None;
310bbfda8aSnia
320bbfda8aSnia
330bbfda8aSnia/*
340bbfda8aSnia * Find (load/generate) an image by name
350bbfda8aSnia */
360bbfda8aSniaImage *
370bbfda8aSniaGetImage(const char *name, ColorPair cp)
380bbfda8aSnia{
390bbfda8aSnia	name_list **list;
400bbfda8aSnia#define GIFNLEN 256
410bbfda8aSnia	char fullname[GIFNLEN];
420bbfda8aSnia	Image *image;
430bbfda8aSnia
440bbfda8aSnia	if(name == NULL) {
450bbfda8aSnia		return NULL;
460bbfda8aSnia	}
47b18c2d1eSnia	if(dpy == NULL) {
48b18c2d1eSnia		// May happen in special cases like --cfgchk with no $DISPLAY
49b18c2d1eSnia		return NULL;
50b18c2d1eSnia	}
510bbfda8aSnia	image = NULL;
520bbfda8aSnia
530bbfda8aSnia	list = &Scr->ImageCache;
540bbfda8aSnia	if(0) {
550bbfda8aSnia		/* dummy */ ;
560bbfda8aSnia	}
570bbfda8aSnia	else if((name [0] == '@') || (strncmp(name, "xpm:", 4) == 0)) {
580bbfda8aSnia#ifdef XPM
590bbfda8aSnia		snprintf(fullname, GIFNLEN, "%s%dx%d", name, (int) cp.fore, (int) cp.back);
600bbfda8aSnia
610bbfda8aSnia		if((image = LookInNameList(*list, fullname)) == NULL) {
620bbfda8aSnia			int startn = (name [0] == '@') ? 1 : 4;
630bbfda8aSnia			if((image = GetXpmImage(name + startn, cp)) != NULL) {
640bbfda8aSnia				AddToList(list, fullname, image);
650bbfda8aSnia			}
660bbfda8aSnia		}
670bbfda8aSnia#else
680bbfda8aSnia		fprintf(stderr, "XPM support disabled, ignoring image %s\n", name);
690bbfda8aSnia		return NULL;
700bbfda8aSnia#endif
710bbfda8aSnia	}
720bbfda8aSnia	else if(strncmp(name, "jpeg:", 5) == 0) {
730bbfda8aSnia#ifdef JPEG
740bbfda8aSnia		if((image = LookInNameList(*list, name)) == NULL) {
750bbfda8aSnia			if((image = GetJpegImage(&name [5])) != NULL) {
760bbfda8aSnia				AddToList(list, name, image);
770bbfda8aSnia			}
780bbfda8aSnia		}
790bbfda8aSnia#else
800bbfda8aSnia		fprintf(stderr, "JPEG support disabled, ignoring image %s\n", name);
810bbfda8aSnia		return NULL;
820bbfda8aSnia#endif
830bbfda8aSnia	}
840bbfda8aSnia	else if((strncmp(name, "xwd:", 4) == 0) || (name [0] == '|')) {
850bbfda8aSnia		int startn = (name [0] == '|') ? 0 : 4;
860bbfda8aSnia		if((image = LookInNameList(*list, name)) == NULL) {
870bbfda8aSnia			if((image = GetXwdImage(&name [startn], cp)) != NULL) {
880bbfda8aSnia				AddToList(list, name, image);
890bbfda8aSnia			}
900bbfda8aSnia		}
910bbfda8aSnia	}
920bbfda8aSnia	else if(strncmp(name, ":xpm:", 5) == 0) {
930bbfda8aSnia		snprintf(fullname, GIFNLEN, "%s%dx%d", name, (int) cp.fore, (int) cp.back);
940bbfda8aSnia		if((image = LookInNameList(*list, fullname)) == NULL) {
950bbfda8aSnia			image = get_builtin_scalable_pixmap(name, cp);
960bbfda8aSnia			if(image == NULL) {
970bbfda8aSnia				/* g_b_s_p() already warned */
980bbfda8aSnia				return NULL;
990bbfda8aSnia			}
1000bbfda8aSnia			AddToList(list, fullname, image);
1010bbfda8aSnia		}
1020bbfda8aSnia	}
1030bbfda8aSnia	else if(strncmp(name, "%xpm:", 5) == 0) {
1040bbfda8aSnia		snprintf(fullname, GIFNLEN, "%s%dx%d", name, (int) cp.fore, (int) cp.back);
1050bbfda8aSnia		if((image = LookInNameList(*list, fullname)) == NULL) {
1060bbfda8aSnia			image = get_builtin_animated_pixmap(name, cp);
1070bbfda8aSnia			if(image == NULL) {
1080bbfda8aSnia				/* g_b_a_p() already warned */
1090bbfda8aSnia				return NULL;
1100bbfda8aSnia			}
1110bbfda8aSnia			AddToList(list, fullname, image);
1120bbfda8aSnia		}
1130bbfda8aSnia	}
1140bbfda8aSnia	else if(name [0] == ':') {
1150bbfda8aSnia		unsigned int    width, height;
1160bbfda8aSnia		Pixmap          pm = 0;
1170bbfda8aSnia		XGCValues       gcvalues;
1180bbfda8aSnia
1190bbfda8aSnia		snprintf(fullname, GIFNLEN, "%s%dx%d", name, (int) cp.fore, (int) cp.back);
1200bbfda8aSnia		if((image = LookInNameList(*list, fullname)) == NULL) {
1210bbfda8aSnia			pm = get_builtin_plain_pixmap(name, &width, &height);
1220bbfda8aSnia			if(pm == None) {
1230bbfda8aSnia				/* g_b_p_p() already warned */
1240bbfda8aSnia				return NULL;
1250bbfda8aSnia			}
1260bbfda8aSnia			image = AllocImage();
1270bbfda8aSnia			image->pixmap = XCreatePixmap(dpy, Scr->Root, width, height, Scr->d_depth);
1280bbfda8aSnia			if(Scr->rootGC == (GC) 0) {
1290bbfda8aSnia				Scr->rootGC = XCreateGC(dpy, Scr->Root, 0, &gcvalues);
1300bbfda8aSnia			}
1310bbfda8aSnia			gcvalues.background = cp.back;
1320bbfda8aSnia			gcvalues.foreground = cp.fore;
1330bbfda8aSnia			XChangeGC(dpy, Scr->rootGC, GCForeground | GCBackground, &gcvalues);
1340bbfda8aSnia			XCopyPlane(dpy, pm, image->pixmap, Scr->rootGC, 0, 0, width, height, 0, 0,
1350bbfda8aSnia			           (unsigned long) 1);
1360bbfda8aSnia			image->width  = width;
1370bbfda8aSnia			image->height = height;
1380bbfda8aSnia			AddToList(list, fullname, image);
1390bbfda8aSnia		}
1400bbfda8aSnia	}
1410bbfda8aSnia	else {
1420bbfda8aSnia		snprintf(fullname, GIFNLEN, "%s%dx%d", name, (int) cp.fore, (int) cp.back);
1430bbfda8aSnia		if((image = LookInNameList(*list, fullname)) == NULL) {
1440bbfda8aSnia			if((image = GetBitmapImage(name, cp)) != NULL) {
1450bbfda8aSnia				AddToList(list, fullname, image);
1460bbfda8aSnia			}
1470bbfda8aSnia		}
1480bbfda8aSnia	}
1490bbfda8aSnia	return image;
1500bbfda8aSnia#undef GIFNLEN
1510bbfda8aSnia}
1520bbfda8aSnia
1530bbfda8aSnia
1540bbfda8aSnia/*
1550bbfda8aSnia * Creation/cleanup of Image structs
1560bbfda8aSnia */
1570bbfda8aSniaImage *
1580bbfda8aSniaAllocImage(void)
1590bbfda8aSnia{
1600bbfda8aSnia	return calloc(1, sizeof(Image));
1610bbfda8aSnia}
1620bbfda8aSnia
1630bbfda8aSniavoid
1640bbfda8aSniaFreeImage(Image *image)
1650bbfda8aSnia{
1660bbfda8aSnia	Image *im, *im2;
1670bbfda8aSnia
1680bbfda8aSnia	im = image;
1690bbfda8aSnia	while(im != NULL) {
1700bbfda8aSnia		/* Cleanup sub-bits */
1710bbfda8aSnia		if(im->pixmap) {
1720bbfda8aSnia			XFreePixmap(dpy, im->pixmap);
1730bbfda8aSnia		}
1740bbfda8aSnia		if(im->mask) {
1750bbfda8aSnia			XFreePixmap(dpy, im->mask);
1760bbfda8aSnia		}
1770bbfda8aSnia
1780bbfda8aSnia		/* Cleanup self */
1790bbfda8aSnia		im2 = im->next;
1800bbfda8aSnia		im->next = NULL;
1810bbfda8aSnia		free(im);
1820bbfda8aSnia
1830bbfda8aSnia		/*
1840bbfda8aSnia		 * Loop back around, unless we hit the original.  e.g.,
1850bbfda8aSnia		 * "foo%.xpm" animations load the images into a closed loop, so
1860bbfda8aSnia		 * FreeImage() would do Very Bad Things running around the track
1870bbfda8aSnia		 * until it segfaults or the like.
1880bbfda8aSnia		 */
1890bbfda8aSnia		if(im2 == image) {
1900bbfda8aSnia			break;
1910bbfda8aSnia		}
1920bbfda8aSnia		im = im2;
1930bbfda8aSnia	}
1940bbfda8aSnia}
1950bbfda8aSnia
1960bbfda8aSnia
1970bbfda8aSnia
1980bbfda8aSnia/*
1990bbfda8aSnia * Utils for image*
2000bbfda8aSnia */
2010bbfda8aSnia
2020bbfda8aSnia/*
2030bbfda8aSnia * Expand out the real pathname for an image.  Turn ~ into $HOME if
2040bbfda8aSnia * it's there, and look under the entries in PixmapDirectory if the
2050bbfda8aSnia * result isn't a full path.
2060bbfda8aSnia */
2070bbfda8aSniachar *
2080bbfda8aSniaExpandPixmapPath(const char *name)
2090bbfda8aSnia{
2100bbfda8aSnia	char *ret;
2110bbfda8aSnia
2120bbfda8aSnia	ret = NULL;
2130bbfda8aSnia
2140bbfda8aSnia	/* If it starts with '~/', replace it with our homedir */
2150bbfda8aSnia	if(name[0] == '~' && name[1] == '/') {
2160bbfda8aSnia		asprintf(&ret, "%s/%s", Home, name + 2);
2170bbfda8aSnia		return ret;
2180bbfda8aSnia	}
2190bbfda8aSnia
2200bbfda8aSnia	/*
2210bbfda8aSnia	 * If it starts with /, it's an absolute path, so just pass it
2220bbfda8aSnia	 * through.
2230bbfda8aSnia	 */
2240bbfda8aSnia	if(name[0] == '/') {
2250bbfda8aSnia		return strdup(name);
2260bbfda8aSnia	}
2270bbfda8aSnia
2280bbfda8aSnia	/*
2290bbfda8aSnia	 * If we got here, it's some sort of relative path (or a bare
2300bbfda8aSnia	 * filename), so search for it under PixmapDirectory if we have it.
2310bbfda8aSnia	 */
2320bbfda8aSnia	if(Scr->PixmapDirectory) {
2330bbfda8aSnia		char *colon;
2340bbfda8aSnia		char *p = Scr->PixmapDirectory;
2350bbfda8aSnia
2360bbfda8aSnia		/* PixmapDirectory is a colon-separated list */
2370bbfda8aSnia		while((colon = strchr(p, ':'))) {
2380bbfda8aSnia			*colon = '\0';
2390bbfda8aSnia			asprintf(&ret, "%s/%s", p, name);
2400bbfda8aSnia			*colon = ':';
2410bbfda8aSnia			if(!access(ret, R_OK)) {
2420bbfda8aSnia				return (ret);
2430bbfda8aSnia			}
2440bbfda8aSnia			free(ret);
2450bbfda8aSnia			p = colon + 1;
2460bbfda8aSnia		}
2470bbfda8aSnia
2480bbfda8aSnia		asprintf(&ret, "%s/%s", p, name);
2490bbfda8aSnia		if(!access(ret, R_OK)) {
2500bbfda8aSnia			return (ret);
2510bbfda8aSnia		}
2520bbfda8aSnia		free(ret);
2530bbfda8aSnia	}
2540bbfda8aSnia
2550bbfda8aSnia
2560bbfda8aSnia	/*
2570bbfda8aSnia	 * If we get here, we have no idea.  For simplicity and consistency
2580bbfda8aSnia	 * for our callers, just return what we were given.
2590bbfda8aSnia	 */
2600bbfda8aSnia	return strdup(name);
2610bbfda8aSnia}
2620bbfda8aSnia
2630bbfda8aSnia
2640bbfda8aSnia/*
2650bbfda8aSnia * Generalized loader for animations.
2660bbfda8aSnia *
2670bbfda8aSnia * These are specified with a '%' in the filename, which is replaced by a
2680bbfda8aSnia * series of numbers.  So e.g.
2690bbfda8aSnia *
2700bbfda8aSnia * "foo%.xpm" -> [ "foo1.xpm", "foo2.xpm", ...]
2710bbfda8aSnia *
2720bbfda8aSnia * These then turn into a looped-linked-list of Image's.  We support
2730bbfda8aSnia * these for all types of images, so write it up into a central handler
2740bbfda8aSnia * once to centralize the logic.
2750bbfda8aSnia */
2760bbfda8aSniaImage *
2770bbfda8aSniaget_image_anim_cp(const char *name,
2780bbfda8aSnia                  ColorPair cp, Image * (*imgloader)(const char *, ColorPair))
2790bbfda8aSnia{
2800bbfda8aSnia	Image   *head, *tail;
2810bbfda8aSnia	char    *pref, *suff, *stmp;
2820bbfda8aSnia	int     i;
2830bbfda8aSnia
2840bbfda8aSnia	/* This shouldn't get called for non-animations */
2850bbfda8aSnia	if((stmp = strchr(name, '%')) == NULL) {
2860bbfda8aSnia		fprintf(stderr, "%s() called for non-animation '%s'\n", __func__, name);
2870bbfda8aSnia		return NULL;
2880bbfda8aSnia	}
2890bbfda8aSnia	if(stmp[1] == '\0') {
2900bbfda8aSnia		fprintf(stderr, "%s(): nothing after %% in '%s'\n", __func__, name);
2910bbfda8aSnia		return NULL;
2920bbfda8aSnia	}
2930bbfda8aSnia	stmp = NULL;
2940bbfda8aSnia
2950bbfda8aSnia	/*
2960bbfda8aSnia	 * For animated requests, we load a series of files, replacing the %
2970bbfda8aSnia	 * with numbers in series.
2980bbfda8aSnia	 */
2990bbfda8aSnia	tail = head = NULL;
3000bbfda8aSnia
3010bbfda8aSnia	/* Working copy of the filename split to before/after the % */
3020bbfda8aSnia	pref = strdup(name);
3030bbfda8aSnia	suff = strchr(pref, '%');
3040bbfda8aSnia	*suff++ = '\0';
3050bbfda8aSnia
3060bbfda8aSnia	/* "foo%.xpm" -> [ "foo1.xpm", "foo2.xpm", ...] */
3070bbfda8aSnia	for(i = 1 ; ; i++) {
3080bbfda8aSnia#define ANIM_PATHLEN 256
3090bbfda8aSnia		char path[ANIM_PATHLEN];
3100bbfda8aSnia		Image *tmp;
3110bbfda8aSnia
3120bbfda8aSnia		if(snprintf(path, ANIM_PATHLEN, "%s%d%s", pref, i,
3130bbfda8aSnia		                suff) >= (ANIM_PATHLEN - 1)) {
3140bbfda8aSnia			fprintf(stderr, "%s(): generated filename for '%s' #%d longer than %d.\n",
3150bbfda8aSnia			        __func__, name, i, ANIM_PATHLEN);
3160bbfda8aSnia			FreeImage(head);
3170bbfda8aSnia			free(pref);
3180bbfda8aSnia			return NULL;
3190bbfda8aSnia		}
3200bbfda8aSnia#undef ANIM_PATHLEN
3210bbfda8aSnia
3220bbfda8aSnia		/*
3230bbfda8aSnia		 * Load this image, and set ->next so it's explicitly the
3240bbfda8aSnia		 * [current] tail of the list.
3250bbfda8aSnia		 */
3260bbfda8aSnia		tmp = imgloader(path, cp);
3270bbfda8aSnia		if(tmp == NULL) {
3280bbfda8aSnia			break;
3290bbfda8aSnia		}
3300bbfda8aSnia		tmp->next = NULL;
3310bbfda8aSnia
3320bbfda8aSnia		/*
3330bbfda8aSnia		 * If it's the first, it's the head (image) we return, as well as
3340bbfda8aSnia		 * our current tail marker (s).  Else, append to that tail.
3350bbfda8aSnia		 */
3360bbfda8aSnia		if(head == NULL) {
3370bbfda8aSnia			tail = head = tmp;
3380bbfda8aSnia		}
3390bbfda8aSnia		else {
3400bbfda8aSnia			tail->next = tmp;
3410bbfda8aSnia			tail = tmp;
3420bbfda8aSnia		}
3430bbfda8aSnia	}
3440bbfda8aSnia	free(pref);
3450bbfda8aSnia
3460bbfda8aSnia	/* Set the tail to loop back to the head */
3470bbfda8aSnia	if(tail != NULL) {
3480bbfda8aSnia		tail->next = head;
3490bbfda8aSnia	}
3500bbfda8aSnia
3510bbfda8aSnia	/* Warn if we got nothing */
3520bbfda8aSnia	if(head == NULL) {
3530bbfda8aSnia		fprintf(stderr, "Cannot find any image frames for '%s'\n", name);
3540bbfda8aSnia	}
3550bbfda8aSnia
3560bbfda8aSnia	return head;
3570bbfda8aSnia}
358