Tiny Planets Coding

The other day on Google+ I read a post from Ana Andres talking about coding Tiny Planets, and how it was running really slow in Matlab somewhere in the pixel drawing code.

That got me thinking about how I would code it in C#, and Ana had a good blog post showing one way of thinking about how the transformation was happening , one of her goals was to make an animated GIF so people could see the wrap bend happening.

Tiny Planet Code A
Tiny Planet A GIF

Now the this was plenty fast enough, I was getting 2-3 fps on my laptop using only one core, so from a speed perspective moved on.

My first plan was to interpolate between the points (all though that would really slow, and look bad at the outside), but when I showed my co-worker my method, he said I was silly and should just traverse the output space, and find the input pixel that maps, so I did that and got:

Tiny Planet Code C

I then had to put the bend code back in, as that was a special aspect needed for the animation and got:

Tiny Planet D GIF

I then started playing with sub-sampling to make the output less jaggie (F), and then I decided blend in YUV (or YCrCb) colour space, and finally settled on five point YUV average where you have a center of the pixel and the four 1/4 towards the pixel diagonal corners. Givening:

Tiny Planet H GIF

Sorry for GIF being links they are larger than they should be for in-lining..

Now improvement on the process would be to allow scaling and translation of the origin on the polar focus, this might be useful in an interactive UI to allow exploring the space.

Here’s my source code. A to H is the progression, it was fun to learn to make GIFs in C#.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Diagnostics;
using System.Windows.Media.Imaging;
using System.Windows;
using System.IO;

namespace TinyPlanet
{
	class Program
	{
		static void Main(string[] args)
		{
			Bitmap src = new Bitmap(@"C:\temp\tinyplanet-1.png");

			//GifA(src);

			//Bitmap dsta = Bend_A(src, i);
			//dsta.Save(@"C:\temp\tinyplanet-out_A.png");

			//Bitmap dstc = Bend_C(src, 100);
			//dstc.Save(@"C:\temp\tinyplanet-out_C.png");

			//GifD(src);

			//Bitmap dstd = Bend_D(src, 60);
			//dstd.Save(@"C:\temp\tinyplanet-out_D.png");

			//Bitmap dste = Bend_E(src, 60);
			//dste.Save(@"C:\temp\tinyplanet-out_E.png");

			//Bitmap dstf = Bend_F(src, 60);
			//dstf.Save(@"C:\temp\tinyplanet-out_F.png");

			//Bitmap dstg = Bend_G(src, 60);
			//dstg.Save(@"C:\temp\tinyplanet-out_G.png");

			//Bitmap dsth = Bend_H(src, 60);
			//dsth.Save(@"C:\temp\tinyplanet-out_H.png");

			GifH(src);
		}

		static void GifA(Bitmap src)
		{
			GifBitmapEncoder gEnc = new GifBitmapEncoder();

			for (int i = 0; i <= 100; i++)
			{
				Bitmap dst = Bend_A(src, i);
				Debug.WriteLine(string.Format("frame {0}", i));

				var frame = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
						dst.GetHbitmap(),
						IntPtr.Zero,
						Int32Rect.Empty,
						BitmapSizeOptions.FromEmptyOptions());
				gEnc.Frames.Add(BitmapFrame.Create(frame));
			}

			gEnc.Save(new FileStream(@"C:\temp\tinyplanet-out_A.gif", FileMode.Create));
		}

		static void GifD(Bitmap src)
		{
			GifBitmapEncoder gEnc = new GifBitmapEncoder();

			for (int i = 0; i <= 100; i++)
			{
				Bitmap dst = Bend_D(src, i);
				Debug.WriteLine(string.Format("frame {0}", i));

				var frame = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
						dst.GetHbitmap(),
						IntPtr.Zero,
						Int32Rect.Empty,
						BitmapSizeOptions.FromEmptyOptions());
				gEnc.Frames.Add(BitmapFrame.Create(frame));
			}

