//
// PImageNative.cc for pekwm
// Copyright (C) 2005 Claes Nasten <pekdon{@}pekdon{.}net>
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//

#include "../config.h"

#include "PImageNative.hh"
#include "PScreen.hh"
#include "ScreenResources.hh"
#include "PixmapHandler.hh"
#include "Util.hh"

#include <iostream>

extern "C" {
#include <X11/Xutil.h>
}

using std::cerr;
using std::endl;
using std::list;
using std::string;

list<PImageNativeLoader*> PImageNative::_loader_list = list<PImageNativeLoader*>();

//! @brief PImageNative constructor.
PImageNative::PImageNative(Display *dpy) : PImage(dpy),
																					 _data(NULL), _has_alpha(false)
{
}

//! @brief PImageNative destructor.
PImageNative::~PImageNative(void)
{
}

//! @brief Loads image from file.
//! @param file File to load.
//! @return Returns true on success, else false.
bool
PImageNative::load(const std::string &file)
{
	string ext(Util::getFileExt(file));
	if (!ext.size()) {
		cerr << " *** WARNING: no file extension on " << file << "!" << endl;
		return false;
	}

	list<PImageNativeLoader*>::iterator it(_loader_list.begin());
	for (; it != _loader_list.end(); ++it) {
		if (!strcasecmp((*it)->getExt(), ext.c_str())) {
			_data = (*it)->load(file, _width, _height, _has_alpha);
			if (_data) {
				_pixmap = createPixmap(_data, _width, _height);
				_mask = createMask(_data, _width, _height);
				break;
			}
		}
	}

	return (_data);
}

//! @brief Frees resources used by image.
void
PImageNative::unload(void)
{
	if (_data) {
		delete [] _data;
	}
	if (_pixmap) {
		ScreenResources::instance()->getPixmapHandler()->returnPixmap(_pixmap);
	}
	if (_mask) {
		ScreenResources::instance()->getPixmapHandler()->returnPixmap(_mask);
	}

	_pixmap = None;
	_mask = None;
	_width = 0;
	_height = 0;
}

//! @brief Draws image on drawable.
//! @param draw Drawable to draw on.
//! @param x Destination x coordinate.
//! @param y Destination y coordinate.
//! @param width Destination width, defaults to 0 which expands to image size.
//! @param height Destination height, defaults to 0 which expands to image size.
void
PImageNative::draw(Drawable draw, int x, int y, uint width, uint height)
{
	if (!_data)
		return;

	// Expand variables.
	if (!width)
		width = _width;
	if (!height)
		height = _height;

	// Draw image.

	// Fixed and Scaled to original size is the same thing.
	if ((_type == IMAGE_TYPE_FIXED)
			|| ((_type == IMAGE_TYPE_SCALED)
					&& (_width == width) && (_height == height))) {
		// Plain copy of the pixmap onto Drawable.
		XCopyArea(_dpy, _pixmap, draw, PScreen::instance()->getGC(),
							0, 0, width, height, x, y);

	} else if (_type == IMAGE_TYPE_SCALED) {
		uchar *scaled_data;
		// Create scaled representation of image.		
		scaled_data = getScaledData(width, height);
		if (scaled_data) {
			Pixmap pix;
			// Create pixmap.
			pix = createPixmap(scaled_data, width, height);
			if (pix) {
				XCopyArea(_dpy, pix, draw, PScreen::instance()->getGC(),
									0, 0, width, height, x, y);
				ScreenResources::instance()->getPixmapHandler()->returnPixmap(pix);
			}

			delete [] scaled_data;
		}
		
	} else if (_type == IMAGE_TYPE_TILED) {
		// Create a GC with _pixmap as tile and tiled fill style.
		GC gc;
		XGCValues gv;

		gv.fill_style = FillTiled;
		gv.tile = _pixmap;
		gv.ts_x_origin = x;
		gv.ts_y_origin = y;

		gc = XCreateGC(_dpy , draw,
									 GCFillStyle|GCTile|GCTileStipXOrigin|GCTileStipYOrigin,
									 &gv);

		// Tile the image onto drawable.
		XFillRectangle(_dpy, draw, gc, x, y, width, height);

		XFreeGC(_dpy, gc);
	}
#ifdef DEBUG
	else {
		cerr << __FILE__ << "@" << __LINE__ << ": "
				 << "PImageNative(" << this << ")::draw(" << draw << ", "
				 << x << " ," << y << ", " << width << ", " << height << ")" << endl
				 << " *** no image type set, not drawing!" << endl;
	}
#endif // DEBUG
}

