/* -*- Mode: C; tab-width: 4 -*- */
/* image --- image bouncer */

#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)image.c	5.00 2000/11/01 xlockmore";

#endif

/*-
 * Copyright (c) 1991 by Patrick J. Naughton.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * Revision History:
 * 01-Nov-2000: Allocation checks
 * 10-May-1997: Compatible with xscreensaver
 * 03-Nov-1995: Patched to add an arbitrary xpm file.
 * 21-Sep-1995: Patch if xpm fails to load <Markus.Zellner@anu.edu.au>.
 * 17-Jun-1995: Pixmap stuff of Skip_Burrell@sterling.com added.
 * 07-Dec-1994: Icons are now better centered if do not exactly fill an area.
 * 29-Jul-1990: Written.
 */

#ifdef STANDALONE
#define MODE_image
#define PROGCLASS "Image"
#define HACK_INIT init_image
#define HACK_DRAW draw_image
#define image_opts xlockmore_opts
#define DEFAULTS "*delay: 2000000 \n" \
 "*count: -10 \n" \
 "*ncolors: 200 \n" \
 "*bitmap: \n"
#include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"		/* in xlockmore distribution */
#include "color.h"
#endif /* STANDALONE */
#include "iostuff.h"

#ifdef MODE_image

ModeSpecOpt image_opts =
{0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};

#ifdef USE_MODULES
ModStruct   image_description =
{"image", "init_image", "draw_image", "release_image",
 "refresh_image", "init_image", (char *) NULL, &image_opts,
 2000000, -10, 1, 1, 64, 1.0, "",
 "Shows randomly appearing logos", 0, NULL};

#endif

/* aliases for vars defined in the bitmap file */
#define IMAGE_WIDTH	image_width
#define IMAGE_HEIGHT	image_height
#define IMAGE_BITS	image_bits

#include "image.xbm"

#ifdef HAVE_XPM
#define IMAGE_NAME	image_name
#include "image.xpm"
#define DEFAULT_XPM 1
#endif

#define MINICONS 1

typedef struct {
	int         x, y;
	int         color;
} imagetype;

typedef struct {
	int         width, height;
	int         nrows, ncols;
	XPoint      image_offset;
	int         iconcount;
	imagetype  *icons;
	int         graphics_format;
	GC          backGC;
	XImage     *logo;
	Colormap    cmap;
	unsigned long black;
} imagestruct;

static imagestruct *ims = (imagestruct *) NULL;

static void
free_stuff(Display * display, imagestruct * ip)
{
	if (ip->cmap != None) {
		XFreeColormap(display, ip->cmap);
		if (ip->backGC != None) {
			XFreeGC(display, ip->backGC);
			ip->backGC = None;
		}
		ip->cmap = None;
	} else
		ip->backGC = None;
}

static void
free_image(Display * display, imagestruct * ip)
{
	if (ip->icons != NULL) {
		free(ip->icons);
		ip->icons = (imagetype *) NULL;
	}
	free_stuff(display, ip);
	if (ip->logo != None) {
		destroyImage(&ip->logo, &ip->graphics_format);
		ip->logo = None;
	}
}

static Bool
init_stuff(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	imagestruct *ip = &ims[MI_SCREEN(mi)];

	if (ip->logo == None) {
		getImage(mi, &ip->logo, IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_BITS,
#ifdef HAVE_XPM
			 DEFAULT_XPM, IMAGE_NAME,
#endif
			 &ip->graphics_format, &ip->cmap, &ip->black);
		if (ip->logo == None) {
			free_image(display, ip);
			return False;
		}
	}
#ifndef STANDALONE
	if (ip->cmap != None) {
		setColormap(display, window, ip->cmap, MI_IS_INWINDOW(mi));
		if (ip->backGC == None) {
			XGCValues   xgcv;

			xgcv.background = ip->black;
			if ((ip->backGC = XCreateGC(display, window, GCBackground,
					&xgcv)) == None) {
				free_image(display, ip);
				return False;
			}
		}
	} else
#endif /* STANDALONE */
	{
		ip->black = MI_BLACK_PIXEL(mi);
		ip->backGC = MI_GC(mi);
	}
	return True;
}