			gEnc.Save(new FileStream(@"C:\temp\tinyplanet-out_D.gif", FileMode.Create));
		}

		static void GifH(Bitmap src)
		{
			GifBitmapEncoder gEnc = new GifBitmapEncoder();

			for (int i = 0; i <= 100; i++)
			{
				Bitmap dst = Bend_H(src, i);
				Debug.WriteLine(string.Format("frame {0}", i));

				var frame = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
						dst.GetHbitmap(),
						IntPtr.Zero,
						Int32Rect.Empty,
						BitmapSizeOptions.FromEmptyOptions());
				gEnc.Frames.Add(BitmapFrame.Create(frame));
			}

			gEnc.Save(new FileStream(@"C:\temp\tinyplanet-out_H.gif", FileMode.Create));
		}

		static Bitmap Bend_A(Bitmap src, int bend_i)
		{
			int src_half_width = src.Width / 2;
			int dst_width = (src.Height * 2) + src.Width;
			int dst_height = src.Height * 2;
			int dst_origin_x = dst_width / 2;
			int dst_origin_y = dst_height / 2;
		
			double bend = bend_i / 100.0; // turn to percentage;

			int bend_x = (int)(src_half_width * (1.0 - bend));
			double bent_pixels = src_half_width - bend_x;
			double final_ang_d = 180.0 * bend;
			//Debug.WriteLine(string.Format("final_ang_d {0}", final_ang_d));
			double final_ang = final_ang_d * (Math.PI / 180.0);


			Bitmap dst = new Bitmap(dst_width, dst_height);
			Graphics g = Graphics.FromImage(dst);
			g.Clear(Color.White);
			g.DrawImage(dst, 0, 0, dst.Width, dst.Height);


			for (int x = 0; x < src_half_width; x++)
			{
				if (x >= bend_x)
				{
					// do polar
					int bx = x - bend_x;

					double rad = ((bx / bent_pixels) * final_ang) + (Math.PI / 2.0);
					//Debug.WriteLine(string.Format("bend % {0} ang {1}", bx / bent_pixels, rad * (180 / Math.PI)));
					double sin = Math.Sin(rad);
					double cos = Math.Cos(rad);


					for (int y = 0; y < src.Height; y++)
					{
						dst.SetPixel(dst_origin_x - bend_x + (int)(cos * y), dst_origin_y - (int)(sin * y), src.GetPixel(src_half_width - x, src.Height - y - 1));
						dst.SetPixel(dst_origin_x + bend_x - (int)(cos * y), dst_origin_y - (int)(sin * y), src.GetPixel(src_half_width + x, src.Height - y - 1));
					}
				}
				else
				{
					// do rectilinear
					for (int y = 0; y < src.Height; y++)
					{
						// left side
						dst.SetPixel(dst_origin_x - x, dst_origin_y - y, src.GetPixel(src_half_width - x, src.Height - y - 1));

						// right side
						dst.SetPixel(dst_origin_x + x, dst_origin_y - y, src.GetPixel(src_half_width + x, src.Height - y - 1));
					}
				}
			}

			return dst;
		}

		static Bitmap Bend_C(Bitmap src, int bend_i)
		{
			int src_half_width = src.Width / 2;
			int dst_width = (src.Height * 2) + src.Width;
			int dst_height = src.Height * 2;
			int dst_origin_x = dst_width / 2;
			int dst_origin_y = dst_height / 2;

			double bent_pixels = src_half_width;
			double final_ang = Math.PI;


			Bitmap dst = new Bitmap(dst_width, dst_height);
			for (int x = 0; x < dst_width; x++)
			{
				for (int y = 0; y < dst_height; y++)
				{
					// map from output to input
					int dx = x - dst_origin_x;
					int dy = y - dst_origin_y;

					double r = Math.Sqrt((dx * dx) + (dy * dy));
					double q = Math.Atan2(dy, dx);

					double pic_ang = q + (Math.PI / 2.0);
					double mod_ang = ((pic_ang + Math.PI) % (Math.PI * 2.0)) - Math.PI;

					int dev_x = (int)((mod_ang / final_ang) * bent_pixels);
					int dev_y = (int)r;

					//Debug.WriteLine(string.Format("x {0} y {1} dx {2} dy{3}, r {4}, q {5}, dev_x {6}, pic_ang {7}", x, y, dx, dy, r, q, dev_x, pic_ang));

					if (Math.Abs(dev_x) <= src_half_width &&
						dev_y < src.Height)
					{
						dst.SetPixel(x, y, src.GetPixel(dev_x + src_half_width, src.Height - dev_y - 1));
					}
				}
			}

			return dst;
		}

		static Bitmap Bend_D(Bitmap src, int bend_i)
		{
			int src_half_width = src.Width / 2;
			int dst_width = (src.Height * 2) + src.Width;
			int dst_height = src.Height * 2;
			int dst_origin_x = dst_width / 2;
			int dst_origin_y = dst_height / 2;

			double bend = bend_i / 100.0; // turn to percentage;


			int bend_x = (int)(src_half_width * (1.0 - bend));
			double bent_pixels = src_half_width - bend_x;
			double final_ang_d = 180.0 * bend;
			//Debug.WriteLine(string.Format("final_ang_d {0}", final_ang_d));
			double final_ang = final_ang_d * (Math.PI / 180.0);


			Bitmap dst = new Bitmap(dst_width, dst_height);
			Graphics g = Graphics.FromImage(dst);
			g.Clear(Color.White);
			g.DrawImage(dst, 0, 0, dst.Width, dst.Height);

			int bend_start_x = dst_origin_x - bend_x;
			int bend_end_x = dst_origin_x + bend_x;

			for (int x = 0; x < dst_width; x++)
			{
				int fix_x = (x < dst_origin_x ? bend_x : -bend_x);
				for (int y = 0; y < dst_height; y++)
				{
					if (x > bend_start_x &&
						x < bend_end_x)
					{
						// rectanliner 
						int ox = (x - bend_start_x) + (src_half_width - bend_x);

						if (y < src.Height)
						{
							dst.SetPixel(x, y, src.GetPixel(ox, y));
						}
					}
					else
					{
						// map from output to input
						int dx = x - dst_origin_x + fix_x;
						int dy = y - dst_origin_y;

						double r = Math.Sqrt((dx * dx) + (dy * dy));
						double q = Math.Atan2(dy, dx);

						double pic_ang = q + (Math.PI / 2.0);
						double mod_ang = ((pic_ang + Math.PI) % (Math.PI * 2.0)) - Math.PI;

						if (Math.Abs(mod_ang) <= final_ang)
						{
							int dev_x = (int)((mod_ang / final_ang) * bent_pixels) - fix_x + src_half_width;
							int dev_y = (int)r;

							//Debug.WriteLine(string.Format("x {0} y {1} dx {2} dy{3}, r {4}, q {5}, dev_x {6}, pic_ang {7}", x, y, dx, dy, r, q, dev_x, pic_ang));

							if (dev_x < src.Width && dev_x >= 0 && 
								dev_y < src.Height)
							{
								dst.SetPixel(x, y, src.GetPixel(dev_x, src.Height - dev_y - 1));
							}
						}
					}
				}
			}

			return dst;
		}

		static Bitmap Bend_E(Bitmap src, int bend_i)
		{
			int src_half_width = src.Width / 2;
			int dst_width = (src.Height * 2) + src.Width;
			int dst_height = src.Height * 2;
			int dst_origin_x = dst_width / 2;
			int dst_origin_y = dst_height / 2;

			double bend = bend_i / 100.0; // turn to percentage;


			int bend_x = (int)(src_half_width * (1.0 - bend));
			double bent_pixels = src_half_width - bend_x;
			double final_ang_d = 180.0 * bend;
			//Debug.WriteLine(string.Format("final_ang_d {0}", final_ang_d));
			double final_ang = final_ang_d * (Math.PI / 180.0);


			Bitmap dst = new Bitmap(dst_width, dst_height);
			Graphics g = Graphics.FromImage(dst);
			g.Clear(Color.White);
			g.DrawImage(dst, 0, 0, dst.Width, dst.Height);

			int bend_start_x = dst_origin_x - bend_x;
			int bend_end_x = dst_origin_x + bend_x;

			for (int x = 0; x < dst_width; x++)
			{
				int fix_x = (x < dst_origin_x ? bend_x : -bend_x);
				int ox = (x - bend_start_x) + (src_half_width - bend_x);

				for (int y = 0; y < dst_height; y++)
				{
					if (x > bend_start_x &&
						x < bend_end_x)
					{
						// rectanliner 
						if (y < src.Height)
						{
							dst.SetPixel(x, y, src.GetPixel(ox, y));
						}
					}
					else
					{
						// map from output to input
						int dx = x - dst_origin_x + fix_x;
						int dy = y - dst_origin_y;

						dst.SetPixel(x, y, Color.FromArgb(ARGBAtPoint(dx, dy, final_ang, ref src, bent_pixels, fix_x)));
					}
				}
			}

			return dst;
		}

		static Bitmap Bend_F(Bitmap src, int bend_i)
		{
			int src_half_width = src.Width / 2;
			int dst_width = (src.Height * 2) + src.Width;
			int dst_height = src.Height * 2;
			int dst_origin_x = dst_width / 2;
			int dst_origin_y = dst_height / 2;

			double bend = bend_i / 100.0; // turn to percentage;


			int bend_x = (int)(src_half_width * (1.0 - bend));
			double bent_pixels = src_half_width - bend_x;
			double final_ang_d = 180.0 * bend;
			//Debug.WriteLine(string.Format("final_ang_d {0}", final_ang_d));
			double final_ang = final_ang_d * (Math.PI / 180.0);


			Bitmap dst = new Bitmap(dst_width, dst_height);
			Graphics g = Graphics.FromImage(dst);
			g.Clear(Color.White);
			g.DrawImage(dst, 0, 0, dst.Width, dst.Height);

			int bend_start_x = dst_origin_x - bend_x;
			int bend_end_x = dst_origin_x + bend_x;

			for (int x = 0; x < dst_width; x++)
			{
				int fix_x = (x < dst_origin_x ? bend_x : -bend_x);
				int ox = (x - bend_start_x) + (src_half_width - bend_x);

				for (int y = 0; y < dst_height; y++)
				{
					if (x > bend_start_x &&
						x < bend_end_x)
					{
						// rectanliner 
						if (y < src.Height)
						{
							dst.SetPixel(x, y, src.GetPixel(ox, y));
						}
					}
					else
					{
						// map from output to input
						int dx = x - dst_origin_x + fix_x;
						int dy = y - dst_origin_y;

						int p0 = ARGBAtPoint(dx - 0.5, dy - 0.5, final_ang, ref src, bent_pixels, fix_x);
						int p1 = ARGBAtPoint(dx - 0.5, dy + 0.5, final_ang, ref src, bent_pixels, fix_x);
						int p2 = ARGBAtPoint(dx + 0.5, dy - 0.5, final_ang, ref src, bent_pixels, fix_x);
						int p3 = ARGBAtPoint(dx + 0.5, dy + 0.5, final_ang, ref src, bent_pixels, fix_x);

						int R = (((p0 >> 16) & 0xFF) + ((p0 >> 16) & 0xFF) + ((p0 >> 16) & 0xFF) + ((p0 >> 16) & 0xFF)) / 4;
						int G = (((p0 >> 8) & 0xFF) + ((p0 >> 8) & 0xFF) + ((p0 >> 8) & 0xFF) + ((p0 >> 8) & 0xFF)) / 4;
						int B = ((p0 & 0xFF) + (p0 & 0xFF) + (p0 & 0xFF) + (p0 & 0xFF)) / 4;

						dst.SetPixel(x, y, Color.FromArgb(R, G, B));
					}
				}
			}

			return dst;
		}

		static Bitmap Bend_G(Bitmap src, int bend_i)
		{
			int src_half_width = src.Width / 2;
			int dst_width = (src.Height * 2) + src.Width;
			int dst_height = src.Height * 2;
			int dst_origin_x = dst_width / 2;
			int dst_origin_y = dst_height / 2;

			double bend = bend_i / 100.0; // turn to percentage;


			int bend_x = (int)(src_half_width * (1.0 - bend));
			double bent_pixels = src_half_width - bend_x;
			double final_ang_d = 180.0 * bend;
			//Debug.WriteLine(string.Format("final_ang_d {0}", final_ang_d));
			double final_ang = final_ang_d * (Math.PI / 180.0);


			Bitmap dst = new Bitmap(dst_width, dst_height);
			Graphics g = Graphics.FromImage(dst);
			g.Clear(Color.White);
			g.DrawImage(dst, 0, 0, dst.Width, dst.Height);

			int bend_start_x = dst_origin_x - bend_x;
			int bend_end_x = dst_origin_x + bend_x;

			for (int x = 0; x < dst_width; x++)
			{
				int fix_x = (x < dst_origin_x ? bend_x : -bend_x);
				int ox = (x - bend_start_x) + (src_half_width - bend_x);

				for (int y = 0; y < dst_height; y++)
				{
					if (x > bend_start_x &&
						x < bend_end_x)
					{
						// rectanliner 
						if (y < src.Height)
						{
							dst.SetPixel(x, y, src.GetPixel(ox, y));
						}
					}
					else
					{
						// map from output to input
						int dx = x - dst_origin_x + fix_x;
						int dy = y - dst_origin_y;

						int p0 = ARGBAtPoint(dx - 0.5, dy - 0.5, final_ang, ref src, bent_pixels, fix_x);
						int p1 = ARGBAtPoint(dx - 0.5, dy + 0.5, final_ang, ref src, bent_pixels, fix_x);
						int p2 = ARGBAtPoint(dx + 0.5, dy - 0.5, final_ang, ref src, bent_pixels, fix_x);
						int p3 = ARGBAtPoint(dx + 0.5, dy + 0.5, final_ang, ref src, bent_pixels, fix_x);

						int yy, cr, cb;
						int yt = 0, crt = 0, cbt = 0;
						YCbCrFromRGB(p0, out yy, out cr, out cb);
						yt += yy; crt += cr; cbt += cb;
						YCbCrFromRGB(p1, out yy, out cr, out cb);
						yt += yy; crt += cr; cbt += cb;
						YCbCrFromRGB(p2, out yy, out cr, out cb);
						yt += yy; crt += cr; cbt += cb;
						YCbCrFromRGB(p3, out yy, out cr, out cb);
						yt += yy; crt += cr; cbt += cb;

						dst.SetPixel(x, y, Color.FromArgb(ARGBFromYCbCr(yt/4, cbt/4, crt/4)));
					}
				}
			}

			Debug.WriteLine(string.Format("red {0:x4}", Color.Red.ToArgb()));

			return dst;
		}

		static Bitmap Bend_H(Bitmap src, int bend_i)
		{
			int src_half_width = src.Width / 2;
			int dst_width = (src.Height * 2) + src.Width;
			int dst_height = src.Height * 2;
			int dst_origin_x = dst_width / 2;
			int dst_origin_y = dst_height / 2;

			double bend = bend_i / 100.0; // turn to percentage;


			int bend_x = (int)(src_half_width * (1.0 - bend));
			double bent_pixels = src_half_width - bend_x;
			double final_ang_d = 180.0 * bend;
			//Debug.WriteLine(string.Format("final_ang_d {0}", final_ang_d));
			double final_ang = final_ang_d * (Math.PI / 180.0);


			Bitmap dst = new Bitmap(dst_width, dst_height);
			Graphics g = Graphics.FromImage(dst);
			g.Clear(Color.White);
			g.DrawImage(dst, 0, 0, dst.Width, dst.Height);

			int bend_start_x = dst_origin_x - bend_x;
			int bend_end_x = dst_origin_x + bend_x;

			for (int x = 0; x < dst_width; x++)
			{
				int fix_x = (x < dst_origin_x ? bend_x : -bend_x);
				int ox = (x - bend_start_x) + (src_half_width - bend_x);

				for (int y = 0; y < dst_height; y++)
				{
					if (x > bend_start_x &&
						x < bend_end_x)
					{
						// rectanliner 
						if (y < src.Height)
						{
							dst.SetPixel(x, y, src.GetPixel(ox, y));
						}
					}
					else
					{
						// map from output to input
						int dx = x - dst_origin_x + fix_x;
						int dy = y - dst_origin_y;

						int p0 = ARGBAtPoint(dx - 0.25, dy - 0.25, final_ang, ref src, bent_pixels, fix_x);
						int p1 = ARGBAtPoint(dx - 0.25, dy + 0.25, final_ang, ref src, bent_pixels, fix_x);
						int p2 = ARGBAtPoint(dx + 0.25, dy - 0.25, final_ang, ref src, bent_pixels, fix_x);
						int p3 = ARGBAtPoint(dx + 0.25, dy + 0.25, final_ang, ref src, bent_pixels, fix_x);
						int p4 = ARGBAtPoint(dx, dy, final_ang, ref src, bent_pixels, fix_x);

						int yy, cr, cb;
						int yt = 0, crt = 0, cbt = 0;
						YCbCrFromRGB(p0, out yy, out cr, out cb);
						yt += yy; crt += cr; cbt += cb;
						YCbCrFromRGB(p1, out yy, out cr, out cb);
						yt += yy; crt += cr; cbt += cb;
						YCbCrFromRGB(p2, out yy, out cr, out cb);
						yt += yy; crt += cr; cbt += cb;
						YCbCrFromRGB(p3, out yy, out cr, out cb);
						yt += yy; crt += cr; cbt += cb;
						YCbCrFromRGB(p4, out yy, out cr, out cb);
						yt += yy; crt += cr; cbt += cb;

						dst.SetPixel(x, y, Color.FromArgb(ARGBFromYCbCr(yt / 5, cbt / 5, crt / 5)));
					}
				}
			}

			Debug.WriteLine(string.Format("red {0:x4}", Color.Red.ToArgb()));

			return dst;
		}

		static void YCbCrFromRGB(int RGB, out int Y, out int Cb, out int Cr)
		{
			int r = (RGB >> 16) & 0xFF;
			int g = (RGB >> 8) & 0xFF;
			int b = RGB & 0xFF;
			Y = (int)(0.299 * r + 0.587 * g + 0.114 * b);
			Cb = (int)(128 - 0.169 * r - 0.331 * g + 0.500 * b);
			Cr = (int)(128 + 0.500 * r - 0.419 * g - 0.081 * b);
		}

		static int ARGBFromYCbCr(int Y, int Cb, int Cr)
		{
			int r = Y + (int)((1.4f * (Cb - 128))+0.5);
			int g = Y + (int)((-0.343f * (Cr - 128) - 0.711f * (Cb - 128))+0.5);
			int b = Y + (int)((1.765f * (Cr - 128)) + 0.5);
			return (int)(0xFF000000 + (r << 16) + (g << 8) + (b));
		}


		static int ARGBAtPoint(double dx, double dy, double final_ang, ref Bitmap src, double bent_pixels, int fix_x)
		{
			double r = Math.Sqrt((dx * dx) + (dy * dy));
			double q = Math.Atan2(dy, dx);

			double pic_ang = q + (Math.PI / 2.0);
			double mod_ang = ((pic_ang + Math.PI) % (Math.PI * 2.0)) - Math.PI;

			if (Math.Abs(mod_ang) <= final_ang)
			{
				int dev_x = (int)((mod_ang / final_ang) * bent_pixels) - fix_x + (src.Width/2);
				int dev_y = (int)r;

				//Debug.WriteLine(string.Format("x {0} y {1} dx {2} dy{3}, r {4}, q {5}, dev_x {6}, pic_ang {7}", x, y, dx, dy, r, q, dev_x, pic_ang));

				if (dev_x < src.Width && dev_x >= 0 &&
					dev_y < src.Height)
				{
					return src.GetPixel(dev_x, src.Height - dev_y - 1).ToArgb();
				}
			}

			return Color.White.ToArgb();
		}
	}
		
}

