
/* autopano-sift, Automatic panorama image creation
 * Copyright (C) 2004 -- Sebastian Nowozin
 *
 * This program is free software released under the GNU General Public
 * License, which is included in this software package (doc/LICENSE).
 */

/* GUIImage.cs
 *
 * Gateway classes between Gtk# GUI classes and Image* image classes.
 *
 * (C) Copyright 2004 -- Sebastian Nowozin (nowozin@cs.tu-berlin.de)
 */

using System;
using System.Collections;


// Display containing two aligned pictures in one pixel buffer.
//
// The two images are aligned in a way to minimize the maximum the
// overall resulting x and y dimensions. That is, if both images are more wide
// than heigh, they are aligned up/down. Otherwise they are aligned left/right
// otherwise. In all cases, the first image is located at the top/left
// position.
public class DisplayTwoImages : DisplayImage
{
	private DisplayTwoImages ()
	{
	}

	int xDiv, yDiv;
	// (XDiv, YDiv) denote the pixel position in the single large pixel buffer
	// where the second image starts. The first image always starts at (0, 0).
	public int XDiv {
		get {
			return (xDiv);
		}
	}
	public int YDiv {
		get {
			return (yDiv);
		}
	}

	// Constructor for a two-picture pannel. There are two images given by
	// their filenames, which are loaded into a single pixel buffer.
	public DisplayTwoImages (string filename1, string filename2)
	{
		DisplayImage img1 = new DisplayImage (filename1);
		DisplayImage img2 = new DisplayImage (filename2);

		int yMax = img1.Pbuf.Height;
		if (img2.Pbuf.Height > yMax)
			yMax = img2.Pbuf.Height;

		int xMax = img1.Pbuf.Width;
		if (img2.Pbuf.Width > xMax)
			xMax = img2.Pbuf.Width;

		// If its more high than wide, choose horizontal layout
		if (yMax >= xMax) {
			xDiv = img1.Pbuf.Width;
			yDiv = 0;
			pbuf = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, false, 8,
				img1.Pbuf.Width + img2.Pbuf.Width, yMax);
		} else {
			xDiv = 0;
			yDiv = img1.Pbuf.Height;
			pbuf = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, false, 8,
				xMax, img1.Pbuf.Height + img2.Pbuf.Height);
		}

		pbuf.Fill (0x000000);

		// FIXME: gtk# brokenness (temporary, will be fixed)
		unsafe {
		byte *pbufPixels = (byte *) pbuf.Pixels;
		byte *img1PbufPixels = (byte *) img1.Pbuf.Pixels;
		byte *img2PbufPixels = (byte *) img2.Pbuf.Pixels;

		// Copy content of first image.
		for (int y = 0 ; y < img1.Pbuf.Height ; ++y) {
			for (int x = 0 ; x < img1.Pbuf.Width ; ++x) {
				for (int n = 0 ; n < 3 ; ++n) {
					pbufPixels[y * pbuf.Rowstride + x * pbuf.NChannels + n] =
						img1PbufPixels[y * img1.Pbuf.Rowstride + x * img2.Pbuf.NChannels + n];
				}
			}
		}

		// Copy content of second image right to the first.
		for (int y = 0 ; y < img2.Pbuf.Height ; ++y) {
			for (int x = 0 ; x < img2.Pbuf.Width ; ++x) {
				for (int n = 0 ; n < 3 ; ++n) {
					pbufPixels[(y + yDiv) * pbuf.Rowstride +
						(x + xDiv) * pbuf.NChannels + n] =
						img2PbufPixels[y * img2.Pbuf.Rowstride + x * img2.Pbuf.NChannels + n];
				}
			}
		}
		}
	}
}

// GUI Image display class, showing a single picture and handling the
// image loading.
public class DisplayImage : BasicImagingInterface
{
	protected Gdk.Pixbuf pbuf;
	public Gdk.Pixbuf Pbuf {
		get {
			return (pbuf);
		}
	}

	public override int Width {
		get {
			return (pbuf.Width);
		}
	}
	public override int Height {
		get {
			return (pbuf.Height);
		}
	}

	internal DisplayImage ()
	{
	}