static void
drawimages(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	imagestruct *ip = &ims[MI_SCREEN(mi)];
	int         i;


	MI_CLEARWINDOWCOLORMAPFAST(mi, ip->backGC, ip->black);
	if (MI_NPIXELS(mi) <= 2)
		XSetForeground(display, ip->backGC, MI_WHITE_PIXEL(mi));
	for (i = 0; i < ip->iconcount; i++) {
		if (ip->logo != NULL && ip->icons[i].x >= 0) {
			if (MI_NPIXELS(mi) > 2 && ip->graphics_format < IS_XPM)
				XSetForeground(display, ip->backGC, MI_PIXEL(mi, ip->icons[i].color));
			(void) XPutImage(display, window, ip->backGC, ip->logo, 0, 0,
			ip->logo->width * ip->icons[i].x + ip->image_offset.x,
					 ip->logo->height * ip->icons[i].y + ip->image_offset.y,
					 ip->logo->width, ip->logo->height);
		}
	}
}

void
init_image(ModeInfo * mi)
{
	imagestruct *ip;
	int         i;

	if (ims == NULL) {
		if ((ims = (imagestruct *) calloc(MI_NUM_SCREENS(mi),
					      sizeof (imagestruct))) == NULL)
			return;
	}
	ip = &ims[MI_SCREEN(mi)];

	if (!init_stuff(mi))
		return;

	ip->width = MI_WIDTH(mi);
	ip->height = MI_HEIGHT(mi);
	if (ip->width > ip->logo->width)
		ip->ncols = ip->width / ip->logo->width;
	else
		ip->ncols = 1;
	if (ip->height > ip->logo->height)
		ip->nrows = ip->height / ip->logo->height;
	else
		ip->nrows = 1;
	ip->image_offset.x = (ip->width - ip->ncols * ip->logo->width) / 2;
	ip->image_offset.y = (ip->height - ip->nrows * ip->logo->height) / 2;
	ip->iconcount = MI_COUNT(mi);
	if (ip->iconcount < -MINICONS)
		ip->iconcount = NRAND(-ip->iconcount - MINICONS + 1) + MINICONS;
	else if (ip->iconcount < MINICONS)
		ip->iconcount = MINICONS;
	if (ip->iconcount > ip->ncols * ip->nrows)
		ip->iconcount = ip->ncols * ip->nrows;
	if (ip->icons != NULL)
		free(ip->icons);
	if ((ip->icons = (imagetype *) malloc(ip->iconcount *
			sizeof (imagetype))) == NULL) {
		free_image(MI_DISPLAY(mi), ip);
		return;
	}
	for (i = 0; i < ip->iconcount; i++)
		ip->icons[i].x = -1;

	MI_CLEARWINDOWCOLORMAP(mi, ip->backGC, ip->black);
}

void
draw_image(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	int         i;
	imagestruct *ip;

	if (ims == NULL)
		return;
	ip = &ims[MI_SCREEN(mi)];
	if (ip->icons == NULL)
		return;

	MI_IS_DRAWN(mi) = True;
	XSetForeground(display, ip->backGC, ip->black);
	for (i = 0; i < ip->iconcount; i++) {
		if ((ip->ncols * ip->nrows > ip->iconcount) && ip->icons[i].x >= 0)
			XFillRectangle(display, window, ip->backGC,
			ip->logo->width * ip->icons[i].x + ip->image_offset.x,
				       ip->logo->height * ip->icons[i].y + ip->image_offset.y,
				       ip->logo->width, ip->logo->height);
		ip->icons[i].x = NRAND(ip->ncols);
		ip->icons[i].y = NRAND(ip->nrows);
		ip->icons[i].color = NRAND(MI_NPIXELS(mi));
	}
	drawimages(mi);
}

void
release_image(ModeInfo * mi)
{
	if (ims != NULL) {
		int         screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
			free_image(MI_DISPLAY(mi), &ims[screen]);
		free(ims);
		ims = (imagestruct *) NULL;
	}
}

void
refresh_image(ModeInfo * mi)
{
#ifdef HAVE_XPM
	imagestruct *ip;

	if (ims == NULL)
		return;
	ip = &ims[MI_SCREEN(mi)];
	if (ip->icons == NULL)
		return;

	if (ip->graphics_format >= IS_XPM) {
		/* This is needed when another program changes the colormap. */
		free_image(MI_DISPLAY(mi), ip);
		init_image(mi);
		return;
	}
#endif
	drawimages(mi);
}

#endif /* MODE_image */