Why my file operations fail on the Mac

My first thought for why things fails is the the Environment.SpecialFolder don’t map, and thus I found this page talking about it

So I ran the code on my Windows XP box and got:

Desktop                 C:\Documents and Settings\spilgrim\Desktop
Programs                C:\Documents and Settings\spilgrim\Start Menu\Programs
Personal                C:\Documents and Settings\spilgrim\My Documents
Personal                C:\Documents and Settings\spilgrim\My Documents
Favorites               C:\Documents and Settings\spilgrim\Favorites
Startup                 C:\Documents and Settings\spilgrim\Start Menu\Programs\Startup
Recent                  C:\Documents and Settings\spilgrim\Recent
SendTo                  C:\Documents and Settings\spilgrim\SendTo
StartMenu               C:\Documents and Settings\spilgrim\Start Menu
MyMusic                 C:\Documents and Settings\spilgrim\My Documents\My Music
DesktopDirectory        C:\Documents and Settings\spilgrim\Desktop
MyComputer
Templates               C:\Documents and Settings\spilgrim\Templates
ApplicationData         C:\Documents and Settings\spilgrim\Application Data
LocalApplicationData    C:\Documents and Settings\spilgrim\Local Settings\Application Data
InternetCache           C:\Documents and Settings\spilgrim\Local Settings\Temporary Internet Files
Cookies                 C:\Documents and Settings\spilgrim\Cookies
History                 C:\Documents and Settings\spilgrim\Local Settings\History
CommonApplicationData   C:\Documents and Settings\All Users\Application Data
System                  C:\WINDOWS\system32
ProgramFiles            C:\Program Files
MyPictures              C:\Documents and Settings\spilgrim\My Documents\My Pictures
CommonProgramFiles      C:\Program Files\Common Files

