/*-
 * Copyright (c) 2001 Jordan DeLong
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "wm.h"

/* flag if mitshm is available and usable */
static int image_mitshm		= 0;

/* determine if we can use MITSHM */
void image_init() {
	if (options.wantmitshm) {
		image_mitshm = XShmQueryExtension(display);
		if (image_mitshm)
			warnx("using MITSHM for images");
	} else
		image_mitshm = 0;
}

/* create an ximage, using MITSHM if possible */
static XImage *createximg(screen_t *screen, int width, int height,
		XShmSegmentInfo **shminfo, int fallback) {
	XImage *ximage;

	/* use mitshm if available */
	if (!image_mitshm)
		goto nomitshm;

	/* create the seginfo and the ximage */
	*shminfo = malloc(sizeof(XShmSegmentInfo));
	if (!*shminfo)
		return NULL;
	ximage = XShmCreateImage(display, DefaultVisual(display, screen->num),
		DefaultDepth(display, screen->num), ZPixmap, NULL,
		*shminfo, width, height);
	if (!ximage)
		goto free1;

	/* get a shared memory segment */
	(*shminfo)->shmid = shmget(IPC_PRIVATE,
		ximage->bytes_per_line * ximage->height, IPC_CREAT | 0777);
	if ((*shminfo)->shmid == -1)
		goto free2;
	(*shminfo)->shmaddr = ximage->data = shmat((*shminfo)->shmid, 0, 0);
	(*shminfo)->readOnly = 0;

	/* try to attach the server */
	if (XShmAttach(display, *shminfo) == 0)
		goto free3;
	XSync(display, 0);

	/* cause the seg to remove when all procs detach */
	shmctl((*shminfo)->shmid, IPC_RMID, 0);

	/* if we get here we succeeded with shm */
	return ximage;

	/* falls through to try a non shm ximage */
free3:
	shmctl((*shminfo)->shmid, IPC_RMID, 0);
	shmdt((*shminfo)->shmaddr);
free2:
	XDestroyImage(ximage);
free1:
	free(*shminfo);
	*shminfo = NULL;
	warnx("mitshm failed; falling back to normal ximage");

nomitshm:
	/* only proceed if non-mitshm are ok */
	if (!fallback)
		return NULL;

	/* if mitshm isn't available, just use normal ximage */
	ximage = XCreateImage(display, DefaultVisual(display, screen->num),
		DefaultDepth(display, screen->num), ZPixmap, 0,
		NULL, width, height, 8, 0);

	/* allocate memory for the image data */
	ximage->data = malloc(ximage->bytes_per_line * height);
	if (!ximage->data) {
		XDestroyImage(ximage);
		return NULL;
	}

	return ximage;
}

/* create an image of a particular size */
image_t *image_create(screen_t *screen, int width, int height) {
	image_t *image;

	/* get memory for the image_t */
	image = calloc(1, sizeof(image_t));
	if (!image)
		return NULL;
	image->screen = screen;

	/* create the ximage */
	image->ximage = createximg(screen, width, height, &image->shminfo, 1);
	if (!image->ximage)
		goto free;
	image->shmimg = image->shminfo != NULL;

	return image;

free:
	free(image);
	return NULL;
}

/* create an image_t out of a pixmap for a particular screen */
image_t *image_frompixmap(pixmap_t *pixmap, screen_t *screen) {
	image_t *image;

	/* get memory for the image_t */
	image = calloc(1, sizeof(image_t));
	if (!image)
		return NULL;
	image->screen = screen;

	/* for shm we need to create the shm image first */
	if (image_mitshm) {
		image->ximage = createximg(screen, pixmap->width,
			pixmap->height, &image->shminfo, 0);
		if (!image->ximage)
			goto nomitshm;
		image->shmimg = 1;
		XShmGetImage(display, pixmap->pixmaps[screen->num],
				image->ximage, 0, 0, AllPlanes);

		return image;
	}

nomitshm:
	image->ximage = XGetImage(display, pixmap->pixmaps[screen->num],
		0, 0, pixmap->width, pixmap->height, AllPlanes, ZPixmap);

	return image;
}

/* destroy an image_t */
void image_destroy(image_t *image) {
	if (image->shmimg) {
		XShmDetach(display, image->shminfo);
		shmdt(image->shminfo->shmaddr);
		free(image->shminfo);
	}
	XDestroyImage(image->ximage);
	free(image);
}

/* wrap image putting for abstraction of shm images */
void image_put(image_t *image, Drawable d, GC gc, int src_x, int src_y,
		int dest_x, int dest_y, int width, int height) {
	if (image->shmimg)
		XShmPutImage(display, d, gc, image->ximage, src_x, src_y,
			dest_x, dest_y, width, height, 0);
	else
		XPutImage(display, d, gc, image->ximage, src_x, src_y,
			dest_x, dest_y, width, height);
	XFlush(display);
}

/* make a copy of an image */
image_t *image_clone(image_t *src) {
	image_t *dest;

	/* get memory for the new image */
	dest = calloc(1, sizeof(image_t));
	if (!dest)
		return NULL;
	dest->screen = src->screen;

	/* copy the image */
	if (src->shmimg) {
		dest->ximage = createximg(src->screen,
			src->ximage->width, src->ximage->height,
			&dest->shminfo, 0);
		if (!dest->ximage)
			goto nomitshm;
		dest->shmimg = 1;
		memcpy(dest->ximage->data, src->ximage->data,
			dest->ximage->bytes_per_line * dest->ximage->width);
		return dest;
	}

nomitshm:
	dest->ximage = XSubImage(src->ximage, 0, 0,
		src->ximage->width, src->ximage->height);

	return dest;
}

/* scale an image to the size specified */
image_t *image_scale(image_t *src, int width, int height) {
	image_t *dest;
	Pixel pixel;
	double dx, dy;
	double srcx, srcy;
	int x, y;

	/* can't size to nothing */
	assert(width > 0 && height > 0);

	/*
	 * if the requested size is the current size,
	 * just return a copy of the image.
	 */
	if (width == src->ximage->width
			&& height == src->ximage->height)
		return image_clone(src);

	/* create the destination image */
	dest = image_create(src->screen, width, height);
	if (!dest)
		return NULL;

	/* find the ratio of the old dims to new */
	dx = (double) src->ximage->width / (double) width;
	dy = (double) src->ximage->height / (double) height;

	/*
	 * perform the scaling; this is probably much slower than
	 * it could be...
	 */
	for (y = 0, srcy = 0; y < height; y++, srcy += dy)
		for (x = 0, srcx = 0; x < width; x++, srcx += dx) {
			pixel = XGetPixel(src->ximage,
				(int) srcx, (int) srcy);
			XPutPixel(dest->ximage, x, y, pixel);
		}

	return dest;
}