//! @brief Returns pixmap at sizen.
//! @param need_free Is set to wheter shape mask returned needs to be freed.
//! @param width Pixmap width, defaults to 0 which expands to image size.
//! @param height Pixmap height, defaults to 0 which expands to image size.
//! @return Returns pixmap at size or None on error.
Pixmap
PImageNative::getPixmap(bool &need_free, uint width, uint height)
{
	// Default
	Pixmap pix = None;
	need_free = false;

	// Expand parameters.
	if (!width)
		width = _width;
	if (!height)
		height = _height;

	// Same size, return _pixmap.
	if ((width == _width) && (height == _height)) {
		pix = _pixmap;

	} else {
		uchar *scaled_data;
	
		scaled_data = getScaledData(width, height);
		if (scaled_data) {
			need_free = true;
			pix = createPixmap(scaled_data, width, height);
			delete [] scaled_data;
		}
	}

	return pix;
}

//! @brief Returns shape mask at size, if any.
//! @param need_free Is set to wheter shape mask returned needs to be freed.
//! @param width Shape mask width, defaults to 0 which expands to image size.
//! @param height Shape mask height, defaults to 0 which expands to image size.
//! @return Returns shape mask at size or None if there is no mask information.
Pixmap
PImageNative::getMask(bool &need_free, uint width, uint height)
{
	// Default
	Pixmap pix = None;
	need_free = false;

	// Expand parameters.
	if (!width)
		width = _width;
	if (!height)
		height = _height;

	// Same size, return _mask.
	if ((width == _width) && (height == _height)) {
		pix = _mask;

	} else {
		uchar *scaled_data;
	
		scaled_data = getScaledData(width, height);
		if (scaled_data) {
			need_free = true;
			pix = createMask(scaled_data, width, height);
			delete [] scaled_data;
		}
	}

	return pix;
}

//! @brief Scales image to size.
//! @param width Width to scale image to.
//! @param height Height to scale image to.
void
PImageNative::scale(uint width, uint height)
{
	// Invalid width or height or no need to scale.
	if (!width || !height || ((width == _width) && (height == height)))
		return;
	uchar *scaled_data;

	scaled_data = getScaledData(width, height);
	if (scaled_data) {
		// Free old resources.
		unload();

		// Set data pointer and create pixmap and mask at new size.
		_data = scaled_data;
		_pixmap = createPixmap(_data, width, height);
		_mask = createMask(_data, width, height);
		_width = width;
		_height = height;
	}
}

//! @brief Creates Pixmap from data.
//! @param data Pointer to data to create pixmap from.
//! @param width Width of image data is representing.
//! @param height Height of image data is representing.
//! @return Returns Pixmap on success, else None.
Pixmap
PImageNative::createPixmap(uchar *data, uint width, uint height)
{
	XImage *ximage;
	Pixmap pix = None;

	ximage = createXImage(data, width, height);
	if (ximage) {
		pix = ScreenResources::instance()->getPixmapHandler()->getPixmap(width,
																																		 height,
																																		 PScreen::instance()->getDepth(),
																																		 false);

		XPutImage(_dpy, pix, PScreen::instance()->getGC(), ximage,
							0, 0, 0, 0, width, height);

		delete [] ximage->data;
		ximage->data = NULL;
		XDestroyImage(ximage);
	}

	return pix;	
}

//! @brief Creates shape mask Pixmap from data.
//! @param data Pointer to data to create mask from.
//! @param width Width of image data is representing.
//! @param height Height of image data is representing.
//! @return Returns Pixmap mask on success, else None.
Pixmap
PImageNative::createMask(uchar *data, uint width, uint height)
{
  if (!_has_alpha) {
    return None;
  }

  // Create XImage
  XImage *ximage;
  ximage = XCreateImage(_dpy, PScreen::instance()->getVisual()->getXVisual(),
                        1, ZPixmap, 0, NULL, width, height, 32, 0);
  if (!ximage) {
    cerr << " *** WARNING: unable to create XImage!" << endl;
    return None;
  }

  // Alocate ximage data storage.
  ximage->data = new char[ximage->bytes_per_line * height / sizeof(char)];

  uchar *src = data + 3; // Skip R, G and B.
  ulong pixel_trans, pixel_solid;

  pixel_trans = PScreen::instance()->getBlackPixel();
  pixel_solid = PScreen::instance()->getWhitePixel();

  for (uint y = 0; y < height; ++y) {
    for (uint x = 0; x < width; ++x) {
      XPutPixel(ximage, x, y, (*src > 127) ? pixel_solid : pixel_trans);
      src += 4; // Skip R, G, B and A
    }
  }

  // Create Pixmap
  Pixmap pix;
  pix = ScreenResources::instance()->getPixmapHandler()->getPixmap(width,
                                                                   height,
                                                                   1,
                                                                   true);
  GC gc = XCreateGC(_dpy, pix, 0, NULL);
  XPutImage(_dpy, pix, gc, ximage,
            0, 0, 0, 0, width, height);
  XFreeGC(_dpy, gc);

  delete [] ximage->data;
  ximage->data = NULL;
  XDestroyImage(ximage);

  return pix;
}