and on my MacBook I get

Desktop                 /Users/parents/Desktop
Programs
Personal                /Users/parents
Favorites
Startup
Recent
SendTo
StartMenu
MyMusic                 /Users/parents/Music
DesktopDirectory        /Users/parents/Desktop
MyComputer
Templates
ApplicationData         /Users/parents/.config
LocalApplicationData    /Users/parents/.local/share
InternetCache
Cookies
History
CommonApplicationData   /usr/share
System
ProgramFiles
MyPictures              /Users/parents/Pictures
CommonProgramFiles

One question raised on that page is why are there two Personal values, and it’s because MyDocuments is the second Enumeration, but .ToString() only names the first in order of an enum set.

So the mystery deepens as I’m using Environment.SpecialFolder.Personal, and therefore it should work, but it seems to be failing to create the data directory, time to explore more…

After some a bit more playing around I found the file “SAVE/player.cha” in my Mac folder structure, and realised I was doing string concatenation with DOS slashes, etc. hard coded all over the place, so I refactored the code, and now it saves/loads files fine.

I then created a Mac .app bundle, so you can now just download form Google Code the Mac OS X version of the game, un-tar/gzip it, and as long as you have Mono installed, tada, instant game action. Albeit slow…

Only problem to solve is that the save files are case sensitive, in how they are searched and managed, and I’ve not got a standard schema in place. I may have to build some wrapper code to manage this, so odd DOS/Windows casing works fine on Mac/Linux systems.