	string filename;
	public DisplayImage (string filename)
	{
		this.filename = filename;
		pbuf = new Gdk.Pixbuf (filename);
	}

	public DisplayImage (string filename, Gdk.Pixbuf pbuf)
	{
		this.filename = filename;
		this.pbuf = pbuf;
	}

	// initialize the image from an image map, with 'setMax' being the
	// white-value to use
	public DisplayImage (ImageMap map, double setMax)
	{
		// generate a 24 bit rgb pixbuf without alphachannel
		pbuf = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, false, 8, map.XDim, map.YDim);
		// set to black (highest 8 bits are ignored, as no alpha channel is used)
		pbuf.Fill (0x000000);

		// FIXME
		unsafe {
		byte *pbufPixels = (byte *) pbuf.Pixels;

		for (int y = 0 ; y < map.YDim ; ++y) {
			for (int x = 0 ; x < map.XDim ; ++x) {
				byte grayVal = (byte) ((map[x, y] * 255.0) / setMax);

				for (int n = 0 ; n < 3 ; ++n)
					pbufPixels[y * pbuf.Rowstride + x * pbuf.NChannels + n] = grayVal;
			}
		}
		}
	}

	public override double ScaleWithin (int dim)
	{
		if (pbuf.Width <= dim && pbuf.Height <= dim)
			return (1.0);

		double xScale = ((double) dim / pbuf.Width);
		double yScale = ((double) dim / pbuf.Height);
		double smallestScale = xScale <= yScale ? xScale : yScale;

		pbuf = pbuf.ScaleSimple ((int) (pbuf.Width * smallestScale),
			(int) (pbuf.Height * smallestScale), Gdk.InterpType.Hyper);

		return (smallestScale);
	}

	public void DrawLine (int x0, int y0, int x1, int y2, byte r, byte g, byte b)
	{
		ArrayList points = BresenhamLine.DrawLine (x0, y0, x1, y2);

		foreach (Point point in points) {
			if (point.y < 0 || point.y >= pbuf.Height)
				continue;
			if (point.x < 0 || point.x >= pbuf.Width)
				continue;
			int pCoord = point.y * pbuf.Rowstride + point.x * pbuf.NChannels;

			unsafe {
			byte *pbufPixels = (byte *) pbuf.Pixels;

			pbufPixels[pCoord + 0] = r;
			pbufPixels[pCoord + 1] = g;
			pbufPixels[pCoord + 2] = b;
			}
		}
	}

	public void DrawCircle (int x, int y, int radius, byte r, byte g, byte b)
	{
		Circle circ = new Circle ();
		ArrayList points = circ.DrawWithRadius (radius);

		// FIXME
		unsafe {
		byte *pbufPixels = (byte *) pbuf.Pixels;

		foreach (Point point in points) {
			if ((y + point.y) < 0 || (y + point.y) >= pbuf.Height)
				continue;
			if ((x + point.x) < 0 || (x + point.x) >= pbuf.Width)
				continue;
			int pCoord = (y + point.y) * pbuf.Rowstride +
				(x + point.x) * pbuf.NChannels;

			pbufPixels[pCoord + 0] = r;
			pbufPixels[pCoord + 1] = g;
			pbufPixels[pCoord + 2] = b;
		}
		}
	}

	// Convert the current image to an ImageMap object using the per-pixel
	// converter 'pconv'. In case 'pconv' is null, the default 1/3 grayscale
	// operator is used.
	public override ImageMap ConvertToImageMap (IPixelConverter pconv)
	{
		if (pconv == null)
			pconv = new CanonicalPixelConverter ();

		ImageMap res = new ImageMap (pbuf.Width, pbuf.Height);

		unsafe {
		byte *pbufPixels = (byte *) pbuf.Pixels;

		for (int y = 0 ; y < pbuf.Height ; ++y) {
			for (int x = 0 ; x < pbuf.Width ; ++x) {
				int byteOffset = y * pbuf.Rowstride + x * pbuf.NChannels;

				res[x, y] = pconv.Convert (pbufPixels[byteOffset + 0],
					pbufPixels[byteOffset + 1], pbufPixels[byteOffset + 2]);
			}
		}
		}

		return (res);
	}
}