//! @brief Createx XImage from data.
//! @param data Pointer to data to create XImage from.
//! @param width Width of image data is representing.
//! @param height Height of image data is representing.
XImage*
PImageNative::createXImage(uchar *data, uint width, uint height)
{
	// Create XImage
	XImage *ximage;
	ximage = XCreateImage(_dpy, PScreen::instance()->getVisual()->getXVisual(),
                          PScreen::instance()->getDepth(), ZPixmap, 0, NULL,
                          width, height, 32, 0);
	if (!ximage) {
		cerr << " *** WARNING: unable to create XImage!" << endl;
		return NULL;
	}

	// Allocate ximage data storage.
	ximage->data = new char[ximage->bytes_per_line * height / sizeof(char)];

	uchar *src = data;
	uchar r, g, b;
	ulong pixel;

	// Put data into XImage.
	for (uint y = 0; y < height; ++y) {
		for (uint x = 0; x < width; ++x) {
			r = *src++;
			g = *src++;
			b = *src++;
			if (_has_alpha)
				*src++;

			// 5 R, 5 G, 5 B (15 bit display)
			if ((ximage->red_mask == 0x7c00)
					&& (ximage->green_mask == 0x3e0) && (ximage->blue_mask == 0x1f)) {
				pixel = ((r << 7) & 0x7c00)
					| ((g << 2) & 0x03e0) | ((b >> 3) & 0x001f);

				// 5 R, 6 G, 5 B (16 bit display)
			} else  if ((ximage->red_mask == 0xf800)
									&& (ximage->green_mask == 0x07e0)
									&& (ximage->blue_mask == 0x001f)) {
				pixel = ((r << 8) &  0xf800)
					| ((g << 3) & 0x07e0) | ((b >> 3) & 0x001f);

			// 8 R, 8 G, 8 B (24/32 bit display)
			} else if  ((ximage->red_mask == 0xff0000)
									&& (ximage->green_mask == 0xff00)
									&& (ximage->blue_mask == 0xff)) {
				pixel = ((r << 16) & 0xff0000)
					| ((g << 8) & 0x00ff00) | (b & 0x0000ff);
			} else {
				pixel = 0;
			}

			XPutPixel(ximage, x, y, pixel);
		}
	}

	return ximage;
}

//! @brief Scales image data and returns pointer to new data.
//! @param width Width of image data to return.
//! @param height Height of image data to return.
//! @return Pointer to image data on success, else NULL.
//! @todo Implement decent scaling routine with data [] cache?
uchar*
PImageNative::getScaledData(uint width, uint height)
{
	if (!width || !height)
		return NULL;

	// Calculate aspect ratio.
	float ratio_x, ratio_y;
	ratio_x = static_cast<float>(_width) / static_cast<float>(width);
	ratio_y = static_cast<float>(_height) / static_cast<float>(height);

	// Allocate memory.
	uchar *scaled_data, *dest;
	scaled_data = new uchar[width * height * (_has_alpha ? 4 : 3)];
	dest = scaled_data;

	// Scale image.
	int i_src;
	float f_src;
	for (uint y = 0; y < height; ++y) {
		f_src = static_cast<int>(ratio_y * y) * _width;
		for (uint x = 0; x < width; ++x) {
			i_src = static_cast<int>(f_src);
			i_src *= (_has_alpha ? 4 : 3);

			*dest++ = _data[i_src++];
			*dest++ = _data[i_src++];
			*dest++ = _data[i_src++];
			if (_has_alpha)
				*dest++ = _data[i_src++];

			f_src += ratio_x;
		}
	}

	return scaled_data;
}


syntax highlighted by Code2HTML, v. 0.9.1