Also I’ll have to make a Linux tar ball for those Linux/Mono people to test play with.

SafeArrayTypeMismatchException

We extended our legacy C++ DCOM application last week, and when the developer wrote the C#.Net end to call the new method, we were getting the error:

System.Runtime.InteropServices.SafeArrayTypeMismatchException

The developer that had added the method stated it work, and pointed to his Delphi test app that worked happily.

Reviewing the method code it all looked fine. Single stepping through the code there where no problems.

The help for this exception says:

The exception thrown when the type of the incoming SAFEARRAY does not match the type specified in the managed signature.

But as we are dynamically calling the method like this:

objAddType = Type.GetTypeFromProgID("DCOM_OBJECT_NAME.COMPANY_CLASS_A");
objAdd = Activator.CreateInstance(objAddType);

object[] input = {};

object result = objAddType.InvokeMember("MethodName", BindingFlags.InvokeMethod, null, objAdd, input);

There was no was no signature.

This is for this Method:

VARIANT CompanyClassAObject::MethodName(void)
{
    VARIANT vaResult;
    VariantInit(&vaResult);

    SAFEARRAY* sfa = GetSafeArray();

    vaResult.vt = VT_UI1 | VT_ARRAY;
    vaResult.parray = sfa;

    return vaResult;
}

with the body of GetSafeArray looked like:

    SAFEARRAYBOUND sfabounds[1];
    sfabounds[0].lLbound = 0;
    sfabounds[0].cElements = bytes_needed;
    SAFEARRAY *sfa = SafeArrayCreate(VT_I1, 1, sfabounds);
    if(sfa)
    {
        void *data;
        if(SafeArrayAccessData(sfa, &data) == S_OK)
        {
            memcpy(data, buffer, bytes_needed);
            SafeArrayUnaccessData(sfa);

            delete buffer;
            return sfa;
        }
    }

The problem ended up being that the SafeArray is created as VT_I1 type but when it is put into the variant type it was typed as VT_UI1.  So the .Net runtime was picking up the conflict and correctly complaining, and once you know what the error is, the exception message make sense.  Funny that!

Setting the SafeArrayCreate to use VT_UI1 and every thing worked.

Creating Palette based GIFs

A question was asked on the mailing list (ages ago) about creating 8-bit GIF files.  I proved this code for simple palette based GIFs, so here it is:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Project
{
    class ProjectMain
    {
        public static void Main()
        {
            GenerateBackgroundImage(Color.AliceBlue, Color.Beige, Color.Cyan, 10, 10, 10, @"c:\image.gif");
        }

        public static void GenerateBackgroundImage(Color leftColour, Color centerColour,
            Color rightColour, int leftWidth, int centerWidth, int rightWidth, string path)
        {

            int width = leftWidth + centerWidth + rightWidth + 2;
            int height = 10;

            using (Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed))
            {
                Color backColour = Color.White;
                byte idxBackground = 0;
                byte idxLeft = 1;
                byte idxCenter = 2;
                byte idxRight = 3;

                ColorPalette cp = bitmap.Palette;
                cp.Entries[idxBackground] = backColour;
                cp.Entries[idxLeft] = leftColour;
                cp.Entries[idxCenter] = centerColour;
                cp.Entries[idxRight] = rightColour;

                bitmap.Palette = cp;

                fillrect(idxBackground, bitmap, new Rectangle(0, 0, width, height));
                fillrect(idxLeft, bitmap, new Rectangle(0, 0, leftWidth, height));
                fillrect(idxCenter, bitmap, new Rectangle(leftWidth + 1, 0, centerWidth, height));
                fillrect(idxRight, bitmap, new Rectangle(leftWidth + 2 + centerWidth, 0, rightWidth, height));

                bitmap.Save(path, ImageFormat.Gif);
            }
        }

        static void fillrect(byte colorIndex, Bitmap b, Rectangle r)
        {
            BitmapData bmpData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.WriteOnly, b.PixelFormat);

            // Get the address of the first line.
            IntPtr ptr = bmpData.Scan0;

            // Declare an array to hold the bytes of the bitmap.
            // This code is specific to a bitmap with 8 bits index per pixels.
            int bytes = b.Width * b.Height;
            byte[] Values = new byte[bytes];

            // Copy the RGB values into the array.
            Marshal.Copy(ptr, Values, 0, bytes);

            for (int y = r.Top; y < r.Bottom; y++)
            {
                for (int x = r.Left; x < r.Right; x++)
                {
                    Values[(y * b.Width) + x] = colorIndex;
                }
            }

            // Copy the values back to the bitmap
            Marshal.Copy(Values, 0, ptr, bytes);

            // Unlock the bits.
            b.UnlockBits(bmpData);
        }
    }
}

I would change fillrect, to seperate out the locking, and Marshal calls, to CreateTempBuffer, FillTempBuffer, and WriteTempBuffer functions, if you were to use fillrect more than a couple times.  But as it stands, the code is correct after each call.

Loving the Visual Studio 2008 compiler

I have been finding lambda and extension methods really helpful in my game port.

Blobs of C styled single linked list code, when changed to generic lists boil down to one line.

like this:

Item item = player.itemsPtr;
while (item != null)
{
    Item next_item = item.next;

    if (item_type == item.type)
    {
        lose_item(item, player); // just removes from linked list.
    }
    item = next_item;
}

to this:

player.items.RemoveAll(item => item.type == item_type);

much nicer.