beagle r4696 - in trunk/beagle/Util/F-Spot: . Imaging upstream-changes



Author: dbera
Date: Sun Apr 13 20:18:35 2008
New Revision: 4696
URL: http://svn.gnome.org/viewvc/beagle?rev=4696&view=rev

Log:
Import F-Spot files from r3823.


Added:
   trunk/beagle/Util/F-Spot/
   trunk/beagle/Util/F-Spot/BitConverter.cs
   trunk/beagle/Util/F-Spot/Imaging/
   trunk/beagle/Util/F-Spot/Imaging/Bim.cs
   trunk/beagle/Util/F-Spot/Imaging/Ciff.cs
   trunk/beagle/Util/F-Spot/Imaging/DCRawFile.cs
   trunk/beagle/Util/F-Spot/Imaging/Exif.cs
   trunk/beagle/Util/F-Spot/Imaging/ImageFile.cs
   trunk/beagle/Util/F-Spot/Imaging/IptcFile.cs
   trunk/beagle/Util/F-Spot/Imaging/JpegFile.cs
   trunk/beagle/Util/F-Spot/Imaging/JpegHeader.cs
   trunk/beagle/Util/F-Spot/Imaging/JpegUtils.cs
   trunk/beagle/Util/F-Spot/Imaging/MrwFile.cs
   trunk/beagle/Util/F-Spot/Imaging/OrderedWriter.cs
   trunk/beagle/Util/F-Spot/Imaging/PngFile.cs
   trunk/beagle/Util/F-Spot/Imaging/PnmFile.cs
   trunk/beagle/Util/F-Spot/Imaging/RafFile.cs
   trunk/beagle/Util/F-Spot/Imaging/SvgFile.cs
   trunk/beagle/Util/F-Spot/Imaging/Tiff.cs
   trunk/beagle/Util/F-Spot/Imaging/X3fFile.cs
   trunk/beagle/Util/F-Spot/Imaging/XmpFile.cs
   trunk/beagle/Util/F-Spot/MetadataStore.cs
   trunk/beagle/Util/F-Spot/PixbufUtils.cs
   trunk/beagle/Util/F-Spot/README
   trunk/beagle/Util/F-Spot/upstream-changes/
   trunk/beagle/Util/F-Spot/upstream-changes/01-initial-diff-r3823.diff

Added: trunk/beagle/Util/F-Spot/BitConverter.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/BitConverter.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,94 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace FSpot {
+	public class BitConverter {
+		public static uint Swap (uint val, bool little) 
+		{
+			return (little != System.BitConverter.IsLittleEndian) ?
+				((uint) ((((uint) (val) & (uint) 0x000000ffU) << 24) |
+					 (((uint) (val) & (uint) 0x0000ff00U) <<  8) | 
+					 (((uint) (val) & (uint) 0x00ff0000U) >>  8) |
+					 (((uint) (val) & (uint) 0xff000000U) >> 24)))
+				: val;
+		}
+		
+		public static ushort Swap (ushort val, bool little)
+		{
+			return (little != System.BitConverter.IsLittleEndian) ?
+				((ushort) ((ushort)(val >> 8) | (ushort)(val << 8)))
+				: val;
+		}
+
+		public static ushort Swap (ushort val)
+		{
+			return ((ushort) ((ushort)(val >> 8) | (ushort)(val << 8)));
+		}
+		
+		public static ulong Swap (ulong val, bool little)
+		{
+		        return (little != System.BitConverter.IsLittleEndian) ?
+			((ulong) ((((ulong) (val) & (ulong) 0x00000000000000ffU) << 56) |     
+				  (((ulong) (val) & (ulong) 0x000000000000ff00U) << 40) |	
+				  (((ulong) (val) & (ulong) 0x0000000000ff0000U) << 24) |
+				  (((ulong) (val) & (ulong) 0x00000000ff000000U) <<  8) |
+				  (((ulong) (val) & (ulong) 0x000000ff00000000U) >>  8) |	
+				  (((ulong) (val) & (ulong) 0x0000ff0000000000U) >> 24) |
+				  (((ulong) (val) & (ulong) 0x00ff000000000000U) >> 40) |
+				  (((ulong) (val) & (ulong) 0xff00000000000000U) >> 56)))
+				: val;
+		}
+		
+		public static byte [] GetBytes (uint val, bool little) 
+		{
+			val = Swap (val, little);
+			return System.BitConverter.GetBytes (val);
+		}
+		
+		public static byte [] GetBytes (ushort val, bool little)
+		{
+			val = Swap (val, little);
+			return System.BitConverter.GetBytes (val);
+		}
+
+		public static byte [] GetBytes (ulong val, bool little)
+		{
+			val = Swap (val, little);
+			return System.BitConverter.GetBytes (val);
+		}
+		
+		public static ushort ToUInt16 (byte [] data, int position, bool little)
+		{
+			ushort val = System.BitConverter.ToUInt16 (data, position);
+			return Swap (val, little);
+		}
+
+		public static uint ToUInt32 (byte [] data, int position, bool little)
+		{
+			uint val = System.BitConverter.ToUInt32 (data, position);
+			return Swap (val, little);
+		}
+
+		public static float ToSingle (byte [] data, int position, bool little)
+		{
+			float retval;
+			unsafe {
+				uint * ptr;
+				ptr = (uint *)&retval;
+				*ptr = ToUInt32 (data, position, little);
+			}
+			return retval;
+		}
+
+		public static int ToInt32 (byte [] data, int position, bool little)
+		{
+			return unchecked ((int) ToUInt32 (data, position, little));
+		}
+
+		public static ulong ToUInt64 (byte [] data, int position, bool little)
+		{
+			ulong val = System.BitConverter.ToUInt64(data, position);
+			return Swap (val, little);
+		}
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/Bim.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/Bim.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,212 @@
+namespace FSpot.Bim {
+	public enum EntryType : ushort {
+		ObsoleteImageInfo = 0x03e8,  
+		MacPrintManager = 0x03e9,
+		MacPrintXML = 0x03ea,
+		ObsoleteIndexedColorTable = 0x03eb,
+		ResolutionInfo = 0x03ed,
+		AlphaChannelNames = 0x03ee,
+		DisplayInfo = 0x03ef,
+		Caption = 0x03f0,
+		Border = 0x03f1,
+		BackgroundColor = 0x03f2,
+		PrintFlags = 0x03f3,
+		GrayHalftone = 0x03f4,
+		ColorHalftone = 0x03f5,
+		DuotoneHalftone = 0x03f6,
+		GrayTransfer = 0x03f7,
+		ColorTransfer = 0x03f8,
+		DuotoneTransfer = 0x03f9,
+		DuotoneInformation = 0x03fa,
+		BWDot = 0x03fb,
+		ObsoleteUnknown1 = 0x03fc,
+		LayerState = 0x0400,
+		WorkingPath = 0x0401,
+		LayerGroupInfomation = 0x0402,
+		ObsoleteUnknown2 = 0x0403,
+		IPTCNAA = 0x0404,
+		RawImageMode = 0x0405,
+		JpegQuality = 0x0406,
+		GridGuideInformation = 0x0408,
+		ThumbnailResource = 0x0409,
+		CopyrightFlag = 0x040a,
+		URL = 0x040b,
+		ThumbnailResource2 = 0x040c,
+		GlobalAngle = 0x040d,
+		ColorSamplers = 0x040e,
+		ICCProfile = 0x040f,
+		Watermark = 0x0410,
+		ICCUntagged = 0x0411,
+		EffectsVisible = 0x0412,
+		SpotHalftone = 0x0413,
+		LastID = 0x0414,
+		UnicodeAlphaNames = 0x0415,
+		IndexedColorTable = 0x0416,
+		TransparentIndex = 0x0417,
+		GlobalAltitude = 0x0419,
+		Slices = 0x041a,
+		WorkflowURL = 0x041b,
+		XPEPJump = 0x041c,
+		AlphaIdentifiers = 0x041d,
+		URLList = 0x041e,
+		VersionInfo = 0x0421,
+		XMP = 0x0424,
+		FirstPath = 0x07d0,
+		LastPAth = 0x0bb6,
+		ClippingPathName = 0x0bb7,
+		PrintFlags2 = 0x2710,
+	}
+
+	/*
+	  From what I can see it looks like these resources are a just
+	  a list of records starting with a 8Bim\0 followed by a 16 bit
+	  value that is the record type then a 8 bit offset and 32bit length. IPTC data is
+	  type 0x0404, I don't know any other types at the moment.
+
+	  see http://www.fine-view.com/jp/lab/doc/ps6ffspecsv2.pdf and the section on image resource blocks.
+	*/
+
+	public class Entry
+	{
+		public ushort  Type;
+		public string  Name;
+		public byte [] Data;
+
+		const string Marker = "8BIM";
+		public Entry ()
+		{
+
+		}
+
+		public int Load (System.IO.Stream stream)
+		{
+			byte [] header = new byte [6];
+			
+			stream.Read (header, 0, header.Length);
+			if (System.Text.Encoding.ASCII.GetString (header, 0, 4) != Marker)
+				throw new System.Exception ("missing header");
+			
+			Type = FSpot.BitConverter.ToUInt16 (header, 4, false);
+
+		        int name_length = stream.ReadByte ();
+			if (name_length > 0) {
+				byte [] name_data = new byte [name_length];
+				stream.Read (name_data, 0, name_length);
+				Name = System.Text.Encoding.ASCII.GetString (name_data);
+			}
+			
+			if (name_length % 2 == 0)
+				stream.ReadByte ();
+
+			stream.Read (header, 0, 4);
+			uint length = FSpot.BitConverter.ToUInt32 (header, 0, false);
+
+			Data = new byte [length];
+			stream.Read (Data, 0, Data.Length);
+
+			if (Data.Length % 2 > 0)
+				stream.ReadByte ();
+
+			return header.Length + Data.Length;
+		}
+		
+		public void Save (System.IO.Stream stream) 
+		{
+			byte [] tmp;
+			tmp = System.Text.Encoding.ASCII.GetBytes (Marker);
+			stream.Write (tmp, 0, tmp.Length);
+			tmp = FSpot.BitConverter.GetBytes (Type, false);
+			stream.Write (tmp, 0, tmp.Length);
+
+			// Write the name
+			stream.WriteByte ((byte)Name.Length);
+			tmp = System.Text.Encoding.ASCII.GetBytes (Name);
+			stream.Write (tmp, 0, tmp.Length);
+
+			// Pad the name
+			if (tmp.Length % 2 == 0)
+				stream.WriteByte (0);
+
+			// Write the data
+			tmp  = FSpot.BitConverter.GetBytes ((uint)Data.Length, false);
+			stream.Write (tmp, 0, tmp.Length);
+
+			stream.Write (Data, 0, Data.Length);
+			// Pad the data
+			if (Data.Length % 2 > 0)
+				stream.WriteByte (0);
+		}
+	}
+
+	public class BimFile : SemWeb.StatementSource
+	{
+                // False seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+
+		System.Collections.ArrayList entries = new System.Collections.ArrayList ();
+
+		public BimFile (System.IO.Stream stream)
+		{
+			Load (stream);
+		}
+		
+		public void Select (SemWeb.StatementSink sink)
+		{
+			foreach (Entry e in entries) {
+				EntryType type = (EntryType) e.Type;
+
+				switch (type) {
+				case EntryType.IPTCNAA:
+					System.IO.Stream iptcstream = new System.IO.MemoryStream (e.Data);
+					FSpot.Iptc.IptcFile iptc = new FSpot.Iptc.IptcFile (iptcstream);
+					iptc.Select (sink);
+					break;
+				case EntryType.XMP:
+					System.IO.Stream xmpstream = new System.IO.MemoryStream (e.Data);
+					FSpot.Xmp.XmpFile xmp = new FSpot.Xmp.XmpFile (xmpstream);
+					xmp.Select (sink);
+					break;
+				default:
+					break;
+				}
+			}
+		}
+
+		public Entry FindEntry (EntryType type)
+		{
+			foreach (Entry current in entries)
+				if (current.Type == (ushort)type)
+					return current;
+
+			return null;
+		}
+
+		public void Load (System.IO.Stream stream)
+		{
+			while (stream.Position < stream.Length)
+			{
+				Entry current = new Entry ();
+				current.Load (stream);
+				//System.Console.WriteLine ("read {0} - {1}", ((EntryType)current.Type).ToString (), current.Name);
+				try {
+					//System.Console.WriteLine (System.Text.Encoding.ASCII.GetString (current.Data));
+				} catch (System.Exception e) {
+					System.Console.WriteLine (e.ToString ());
+				}
+				entries.Add (current);
+			}
+		}
+
+		public void Save (System.IO.Stream stream)
+		{
+			foreach (Entry e in entries) {
+				e.Save (stream);
+			}
+		}
+	}
+}
+
+
+

Added: trunk/beagle/Util/F-Spot/Imaging/Ciff.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/Ciff.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,509 @@
+using System;
+
+namespace FSpot.Ciff {
+	public enum Tag {
+		// Byte valuesad
+		NullRecord = 0x0000,
+		FreeBytes = 0x0001,
+		CanonColorInfo1 = 0x0032,
+
+		// ASCII strings
+		CanonFileDescription = 0x0805,
+		UserComment = 0x0805, // FIXME this is the same as above, is it correct?
+		CanonRawMakeModel = 0x080a, 
+		CanonFirmwareVersion = 0x080b,
+		ComponentVersion = 0x08c,
+		ROMOperationMode = 0x08d,
+		OwnerName = 0x0810,
+		CanonImageType = 0x0815,
+		OriginalFileName = 0x0816,
+		ThumbnailFileName = 0x0817,
+		
+		// Short values
+		TargetImageType = 0x100a,
+		ShutterReleaseMethod = 0x1010,
+		ShutterReleaseTiming = 0x1011,
+		ReleaseSetting = 0x1016,
+		BaseISO = 0x101c,
+
+		Uknown2 = 0x1028,
+
+		FocalLength = 0x1029,
+		CanonShotInfo = 0x102a,
+		CanonColorInfo2 = 0x102c,
+		CanonCameraSettings = 0x102d,
+		WhiteSample = 0x1030,
+		SensorInfo = 0x1031,
+		CanonCustomFunctions = 0x1033,
+		CanonPictureInfo = 0x1038,
+
+		Unknown3 = 0x1039,
+		Unknown4 = 0x1093,
+		Unknown5 = 0x10a8,
+		
+		WhiteBalanceTable = 0x10a9,
+		
+		Unknown6 = 0x10aa,
+
+		ColorTemperature = 0x10ae,
+		ColorSapce = 0x10b4,
+		
+		Unknown7 = 0x10b5,
+		unknown8 = 0x10c0,
+		Unknown9 = 0x10c1,
+
+		ImageFormat = 0x1803,
+		RecordID = 0x1804,
+		SelfTimerTime = 0x1806,
+		TargetDistanceSetting = 0x1807,
+		SerialNumber = 0x180b,
+		TimeStamp = 0x180e,
+		ImageSpec = 0x1810,
+		FlashInfo = 0x1813,
+		MeasuredEV = 0x1814,
+		FileNumber = 0x1817,
+		ExposureInfo = 0x1818,
+		
+		Unknown10 = 0x1834,
+
+		DecoderTable = 0x1835,
+		
+		Unknown11 = 0x183b,
+
+		// Image Data
+		RawData = 0x2005,
+		JpgFromRaw = 0x2007,
+		ThumbnailImage = 0x2008,
+
+		// Directories
+		ImageDescrption = 0x2804,
+		CameraObject = 0x2807,
+		ShootingRecord = 0x3002,
+		MeasuredInfo = 0x3003,
+		CameraSpecification = 0x3004,
+		ImageProps = 0x300a,
+		ExifInformation = 0x300b
+	}
+
+	
+
+	public struct ImageSpec {
+		public uint ImageWidth;  // Number of horizontal pixels
+		public uint ImageHeight; // Number of vertical pixels
+		public float PixelAspectRatio;
+		public int RotationAngle;  // degrees counter clockwise to rotate (orientation)
+		public uint ComponentBitDepth; // bits per component
+		public uint ColorBitDepth; // bits per component * channels
+		public uint ColorBW; //  byte wise:  0 gray - 1 color ; byte 2 use aspect ratio ; 3 and 4 reserved
+
+		public ImageSpec (byte [] data, bool little)
+		{
+			ImageWidth = BitConverter.ToUInt32 (data, 0, little);
+			ImageHeight = BitConverter.ToUInt32 (data, 4, little);
+
+			PixelAspectRatio = BitConverter.ToSingle (data, 8, little);
+			RotationAngle = BitConverter.ToInt32 (data, 12, little);
+			ComponentBitDepth = BitConverter.ToUInt32 (data, 16, little);
+			ColorBitDepth = BitConverter.ToUInt32 (data, 20, little);
+			ColorBW = BitConverter.ToUInt32 (data, 24, little);
+			System.Console.WriteLine ("0x{0}", ColorBW.ToString ("x"));
+		}
+
+		public PixbufOrientation Orientation {
+			get {
+				int angle = RotationAngle % 360;
+				if (angle < 45)
+					return PixbufOrientation.TopLeft;
+				else if (angle < 135)
+					return PixbufOrientation.RightTop;
+				else if (angle < 225)
+					return PixbufOrientation.BottomRight;
+				else if (angle < 315)
+					return PixbufOrientation.LeftBottom;
+				else
+					return PixbufOrientation.TopLeft;
+			}
+		}
+
+		public bool IsColor {
+			get {
+				return (ColorBW & 1) > 0;
+
+			}
+		}
+
+		public bool HasSquarePixels {
+			get {
+				return (ColorBW & 1 << 1) == 0;
+			}
+		}
+	}
+
+	public struct CaptureTime {
+		uint seconds;
+		int tz_offset;
+		uint tz_data;
+
+		public CaptureTime (byte [] data, bool little)
+		{
+			seconds = BitConverter.ToUInt32 (data, 0, little);
+			tz_offset = BitConverter.ToInt32 (data, 4, little);
+			tz_data = BitConverter.ToUInt32 (data, 8, little);
+		}
+
+		public System.DateTime LocalTime {
+			get {
+				return new System.DateTime (1970, 1, 1).AddSeconds (seconds);
+			}
+		}
+
+		public bool HasTimezone {
+			get {
+				return ((1 << 31 & tz_data) > 0);
+			}
+		}
+
+		public override string ToString ()
+		{
+			string tz = String.Empty;
+			
+			if (HasTimezone)
+				if (tz_offset != 0)
+					tz = System.String.Format ("{0}{1}:{2}", 
+								   seconds > 0 ? "+" : "-", 
+								   tz_offset / 3600, 
+								   tz_offset / 60 % 60);
+				else 
+					tz = "Z";
+
+		       
+			return System.String.Format ("{0}{1}", LocalTime.ToString ("yyyy-MM-ddThh:mm:ss"), tz);
+		}
+	}
+
+	public enum EntryType : ushort {
+		Byte = 0x0000,
+		Ascii = 0x0800,
+		Short = 0x1000,
+		Int = 0x1800,
+		Struct = 0x2000,
+		Directory1 = 0x2800,
+		Directory2 = 0x2800,
+	}
+	
+	public enum Mask {
+		StorageFormat = 0xc000,
+		Type = 0x3800,
+		ID = 0x07ff,
+	}
+
+	/* See http://www.sno.phy.queensu.ca/~phil/exiftool/canon_raw.html */
+	public struct Entry {
+		internal Tag Tag;
+		internal uint Size;
+		internal uint Offset;
+
+		public Entry (byte [] data, int pos, bool little)
+		{
+			Tag = (Tag) BitConverter.ToUInt16 (data, pos, little);
+			Size = BitConverter.ToUInt32 (data, pos + 2, little);
+			Offset = BitConverter.ToUInt32 (data, pos + 6, little);
+		}	
+		
+		public EntryType Type {
+			get {
+				return GetType (Tag);
+			}
+		}
+
+		public static EntryType GetType (Tag tag) 
+		{
+			EntryType type = (EntryType) ((ushort)tag & (ushort)Mask.Type);
+			return type;
+		}
+
+		public static bool IsDirectory (Tag tag)
+		{
+			EntryType type = GetType (tag);
+			return (type == EntryType.Directory1 || type == EntryType.Directory2);
+		}
+	}
+
+	public class ImageDirectory {
+		System.Collections.ArrayList entry_list;
+		uint Count;
+		bool little;
+		uint start;
+		long DirPosition; 
+		System.IO.Stream stream;
+
+		public ImageDirectory (System.IO.Stream stream, uint start, long end, bool little)
+		{
+			this.start = start;
+			this.little = little;
+			this.stream = stream;
+
+			entry_list = new System.Collections.ArrayList ();
+			
+			stream.Position = end - 4;
+			byte [] buf = new byte [10];
+			stream.Read (buf, 0, 4);
+			uint directory_pos  = BitConverter.ToUInt32 (buf, 0, little);
+			DirPosition = start + directory_pos;
+
+			stream.Position = DirPosition;
+			stream.Read (buf, 0, 2);
+
+			Count = BitConverter.ToUInt16 (buf, 0, little);
+			
+			for (int i = 0; i < Count; i++)
+			{
+				stream.Read (buf, 0, 10);
+				System.Console.WriteLine ("reading {0} {1}", i, stream.Position);
+				Entry entry = new Entry (buf, 0, little);
+				entry_list.Add (entry);
+			}
+		}			
+
+		public void Dump ()
+		{
+			System.Console.WriteLine ("Dumping directory with {0} entries", entry_list.Count);
+			for (int i = 0; i < entry_list.Count; i++) {
+				Entry e = (Entry) entry_list[i];
+				System.Console.WriteLine ("\tentry[{0}] = {1}.{6}.{5}({4}).{2}-{3}", 
+							  i, e.Tag, e.Size, e.Offset, e.Tag.ToString ("x"), (uint)e.Tag & ~(uint)Mask.StorageFormat, e.Type); 
+			}
+		}
+
+		public ImageDirectory ReadDirectory (Tag tag)
+		{
+			foreach (Entry e in entry_list) {
+				if (e.Tag == tag) {
+					uint subdir_start = this.start + e.Offset;
+					ImageDirectory subdir = new ImageDirectory (stream, subdir_start, subdir_start + e.Size, little);
+					return subdir;
+				}
+			}
+			return null;
+		}
+		
+		public byte [] ReadEntry (int pos)
+		{
+			Entry e = (Entry) entry_list [pos];
+
+			stream.Position = this.start + e.Offset;			
+
+			byte [] data = new byte [e.Size];
+			stream.Read (data, 0, data.Length);
+
+			return data;
+		}
+		
+		public byte [] ReadEntry (Tag tag) 
+		{
+			int pos = 0;
+			foreach (Entry e in entry_list) {
+				if (e.Tag == tag)
+					return ReadEntry (pos);
+				pos++;
+			}
+			return null;
+		}
+	}
+	
+	public class CiffFile : FSpot.ImageFile , SemWeb.StatementSource {
+		public ImageDirectory root;
+		private uint version;
+		bool little;
+		System.IO.Stream stream;
+
+                // False seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+		
+		public ImageDirectory Root {
+			get {
+				if (root == null) {
+					stream = Open ();
+					root = Load (stream);
+				}
+				
+			        return root;
+			}
+		}
+
+		public CiffFile (Uri uri) : base (uri)
+		{
+		}
+
+		public void Select (SemWeb.StatementSink sink)
+		{
+			byte [] data = null;
+			ImageDirectory props = Root.ReadDirectory (Tag.ImageProps);
+			ImageDirectory camera = props.ReadDirectory (Tag.CameraObject);
+
+			data = props.ReadEntry (Tag.TimeStamp);
+			if (data != null)
+				MetadataStore.AddLiteral (sink, "xmp:CreateDate", new CaptureTime (data, little).ToString ());
+
+			data = props.ReadEntry (Tag.ImageSpec);
+			if (data != null) {
+				ImageSpec spec = new ImageSpec (data, little);
+				MetadataStore.AddLiteral (sink, "tiff:Orientation", ((int)spec.Orientation).ToString ());
+				MetadataStore.AddLiteral (sink, "tiff:ImageWidth", spec.ImageWidth.ToString ());
+				MetadataStore.AddLiteral (sink, "tiff:ImageLength", spec.ImageHeight.ToString ());
+				string comp = spec.ComponentBitDepth.ToString ();
+
+				if (spec.IsColor) {
+					MetadataStore.Add (sink, "tiff:BitsPerSample", "rdf:Seq", new string [] { comp, comp, comp });
+				} else {
+					MetadataStore.Add (sink, "tiff:BitsPerSample", "rdf:Seq", new string [] { comp });
+				}					
+				
+				if (!spec.HasSquarePixels) {
+					MetadataStore.AddLiteral (sink, "tiff:XResolution", 
+								  (1000000 * spec.PixelAspectRatio).ToString ());
+					MetadataStore.AddLiteral (sink, "tiff:YResolution", 
+								  (1000000 * (1 / spec.PixelAspectRatio)).ToString ());
+				}
+					
+			}
+			
+			data = camera.ReadEntry (Tag.CanonRawMakeModel);
+			if (data != null) {
+				string make_model = System.Text.Encoding.ASCII.GetString (data, 0, data.Length - 1);
+				string [] vals = make_model.Split (new char [] {'\0'});
+				MetadataStore.AddLiteral (sink, "tiff:Make", vals [0]); 
+				MetadataStore.AddLiteral (sink, "tiff:Model", vals [1]); 
+			}
+
+			/*
+			// FIXME this doesn't appear to be ascii.
+			data = camera.ReadEntry (Tag.OwnerName);
+			if (data != null) {
+				string name = System.Text.Encoding.ASCII.GetString (data, 0, data.Length - 1);
+				MetadataStore.AddLiteral (sink, "dc:creator", "rdf:Seq", new SemWeb.Literal (name));
+			}
+			*/
+		}
+
+
+		protected ImageDirectory Load (System.IO.Stream stream) 
+		{
+			byte [] header = new byte [26];  // the spec reserves the first 26 bytes as the header block
+			stream.Read (header, 0, header.Length);
+
+			uint start;
+			
+			little = (header [0] == 'I' && header [1] == 'I');
+			
+			start = BitConverter.ToUInt32 (header, 2, little);
+			
+			// HEAP is the type CCDR is the subtype
+			if (System.Text.Encoding.ASCII.GetString (header, 6, 8) != "HEAPCCDR") 
+				throw new ImageFormatException ("Invalid Ciff Header Block");
+			
+			version =  BitConverter.ToUInt32 (header, 14, little);
+
+			//
+			
+			long end = stream.Length;
+			return new ImageDirectory (stream, start, end, little);
+		}
+
+		public uint Version {
+			get { return version; }
+		}
+
+		public override PixbufOrientation GetOrientation ()
+		{
+			PixbufOrientation orientation = PixbufOrientation.TopLeft;
+			ImageDirectory props = Root.ReadDirectory (Tag.ImageProps);
+		       	byte [] data = props.ReadEntry (Tag.ImageSpec);
+			
+			if (data != null)
+				orientation = new ImageSpec (data, little).Orientation;
+			else 
+				System.Console.WriteLine ("NO ORIENTATION");
+
+			return orientation;
+		}
+
+		public override System.DateTime Date {
+			get {
+				ImageDirectory props = Root.ReadDirectory (Tag.ImageProps);
+				byte [] date = props.ReadEntry (Tag.TimeStamp);
+
+				if (date == null) {
+					System.Console.WriteLine ("NO DATE");
+					return base.Date;
+				}
+
+				return new CaptureTime (date, little).LocalTime.ToUniversalTime ();
+			}
+		}
+
+		public override System.IO.Stream PixbufStream ()
+		{
+			byte [] data = GetEmbeddedJpeg ();
+			
+			if (data != null)
+				return new System.IO.MemoryStream (data);
+			else	
+				return DCRawFile.RawPixbufStream (uri);
+		}
+
+#if false
+		public override Gdk.Pixbuf Load (int width, int height)
+		{
+			Gdk.Pixbuf full = this.Load ();
+			Gdk.Pixbuf scaled  = PixbufUtils.ScaleToMaxSize (full, width, height);
+			full.Dispose ();
+			return scaled;
+		}
+#endif
+		public void Dump ()
+		{
+			Root.Dump ();
+			ImageDirectory props = Root.ReadDirectory (Tag.ImageProps);
+			props.Dump ();
+			/*
+				 string path = "out2.jpg";
+			System.IO.File.Delete (path);
+
+			System.IO.Stream output = System.IO.File.Open (path, System.IO.FileMode.OpenOrCreate);
+			byte [] data = GetEmbeddedThumbnail ();
+			System.Console.WriteLine ("data length {0}", data != null ? data.Length : -1);
+			output.Write (data, 0, data.Length);
+			output.Close ();
+			*/
+		}
+
+		public byte [] GetEmbeddedJpeg ()
+		{
+			return Root.ReadEntry (Tag.JpgFromRaw);
+		}
+
+		public byte [] GetEmbeddedThumbnail ()
+		{
+			return Root.ReadEntry (Tag.ThumbnailImage); 
+		}
+		
+		/*
+		public static void Main (string [] args)
+		{
+			CiffFile ciff = new CiffFile (args [0]);
+			ciff.Dump ();
+		}
+		*/
+
+		protected override void Close ()
+		{
+			if (stream != null) {
+				stream.Close ();
+				stream = null;
+			}
+		}
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/DCRawFile.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/DCRawFile.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,135 @@
+using System.Diagnostics;
+using System.IO;
+using System;
+
+namespace FSpot {
+	public class Pipe : System.IO.Stream {
+		// This class is a hack to make sure mono doesn't dispose the process
+		// and by extension the stream from the pipe when we are still using the
+		// the stream.
+		Process process;
+		Stream stream;
+
+		public override bool CanRead {
+			get { return stream.CanRead; }
+		}
+
+		public override bool CanSeek {
+			get { return stream.CanSeek; }
+		}
+
+		public override bool CanWrite {
+			get { return stream.CanWrite; }
+		}
+		
+		public override long Length {
+			get { return stream.Length; }
+		}
+
+		public override long Position {
+			get { return stream.Position; }
+			set { stream.Position = value; }
+		}
+
+		public Pipe (Process p, Stream stream)
+		{
+			this.process = p;
+			this.stream = stream;
+		}
+		
+		public override void Flush ()
+		{
+			stream.Flush ();
+		}
+
+		public override int Read (byte [] b, int s, int l)
+		{
+			return stream.Read (b, s, l);
+		}
+
+		public override long Seek (long l, SeekOrigin origin)
+		{
+			return stream.Seek(l, origin);
+		}
+		
+		public override void SetLength (long l)
+		{
+			stream.SetLength (l);
+		}
+
+		public override void Write (byte [] b, int s, int l)
+		{
+			stream.Write (b, s, l);
+		}
+		
+		public override void Close ()
+		{
+			stream.Close ();
+			stream = null;
+			process.Dispose ();
+			process = null;
+		}
+	}
+
+	public class DCRawFile : ImageFile {
+		const string dcraw_command = "dcraw";
+
+		public DCRawFile (string path) : base (path)
+		{
+		}
+
+		public DCRawFile (Uri uri) : base (uri)
+		{
+		}
+
+		public override System.IO.Stream PixbufStream ()
+		{
+			return RawPixbufStream (uri);
+		}
+
+		internal static System.IO.Stream RawPixbufStream (Uri location)
+		{
+#if false
+			string path = location.LocalPath;
+			string [] args = new string [] { dcraw_command, "-h", "-w", "-c", "-t", "0", path };
+			
+			InternalProcess proc = new InternalProcess (System.IO.Path.GetDirectoryName (path), args);
+			proc.StandardInput.Close ();
+			return proc.StandardOutput;
+#else
+			return null;
+#endif
+		}
+	
+		public static System.IO.Stream RawPixbufStreamOld (string path)
+		{
+			// FIXME this filename quoting is super lame
+			string args = System.String.Format ("-h -w -c -t 0 \"{0}\"", path);
+
+			System.Diagnostics.Process process = new System.Diagnostics.Process ();
+			process.StartInfo = new System.Diagnostics.ProcessStartInfo (dcraw_command, args);
+			process.StartInfo.RedirectStandardOutput = true;
+			process.StartInfo.UseShellExecute = false;
+			process.Start ();
+			return new Pipe (process, process.StandardOutput.BaseStream);
+		}
+		
+#if false
+		public static Gdk.Pixbuf Load (string path, string args)
+		{
+			// FIXME this filename quoting is super lame
+			args = System.String.Format ("-h -w -c \"{0}\"", path);
+
+			System.Console.WriteLine ("path = {0}, args = \"{1}\"", path, args);
+			 
+			using (System.Diagnostics.Process process = new System.Diagnostics.Process ()) {
+				process.StartInfo = new System.Diagnostics.ProcessStartInfo (dcraw_command, args);
+				process.StartInfo.RedirectStandardOutput = true;
+				process.StartInfo.UseShellExecute = false;
+				process.Start ();
+				return PixbufUtils.LoadFromStream (process.StandardOutput.BaseStream);
+			}
+		}
+#endif
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/Exif.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/Exif.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,920 @@
+//
+// Exif.cs : LibExif wrapper for FSpot
+//
+// Author:
+//   Larry Ewing     (lewing novell com)
+//   Ravi Pratap     (ravi ximian com)
+//   Miguel de Icaza (miguel ximian com)
+//
+// (C) 2002, 2004, 2005 Novell, Inc.
+//
+
+using System;
+using System.Collections;
+using System.Runtime.InteropServices;
+
+using Mono.Unix;
+
+namespace Exif {
+	public enum Tag {
+		InteroperabilityIndex		= 0x0001,
+		InteroperabilityVersion	        = 0x0002,
+		ImageWidth 			= 0x0100,
+		ImageHeight 			= 0x0101,
+		BitsPersample 	         	= 0x0102,
+		Compression 			= 0x0103,
+		PhotometricInterpretation 	= 0x0106,
+		FillOrder 			= 0x010a,
+		DocumentName 			= 0x010d,
+		ImageDescription 		= 0x010e,
+		Make 				= 0x010f,
+		Model 				= 0x0110,
+		StripOffsets 			= 0x0111,
+		Orientation 			= 0x0112,
+		SamplesPerPixel 		= 0x0115,
+		RowsPerStrip    		= 0x0116,
+		StripByteCounts 		= 0x0117,
+		XResolution 			= 0x011a,
+		YResolution 			= 0x011b,
+		PlanarConfiguration 		= 0x011c,
+		ResolutionUnit  		= 0x0128,
+		TransferFunction 		= 0x012d,
+		Software 			= 0x0131,
+		DateTime			= 0x0132,
+		Artist				= 0x013b,
+		WhitePoint			= 0x013e,
+		PrimaryChromaticities		= 0x013f,
+		TransferRange			= 0x0156,
+		JPEGProc			= 0x0200,
+		JPEGInterchangeFormat	        = 0x0201,
+		JPEGInterchangeFormatLength	= 0x0202,
+		YCBCRCoefficients		= 0x0211,
+		YCBCRSubSampling		= 0x0212,
+		YCBCRPositioning		= 0x0213,
+		ReferenceBlackWhite		= 0x0214,
+		RelatedImageFileFormat   	= 0x1000,
+		RelatedImageWidth		= 0x1001,
+		RelatedImageHeight		= 0x1002,
+		CFARepeatPatternDim		= 0x828d,
+		CFAPattern			= 0x828e,
+		BatteryLevel			= 0x828f,
+		Copyright			= 0x8298,
+		ExposureTime			= 0x829a,
+		FNumber 			= 0x829d,
+		IPTCNAA	        		= 0x83bb,
+		ExifIfdPointer      		= 0x8769,
+		InterColorProfile		= 0x8773,
+		ExposureProgram 		= 0x8822,
+		SpectralSensitivity		= 0x8824,
+		GPSInfoIfdPointer		= 0x8825,
+		ISOSpeedRatings	        	= 0x8827,
+		OECF				= 0x8828,
+		ExifVersion			= 0x9000,
+		DateTimeOriginal		= 0x9003,
+		DateTimeDigitized		= 0x9004,
+		ComponentsConfiguration	        = 0x9101,
+		CompressedBitsPerPixel	        = 0x9102,
+		ShutterSpeedValue		= 0x9201,
+		ApertureValue			= 0x9202,
+		BrightnessValue  		= 0x9203,
+		ExposureBiasValue		= 0x9204,
+		MaxApertureValue		= 0x9205,
+		SubjectDistance 		= 0x9206,
+		MeteringMode			= 0x9207,
+		LightSource			= 0x9208,
+		Flash				= 0x9209,
+		FocalLength			= 0x920a,
+		SubjectArea			= 0x9214,
+		MakerNote			= 0x927c,
+		UserComment			= 0x9286,
+		SubSecTime			= 0x9290,
+		SubSecTimeOriginal		= 0x9291,
+		SubSecTimeDigitized		= 0x9292,
+		FlashPixVersion 		= 0xa000,
+		ColorSpace			= 0xa001,
+		PixelXDimension 		= 0xa002,
+		PixelYDimension 		= 0xa003,
+		RelatedSoundFile		= 0xa004,
+		InteroperabilityIfdPointer	= 0xa005,
+		FlashEnergy			= 0xa20b,
+		SpatialFrequencyResponse	= 0xa20c,
+		FocalPlaneXResolution	        = 0xa20e,
+		FocalPlaneYResolution	        = 0xa20f,
+		FocalPlaneResolutionUnit	= 0xa210,
+		SubjectLocation 		= 0xa214,
+		ExposureIndex			= 0xa215,
+		SensingMethod			= 0xa217,
+		FileSource			= 0xa300,
+		SceneType			= 0xa301,
+		NewCFAPattern		        = 0xa302,
+		CustomRendered  		= 0xa401,
+		ExposureMode			= 0xa402,
+		WhiteBalance			= 0xa403,
+		DigitalZoomRatio		= 0xa404,
+		FocalLengthIn35mmFilm	        = 0xa405,
+		SceneCaptureType		= 0xa406,
+		GainControl			= 0xa407,
+		Contrast			= 0xa408,
+		Saturation			= 0xa409,
+		Sharpness			= 0xa40a,
+		DeviceSettingDescription	= 0xa40b,
+		SubjectDistanceRange		= 0xa40c,
+		ImageUniqueId   		= 0xa420,
+
+		// The Following IDs are not described the EXIF spec
+
+		// The XMP spec declares that XMP data should live 0x2bc when
+		// embedded in tiff images.
+		XMP                             = 0x02bc,
+		
+		// Print Image Matching data
+		PimIfdPointer              = 0xc4a5
+	}
+	
+	public enum ByteOrder {
+		Motorola,
+		Intel
+	}
+	
+	public enum ExifFormat {
+		Byte      = 1,
+		Ascii     = 2,
+		Short     = 3,
+		Long      = 4,
+		Rational  = 5,
+		Undefined = 7,
+		Slong     = 9,
+		SRational = 10
+	}
+	
+	public enum Ifd {
+		Zero = 0,
+		One,
+		Exif,
+		Gps,
+		InterOperability,
+		Count
+	}
+	
+	internal class ExifUtil {
+		
+		[DllImport ("libexif.dll")]
+		static extern IntPtr exif_tag_get_name (Tag tag);
+		
+		[DllImport ("libexif.dll")]
+		static extern IntPtr exif_tag_get_title (Tag tag);
+		
+		[DllImport ("libexif.dll")]
+		static extern IntPtr exif_tag_get_description (Tag tag);
+		
+		[DllImport ("libexif.dll")]
+		static extern IntPtr exif_byte_order_get_name (ByteOrder order);
+		
+		[DllImport ("libexif.dll")]
+		static extern IntPtr exif_format_get_name (ExifFormat format);
+		
+		[DllImport ("libexif.dll")]
+		static extern char exif_format_get_size (ExifFormat format);
+		
+		[DllImport ("libexif.dll")]
+		static extern IntPtr exif_ifd_get_name (Ifd ifd);
+		
+		public static string GetTagName (Tag tag)
+		{
+			
+			IntPtr raw_ret = exif_tag_get_name (tag);
+			return Marshal.PtrToStringAnsi (raw_ret);
+		}
+		
+		public static string GetTagTitle (Tag tag)
+		{
+			IntPtr raw_ret = exif_tag_get_title (tag);
+			return Marshal.PtrToStringAnsi (raw_ret);
+		}
+		
+		public static string GetTagDescription (Tag tag)
+		{
+			IntPtr raw_ret = exif_tag_get_description (tag);
+			return Marshal.PtrToStringAnsi (raw_ret);
+		}
+		
+		public static string GetByteOrderName (ByteOrder order)
+		{
+			IntPtr raw_ret = exif_byte_order_get_name (order);
+			return Marshal.PtrToStringAnsi (raw_ret);
+		}
+		
+		public static string GetFormatName (ExifFormat format)
+		{
+			IntPtr raw_ret = exif_format_get_name (format);
+			return Marshal.PtrToStringAnsi (raw_ret);
+		}
+		
+		public static char GetFormatSize (ExifFormat format)
+		{
+			return exif_format_get_size (format);
+		}
+		
+		public static string GetIfdName (Ifd ifd)
+		{			
+			IntPtr raw_ret = exif_ifd_get_name (ifd);
+			return Marshal.PtrToStringAnsi (raw_ret);
+		}
+		
+		public static string GetIfdNameExtended (Ifd ifd)
+		{
+			switch (ifd) {
+			case Ifd.Zero:
+				return Catalog.GetString ("Image Directory");
+			case Ifd.One:
+				return Catalog.GetString ("Thumbnail Directory");
+			case Ifd.Exif:
+				return Catalog.GetString ("Exif Directory");
+			case Ifd.Gps:
+				return Catalog.GetString ("GPS Directory");
+			case Ifd.InterOperability:
+				return Catalog.GetString ("InterOperability Directory");
+			default:
+				return Catalog.GetString ("Unknown Directory");
+			}
+		}
+		
+		public static DateTime DateTimeFromString(string dt)
+		{
+			// Exif DateTime strings are formatted as
+			//      "YYYY:MM:DD HH:MM:SS"
+			
+			string delimiters = " :";
+			string[] dt_data = dt.Split ( delimiters.ToCharArray(), 6 );
+			DateTime result;
+			result = new DateTime (Int32.Parse(dt_data[0]), Int32.Parse(dt_data[1]), Int32.Parse(dt_data[2]),
+					       Int32.Parse(dt_data[3]), Int32.Parse(dt_data[4]), Int32.Parse(dt_data[5]));
+			
+			return result;
+		}	
+		
+	}		
+
+	public abstract class ExifObject : IDisposable {
+		protected HandleRef handle;
+		
+		public HandleRef Handle {
+			get {
+				return handle;
+			}
+		}
+		
+		public ExifObject () {}
+
+		public ExifObject (IntPtr ptr)
+		{
+			handle = new HandleRef (this, ptr);
+		}
+		
+		protected abstract void Cleanup ();
+		
+		public void Dispose () {
+			Cleanup ();
+			System.GC.SuppressFinalize (this);
+		}
+		
+		~ExifObject ()
+		{
+			Cleanup ();
+		}
+	}
+	
+	[StructLayout(LayoutKind.Sequential)]
+	internal unsafe struct _ExifContent {
+		public IntPtr entries;
+		public uint count; 
+		public IntPtr parent;
+		
+		public IntPtr priv;
+	}
+	
+	public class ExifContent : ExifObject {
+		ExifData parent;
+		public ExifData Parent {
+			get {
+				return parent;
+			}
+		}
+
+		System.Collections.ArrayList entries;
+		
+		internal ExifContent (ExifData parent, IntPtr handle) : base (handle)
+		{
+			this.parent = parent;
+			exif_content_ref (this.handle);
+		}
+		
+		[DllImport ("libexif.dll")]
+		static extern void exif_content_ref (HandleRef handle);
+		
+		[DllImport ("libexif.dll")]
+		static extern void exif_content_unref (HandleRef handle);
+		
+		protected override void Cleanup ()
+		{
+			exif_content_unref (handle);
+		}
+		
+		[DllImport ("libexif.dll")]
+		internal static extern void exif_content_remove_entry (HandleRef content, HandleRef entry);
+		
+		[DllImport ("libexif.dll")]
+		internal static extern void exif_content_add_entry (HandleRef content, HandleRef entry);
+		
+		public ExifEntry Lookup (Tag tag)
+		{
+			Assemble ();
+			
+			foreach (ExifEntry entry in entries) {
+				if (entry.Tag == tag) {
+					return entry;
+				}
+			}		
+			
+			return null;
+		}
+
+		public bool Contains (ExifEntry entry)
+		{
+			Assemble ();
+
+			return entries.Contains (entry);
+		}
+
+		public ExifEntry GetEntry (Tag tag)
+		{
+			Assemble ();
+			
+			ExifEntry entry = Lookup (tag);
+			if (entry == null)
+				entry = new ExifEntry (this, tag);
+
+			return entry;
+		}
+
+		public void Add (ExifEntry entry)
+		{
+			Assemble ();
+
+			entries.Add (entry);
+			// This call can recurse into this function but it protects
+			// itself by checking if it the content already contains the entry
+			entry.SetParent (this);
+			exif_content_add_entry (this.handle, entry.Handle);	
+		}
+
+		public void Remove (ExifEntry entry)
+		{
+			Assemble ();
+			
+			entries.Remove (entry);
+			// This call can recurse into this function but it protects
+			// itself by checking if it the content already contains the entry
+			entry.SetParent (null);
+			exif_content_remove_entry (this.handle, entry.Handle);
+		}
+		
+		public ExifEntry [] GetEntries () 
+		{
+			Assemble ();
+
+			return (ExifEntry [])entries.ToArray (typeof (ExifEntry));
+		}
+		
+		[DllImport ("libexif.dll")]
+		internal static unsafe extern IntPtr exif_content_foreach_entry (HandleRef content,
+										 ExifContentForeachEntryFunc func,
+										 IntPtr data);
+		
+		internal delegate void ExifContentForeachEntryFunc (IntPtr entry_ptr, IntPtr data);
+		
+	        void AssembleEntry (IntPtr entry, IntPtr data)
+		{
+			entries.Add (new ExifEntry (this, entry));
+		}
+		
+		ExifContentForeachEntryFunc func;
+		
+		public void  Assemble ()
+		{
+			if (entries == null) {
+					entries = new System.Collections.ArrayList ();
+					
+					func = new ExifContentForeachEntryFunc (AssembleEntry);
+					exif_content_foreach_entry (this.Handle, func, IntPtr.Zero);
+			}
+		}
+	}
+	
+	
+	[StructLayout(LayoutKind.Sequential)]
+	internal struct _ExifEntry {
+		public Tag tag;
+		public int format;
+		public uint components;
+		public IntPtr data;
+		public uint  size;
+		
+		public IntPtr parent;
+		
+		public IntPtr priv;
+	}
+	
+	
+	public class ExifEntry : ExifObject {
+		ExifContent parent;
+		public ExifContent Parent {
+			get {
+				unsafe {
+					if (_handle->parent != parent.Handle.Handle)
+						throw new Exception ("Invalid Object State");
+					
+					return parent;
+				}
+			}
+		}
+		// Don't use this unless you know exactly what you are doing
+		internal void SetParent (ExifContent adoptor) {
+			// NOTE this api is ugly but the check prevent the parent state 
+			// from getting confused.  See ExifContent Add and Remove for the 
+			// other half.
+			if (parent != null && parent.Contains (this))
+				parent.Remove (this);
+
+			if (adoptor != null && !adoptor.Contains (this))
+				adoptor.Add (this);
+			
+			parent = adoptor;
+		}
+
+		internal ExifEntry (ExifContent parent, IntPtr native) : base (native)
+		{
+			this.handle = new HandleRef (this, native);
+			this.parent = parent;
+			exif_entry_ref (this.handle);
+		}
+
+		[DllImport ("libexif.dll")]
+		internal static extern IntPtr exif_entry_new ();
+
+		[DllImport ("libexif.dll")]
+		internal static extern void exif_entry_initialize (HandleRef handle, Tag tag);
+
+		public ExifEntry (ExifContent parent, Tag tag)
+		{
+			handle = new HandleRef (this, exif_entry_new ());
+			parent.Add (this);
+			this.Reset (tag);
+		}
+		
+		public void Reset (Tag tag)
+		{
+			unsafe {
+				// Free any exsting data so that _initialize will actually set the data
+				if (_handle->data != IntPtr.Zero)
+					ExifData.free (_handle->data);
+				_handle->data = IntPtr.Zero;
+			}
+
+			exif_entry_initialize (handle, tag);
+
+			//FIXME the month string in time fields in libexif ix currently broken so we do our own. 
+			if (tag == Tag.DateTime
+			    || tag == Tag.DateTimeOriginal
+			    || tag == Tag.DateTimeDigitized)
+				this.SetData (System.DateTime.Now);
+
+		}
+
+
+		public void Reset ()
+		{
+			Reset (Tag);
+		}
+
+		protected override void Cleanup ()
+		{
+			exif_entry_unref (this.handle);
+		}
+		
+		private unsafe _ExifEntry *_handle {
+			get {
+					return (_ExifEntry *)handle.Handle;
+			}
+		}
+		
+		public Tag Tag {
+			get {
+				unsafe {
+					return _handle->tag;
+				}
+			}
+		}
+		
+		public ExifFormat Format {
+			get {
+				unsafe {
+					return (ExifFormat) _handle->format;
+				}
+			}
+		}
+		
+		public byte [] Data {
+			get {
+				unsafe {
+					byte [] data = new byte [_handle->size]; 
+					
+					if (data.Length > 0)
+						Marshal.Copy (_handle->data, data, 0, (int)_handle->size);
+					
+					return data;
+				}
+			}
+		}
+		
+		public void SetData (byte [] data, int size)
+		{
+			unsafe {
+				if (data == null || data.Length == 0)
+					throw new System.Exception ("Invalid Length");
+				
+				if (_handle->data != IntPtr.Zero)
+					ExifData.free (_handle->data);
+				
+				_handle->data = ExifData.malloc ((uint)data.Length);
+				Marshal.Copy (data, 0, _handle->data, data.Length);
+
+				_handle->size = (uint) data.Length;
+				// This needs to be set per type as well but
+				// we do it here as well
+				_handle->components = (uint) (data.Length / size);
+			}
+		}
+		
+		public void SetData (byte []data)
+		{
+			SetData (data, 1);
+		}
+
+		public void SetData (uint s)
+		{
+			this.SetData (FSpot.BitConverter.GetBytes (s, this.ByteOrder == ByteOrder.Intel), 4);
+		}
+
+		public void SetData (ushort s)
+		{
+			this.SetData (FSpot.BitConverter.GetBytes (s, this.ByteOrder == ByteOrder.Intel), 2);
+		}	    
+
+		public void SetData (string value)
+		{
+			int len = System.Text.Encoding.UTF8.GetByteCount (value);
+			byte [] tmp = new byte [len + 1];
+			System.Text.Encoding.UTF8.GetBytes (value, 0, value.Length, tmp, 0);
+			tmp[len] = 0;
+			System.Console.WriteLine ("value = {0} len = {1}", value, len);
+			SetData (tmp, 1);
+		}
+
+		public void SetData (System.DateTime time)
+		{
+			SetData (time.ToString ("yyyy:MM:dd HH:mm:ss"));
+		}
+		
+		private unsafe void PutBytes (byte *dest, byte *src, int count)
+		{
+			int i = 0;
+			if (System.BitConverter.IsLittleEndian == (this.ByteOrder == ByteOrder.Intel)) {
+				for (i = 0; i < count; i++) {
+					//System.Console.WriteLine ("Copying normal byte [{0}]= {1}", i, src[i]);
+					dest [i] = src [i];
+				}
+			} else {
+				for (i = 0; i < count; i++) {
+					//System.Console.WriteLine ("Copying swapped byte [{0}]= {1}", i, src[i]);
+					dest [i] = src [count - i -1];  
+				}
+			}
+		}
+		
+		private unsafe uint ToUInt (byte *src)
+		{
+			uint value;
+			PutBytes ((byte *)&value, (byte *)src, 4);
+			return value;
+		}
+		
+		private unsafe ushort ToUShort (byte *src)
+		{
+			ushort value;
+			PutBytes ((byte *)&value, (byte *)src, 2);
+			return value;
+		}
+		
+		public uint [] GetDataUInt () {
+			unsafe {
+				uint [] result = new uint [_handle->components];
+				uint *src = (uint *)_handle->data;
+				//System.Console.WriteLine ("copying {0} components", result.Length); 
+				for (int i = 0; i < result.Length; i++) {
+					result [i] = ToUInt ((byte *)src);
+					//System.Console.WriteLine ("value[{0}] = {1}", i, result [i]);
+					src += i;
+				}
+				
+				return result;
+			}
+		}			
+
+		public ushort [] GetDataUShort () {
+			unsafe {
+				ushort [] result = new ushort [_handle->components];
+				ushort *src = (ushort *)_handle->data;
+				//System.Console.WriteLine ("copying {0} components", result.Length); 
+				for (int i = 0; i < result.Length; i++) {
+					result [i] = ToUShort ((byte *)src);
+					//System.Console.WriteLine ("value[{0}] = {1}", i, result [i]);
+					src += i;
+				}
+				
+				return result;
+			}
+		}
+
+
+		public int [] GetDataInt () {
+			return null;
+		}
+
+		public ByteOrder ByteOrder
+		{
+			get {
+				return parent.Parent.GetByteOrder ();
+			}
+		}
+
+		public string Description 
+		{
+			get {
+				return ExifUtil.GetTagDescription (Tag);
+			}
+		}
+		
+		public string Name
+		{
+			get {
+				return ExifUtil.GetTagName (Tag);
+			}
+		}
+		
+		public string Title
+		{
+			get {
+				return ExifUtil.GetTagTitle (Tag);
+			}
+		}
+		
+		static int fallback = 0;
+		
+		// FIXME this version is only valid in libexif 0.5
+		[DllImport ("libexif.dll")]
+		internal static extern IntPtr exif_entry_get_value (HandleRef handle);
+		[DllImport ("libexif.dll")]
+		internal static extern IntPtr exif_entry_get_value_brief (HandleRef handle);
+		
+		// FIXME this version is only valid in libexif 0.6
+		[DllImport ("libexif.dll")]
+		internal static extern IntPtr exif_entry_get_value (HandleRef handle, byte  [] value, int maxlen);
+		
+		public string Value
+		{
+			get {
+				if (fallback == 0) {
+					try {
+						exif_entry_get_value_brief (this.Handle);
+						fallback = 1;
+					} catch (EntryPointNotFoundException) {
+						fallback = -1;
+					}
+				}
+				
+				if (fallback > 0) 
+					return Marshal.PtrToStringAnsi (exif_entry_get_value (this.Handle));
+				else {
+					byte [] value = new byte [1024];
+					exif_entry_get_value (this.Handle, value, value.Length);
+
+					int i;
+					for (i = 0; i <  value.Length; i++) {
+						if (value [i] == 0) 
+							break;
+					}
+					int len = System.Math.Max (i, 0);
+					if (len == 0)
+						return null;
+					
+					return System.Text.Encoding.UTF8.GetString (value, 0, len);
+				}
+			}
+		}
+		
+		[DllImport ("libexif.dll")]
+		internal static extern void exif_entry_ref (HandleRef handle);
+		
+		[DllImport ("libexif.dll")]
+		internal static extern void exif_entry_unref (HandleRef handle);
+	}
+	
+	[StructLayout(LayoutKind.Sequential)]
+	internal struct _ExifData {
+		internal IntPtr ifd0;
+		internal IntPtr ifd1;
+		internal IntPtr ifd_exif;
+		internal IntPtr ifd_gps;
+		internal IntPtr ifd_interop;
+
+		internal IntPtr  data;
+		internal int     size;
+		
+		internal IntPtr priv;
+	}
+	
+	public class ExifData : ExifObject {
+		System.Collections.ArrayList ifds;
+		
+		[DllImport ("libexif.dll")]
+		internal static extern IntPtr exif_data_new ();
+		
+		public ExifData ()
+		{
+			handle = new HandleRef (this, exif_data_new ());
+		}
+		
+		[DllImport ("libexif.dll")]
+		internal static extern IntPtr exif_data_new_from_file (string path);
+			
+		public ExifData (string filename)
+		{
+			handle = new HandleRef (this, exif_data_new_from_file (filename));
+		}
+		
+		[DllImport ("libexif.dll")]
+		internal static extern IntPtr exif_data_new_from_data (byte [] data, uint size);
+
+		public ExifData (byte [] data)
+		{
+			handle = new HandleRef (this, exif_data_new_from_data (data, (uint) data.Length));
+		}
+
+		public ExifData (byte [] data, uint size)
+		{
+			handle = new HandleRef (this, exif_data_new_from_data (data, size));
+		}
+
+		[DllImport ("libc")] 
+		internal static extern void free (IntPtr address);
+		
+		[DllImport ("libc")] 
+		internal static extern IntPtr malloc (uint size);
+		
+		[DllImport ("libexif.dll")]
+		private static extern void exif_data_save_data (HandleRef handle, out IntPtr content, out uint size);		
+		public byte [] Save ()
+		{
+			Byte [] content = null;
+			uint size;
+			IntPtr data;
+			unsafe {
+				exif_data_save_data (handle, out data, out size);
+				
+				content = new byte [size];
+				Marshal.Copy (data, content, 0, (int)size);
+				free (data);
+			}
+				
+			System.Console.WriteLine ("Saved {0} bytes", content.Length);
+			return content;
+		}
+		
+		[DllImport ("libexif.dll")]
+		internal static extern void exif_data_unref (HandleRef data);
+		
+		[DllImport ("libexif.dll")]
+		internal static extern void exif_data_free (HandleRef data);
+
+		protected override void Cleanup ()
+		{
+			exif_data_unref (handle);
+		}
+		
+		[DllImport ("libexif.dll")]
+		internal static extern void exif_data_dump (HandleRef data);
+
+		public void Dump ()
+		{
+			exif_data_dump (handle);
+		}
+		
+		public ExifContent GetContents (Ifd ifd)
+		{
+			Assemble ();
+
+			return (ExifContent) ifds [(int)ifd];
+		}
+
+		public ExifContent [] GetContents ()
+		{
+			Assemble ();
+
+			return (ExifContent []) ifds.ToArray (typeof (ExifContent));
+		}
+
+		[DllImport("libexif.dll")]
+		internal static extern ByteOrder exif_data_get_byte_order (HandleRef handle);
+		
+		public ByteOrder GetByteOrder ()
+		{
+			return exif_data_get_byte_order (handle);
+		}
+		
+		internal delegate void ExifDataForeachContentFunc (IntPtr content, IntPtr data);
+		
+		[DllImport ("libexif.dll")]
+		internal unsafe static extern void exif_data_foreach_content(HandleRef handle, ExifDataForeachContentFunc func, IntPtr data);
+		
+		unsafe void AssembleIfds (IntPtr content, IntPtr data)
+		{
+			ifds.Add (new ExifContent (this, content));
+		}
+		
+		public ExifEntry LookupFirst (Tag tag)
+		{
+			Assemble ();
+			foreach (ExifContent content in ifds) {
+				if (content == null)
+					continue;
+				
+				ExifEntry entry = content.Lookup (tag);
+				if (entry != null)
+					return entry;
+			}
+			return null;
+		}
+
+		public string LookupFirstValue (Tag tag)
+		{
+			ExifEntry entry = LookupFirst (tag);
+			if (entry != null) {
+				return entry.Value;
+			}
+			return null;
+		}
+
+		public void Assemble ()
+		{
+			if (ifds == null) {
+				ifds = new System.Collections.ArrayList ();
+
+				if (handle.Handle != IntPtr.Zero)
+					exif_data_foreach_content (handle, new ExifDataForeachContentFunc (AssembleIfds), IntPtr.Zero);
+			}
+		}
+		
+		byte [] empty = new byte [0];
+		public byte [] Data {
+			get {
+				unsafe {
+					_ExifData * obj = (_ExifData *) Handle.Handle;
+					byte [] result;
+					
+					if (obj == null || obj->data == (IntPtr) 0)
+						result = empty;
+					else {
+						result = new byte [obj->size];
+						Marshal.Copy (obj->data, result, 0, obj->size);
+						
+					}
+					return result;
+				}
+			}
+			set {
+				unsafe {
+					_ExifData * obj = (_ExifData *) Handle.Handle;
+					if (value.Length > 65533)
+						throw new System.Exception ("Thumbnail too large");
+					
+					if (obj->data != IntPtr.Zero)
+						free (obj->data);
+					
+					obj->data = malloc ((uint)value.Length);
+					Marshal.Copy (value, 0, obj->data, value.Length);
+				}
+			}
+		}
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/ImageFile.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/ImageFile.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,243 @@
+using System;
+using System.IO;
+using UriUtils = Beagle.Util.UriFu;
+
+namespace FSpot {
+	public class ImageFormatException : ApplicationException {
+		public ImageFormatException (string msg) : base (msg)
+		{
+		}
+	}
+
+	public class ImageFile : IDisposable {
+		protected Uri uri;
+
+		static System.Collections.Hashtable name_table;
+
+		public ImageFile (string path) 
+		{
+			this.uri = UriUtils.PathToFileUri (path);
+		}
+		
+		public ImageFile (Uri uri)
+		{
+			this.uri = uri;
+		}
+		
+		protected Stream Open ()
+		{
+			//Gnome.Vfs.Uri vfs = new Gnome.Vfs.Uri (uri.ToString ());
+			// FIXME, this seems like the sane thing to do, but vfs streams seem to 
+			// actually be faster and they need more testing.
+			//if (vfs.IsLocal)
+			return File.OpenRead (uri.LocalPath);
+
+#if false
+			System.Console.WriteLine ("open uri = {0}", uri.ToString ());
+			return new Gnome.Vfs.VfsStream (uri.ToString (), FileMode.Open);
+#endif
+		}
+
+		public virtual Stream PixbufStream ()
+		{
+			return Open ();
+		}
+
+		static ImageFile ()
+		{
+			name_table = new System.Collections.Hashtable ();
+			name_table [".svg"] = typeof (FSpot.Svg.SvgFile);
+			name_table [".gif"] = typeof (ImageFile);
+			name_table [".bmp"] = typeof (ImageFile);
+			name_table [".jpeg"] = typeof (JpegFile);
+			name_table [".jpg"] = typeof (JpegFile);
+			name_table [".png"] = typeof (FSpot.Png.PngFile);
+			name_table [".cr2"] = typeof (FSpot.Tiff.Cr2File);
+			name_table [".nef"] = typeof (FSpot.Tiff.NefFile);
+			name_table [".pef"] = typeof (FSpot.Tiff.NefFile);
+			name_table [".raw"] = typeof (FSpot.Tiff.NefFile);
+			name_table [".kdc"] = typeof (FSpot.Tiff.NefFile);
+			name_table [".arw"] = typeof (FSpot.Tiff.NefFile);
+			name_table [".tiff"] = typeof (FSpot.Tiff.TiffFile);
+			name_table [".tif"] = typeof (FSpot.Tiff.TiffFile);
+			name_table [".orf"] =  typeof (FSpot.Tiff.NefFile);
+			name_table [".srf"] = typeof (FSpot.Tiff.NefFile);
+			name_table [".dng"] = typeof (FSpot.Tiff.DngFile);
+			name_table [".crw"] = typeof (FSpot.Ciff.CiffFile);
+			name_table [".ppm"] = typeof (FSpot.Pnm.PnmFile);
+			name_table [".mrw"] = typeof (FSpot.Mrw.MrwFile);
+			name_table [".raf"] = typeof (FSpot.Raf.RafFile);
+			name_table [".x3f"] = typeof (FSpot.X3f.X3fFile);
+		}
+
+		public Uri Uri {
+			get { return this.uri; }
+		}
+
+		public PixbufOrientation Orientation {
+			get { return GetOrientation (); }
+		}
+
+		public virtual string Description
+		{
+			get { return null; }
+		}
+		
+#if false
+		public virtual void Save (Gdk.Pixbuf pixbuf, System.IO.Stream stream)
+		{
+			throw new NotImplementedException ();
+		}
+
+		protected Gdk.Pixbuf TransformAndDispose (Gdk.Pixbuf orig)
+		{
+			if (orig == null)
+				return null;
+
+			Gdk.Pixbuf rotated = PixbufUtils.TransformOrientation (orig, this.Orientation, true);
+			//ValidateThumbnail (photo, rotated);
+			if (rotated != orig)
+				orig.Dispose ();
+			
+			return rotated;
+		}
+		
+		public virtual Gdk.Pixbuf Load ()
+		{
+			using (Stream stream = PixbufStream ()) {
+				Gdk.Pixbuf orig = new Gdk.Pixbuf (stream);
+				return TransformAndDispose (orig);
+			}
+		}
+		
+		public virtual Gdk.Pixbuf Load (int max_width, int max_height)
+		{
+			System.IO.Stream stream = PixbufStream ();
+			if (stream == null) {
+				Gdk.Pixbuf orig = this.Load ();
+				Gdk.Pixbuf scaled = PixbufUtils.ScaleToMaxSize (orig,  max_width, max_height);	
+				orig.Dispose ();
+				return scaled;
+			}
+
+			using (stream) {
+				PixbufUtils.AspectLoader aspect = new PixbufUtils.AspectLoader (max_width, max_height);
+				return aspect.Load (stream, Orientation);
+			}	
+		}
+#endif
+		public virtual PixbufOrientation GetOrientation () 
+		{
+			return PixbufOrientation.TopLeft;
+		}
+#if false		
+		// FIXME this need to have an intent just like the loading stuff.
+		public virtual Cms.Profile GetProfile ()
+		{
+			return null;
+		}
+#endif	
+		public virtual System.DateTime Date 
+		{
+			get {
+				return System.IO.File.GetCreationTimeUtc (uri.LocalPath);
+#if false
+				// FIXME mono uses the file change time (ctime) incorrectly
+				// as the creation time so we try to work around that slightly
+				Gnome.Vfs.FileInfo info = new Gnome.Vfs.FileInfo (uri.ToString ());
+
+				DateTime create = info.Ctime;
+				DateTime write = info.Mtime;
+
+				if (create < write)
+					return create;
+				else 
+					return write;
+#endif
+			}
+		}
+
+		[Obsolete ("use HasLoader (System.Uri) instead")]
+		public static bool HasLoader (string path)
+		{
+			return HasLoader (UriUtils.PathToFileUri (path));
+		}
+		
+		public static bool HasLoader (Uri uri)
+		{
+			string path = uri.AbsolutePath;
+			string extension = System.IO.Path.GetExtension (path).ToLower ();
+			System.Type t = (System.Type) name_table [extension];
+			
+			return (t != null);
+		}
+
+		[Obsolete ("use Create (System.Uri) instead")]
+		public static ImageFile Create (string path)
+		{
+			return Create (UriUtils.PathToFileUri (path));
+		}
+
+		public static ImageFile Create (Uri uri)
+		{
+			string path = uri.AbsolutePath;
+			string extension = System.IO.Path.GetExtension (path).ToLower ();
+			System.Type t = (System.Type) name_table [extension];
+			ImageFile img;
+
+			if (t != null)
+				img = (ImageFile) System.Activator.CreateInstance (t, new object[] { uri });
+			else 
+				img = new ImageFile (uri);
+
+			return img;
+		}
+		
+		// FIXME these are horrible hacks to get a temporary name
+		// with the right extension for ImageFile until we use the mime data
+		// properly.  It is here to make sure we can find the places that use
+		// this hack
+		public static string TempPath (string name)
+		{
+			return TempPath (name, System.IO.Path.GetExtension (name));
+		}
+		
+		public static string TempPath (string name, string extension)
+		{
+			string temp = System.IO.Path.GetTempFileName ();
+			string imgtemp = temp + "." + extension;
+
+			System.IO.File.Move (temp, imgtemp);
+
+			return imgtemp;
+		}
+
+		public void Dispose ()
+		{
+			Close ();
+			GC.SuppressFinalize (this);
+		}
+
+		protected virtual void Close ()
+		{
+		}
+
+		public static bool IsRaw (string name)
+		{
+			string [] raw_extensions = {".nef", ".crw", ".cr2", ".arw", ".mrw", ".pef", ".dng"};
+			foreach (string ext in raw_extensions)
+				if (ext == System.IO.Path.GetExtension (name).ToLower ())
+					return true;
+			return false;
+		}
+
+		public static bool IsJpeg (string name)
+		{
+			string [] jpg_extensions = {".jpg", ".jpeg"};
+			foreach (string ext in jpg_extensions)
+				if (ext == System.IO.Path.GetExtension (name).ToLower ())
+					return true;
+			return false;
+		}
+	} 
+}

Added: trunk/beagle/Util/F-Spot/Imaging/IptcFile.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/IptcFile.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,427 @@
+using SemWeb;
+using Mono.Unix;
+
+namespace FSpot.Iptc {
+	public enum Format
+	{
+		Unknown,
+		String,
+		Numeric,
+		Binary,
+		Byte,
+		Short,
+		Int,
+		Date,
+		Time
+	};
+
+	public enum Record
+	{
+		Envelope = 1 << 8,
+		Application = 2 << 8,
+		NewsphotoParameter = 3 << 8,
+		NotAllocated1 = 4 << 8,
+		NotAllocated2 = 5 << 8,
+		AbstractRelationship = 6 << 8,
+		PreObjectData = 7 << 8,
+		ObjectData = 8 << 8,
+		PostObjectData = 9 << 8
+	}
+
+	public enum DataSetID
+	{
+		ModelVersion        = Record.Envelope | 0,
+		Destination         = Record.Envelope | 5,
+		FileFormat          = Record.Envelope | 20,
+		FileFormatVersion   = Record.Envelope | 22,
+		ServiceIdentifier   = Record.Envelope | 30,
+		EnvelopeNumber      = Record.Envelope | 40,
+		ProductID           = Record.Envelope | 50,
+		EnvelopePriority    = Record.Envelope | 60,
+		DateSent            = Record.Envelope | 70,
+		TimeSent            = Record.Envelope | 80,
+		CodedCharacterSet   = Record.Envelope | 90,
+		UNO                 = Record.Envelope | 100,
+		ARMIdentifier       = Record.Envelope | 120,
+		ARMVersion          = Record.Envelope | 122,
+
+		RecordVersion            = Record.Application | 0,
+		ObjectTypeReference      = Record.Application | 3,
+		ObjectAttributeReference = Record.Application | 4,
+		ObjectName               = Record.Application | 5,
+		EditStatus               = Record.Application | 7,
+		EditorialUpdate          = Record.Application | 8,
+		Urgency                  = Record.Application | 10,
+		SubjectReference         = Record.Application | 12,
+		Category                 = Record.Application | 15,
+		SupplementalCategory     = Record.Application | 20,
+		FixtureIdentifier        = Record.Application | 22,
+		Keywords                 = Record.Application | 25,
+		ContentLocationCode      = Record.Application | 26,
+		ContentLocationName      = Record.Application | 27,
+		ReleaseDate              = Record.Application | 30,
+		ReleaseTime              = Record.Application | 35,
+		ExpirationDate           = Record.Application | 37,
+		ExpirationTime           = Record.Application | 38,
+		SpecialInstructions      = Record.Application | 40,
+		ActionAdvised            = Record.Application | 42,
+		ReferenceService         = Record.Application | 45,
+		ReferenceDate            = Record.Application | 47,
+		ReferenceNumber          = Record.Application | 50,
+		DateCreated              = Record.Application | 55,
+		TimeCreated              = Record.Application | 60,
+		DigitalCreationDate      = Record.Application | 62,
+		DigitalCreationTime      = Record.Application | 63,
+		OriginatingProgram       = Record.Application | 65,
+		ProgramVersion           = Record.Application | 70,
+		ObjectCycle              = Record.Application | 75,
+		ByLine                   = Record.Application | 80,
+		ByLineTitle              = Record.Application | 85,
+		City                     = Record.Application | 90,
+		Sublocation              = Record.Application | 92,
+		ProvinceState            = Record.Application | 95,
+		PrimaryLocationCode      = Record.Application | 100,
+		PrimaryLocationName      = Record.Application | 101,
+		OriginalTransmissionReference = Record.Application | 103,
+		Headline                 = Record.Application | 105,
+		Credit                   = Record.Application | 110,
+		Source                   = Record.Application | 115,
+		CopyrightNotice          = Record.Application | 116,
+		Contact                  = Record.Application | 118,
+		CaptionAbstract          = Record.Application | 120,
+		WriterEditor             = Record.Application | 122,
+		RasterizedCaption        = Record.Application | 125,
+		ImageType                = Record.Application | 130,
+		ImageOrientation         = Record.Application | 131,
+		LanguageIdentifier       = Record.Application | 135,
+		AudioType                = Record.Application | 150,
+		AudioSamplingRate        = Record.Application | 151,
+		AudioSamplingReduction   = Record.Application | 152,
+		AudioDuration            = Record.Application | 153,
+		AudioOutcue              = Record.Application | 154,
+		ObjectDataPreviewFileFormat = Record.Application | 200,
+		ObjectDataPreviewFileFormatVersion  = Record.Application | 201,
+		ObjectDataPreviewData    = Record.Application | 202,
+		
+		SizeMode                 = Record.PreObjectData | 10,
+		MaxSubfileSize           = Record.PreObjectData | 20,
+		ObjectDataSizeAnnounced  = Record.PreObjectData | 90,
+		MaximumObjectDataSize    = Record.PreObjectData | 95,
+
+		Subfile                  = Record.ObjectData | 10,
+
+		ConfirmedObjectDataSize  = Record.PostObjectData | 10
+	}
+
+	public class DataSetInfo 
+	{
+		DataSetID ID;
+		public string Name;
+		public string Description;
+		public string XmpName;
+		bool Mandatory;
+		bool Repeatable;
+		uint MinSize;
+		uint MaxSize;
+		public Format Format;
+		
+		public static System.Collections.Hashtable IDTable;
+
+		static DataSetInfo ()
+		{
+			IDTable = new System.Collections.Hashtable ();
+			foreach (DataSetInfo info in datasets) {
+				IDTable [info.ID] = info;
+			}
+		}
+		
+		public override string ToString ()
+		{
+		        return System.String.Format ("{0}-({1},{2},{3},{4})", Name, Mandatory, Repeatable, MinSize, MaxSize);
+		}
+
+		private static DataSetInfo [] datasets = {
+			new DataSetInfo (DataSetID.ModelVersion, Format.Short, "Model Version", true, false, 2, 2, 
+					 Catalog.GetString ("IPTC Information Interchange Model (IIM) Version number")),
+			new DataSetInfo (DataSetID.Destination, Format.String, "Destination", false, true, 0, 1024, 
+					 Catalog.GetString ("OSI Destination routing information")),
+			new DataSetInfo (DataSetID.FileFormat, Format.Short, "File Format", true, false, 2, 2, 
+					 Catalog.GetString ("IPTC file format")),
+			new DataSetInfo (DataSetID.ServiceIdentifier, Format.String, "Service Identifier", true, false, 0, 10, 
+					 Catalog.GetString ("Identifies the provider and product")),
+			new DataSetInfo (DataSetID.EnvelopeNumber, Format.Numeric, "Envelope Number", true, false, 8, 8, 
+					 Catalog.GetString ("A unique number identifying the envelope")), // FIXME
+			new DataSetInfo (DataSetID.ProductID, Format.Numeric, "Product I.D.", false, true, 0, 32, 
+					 Catalog.GetString ("A unique number")), // FIXME
+			new DataSetInfo (DataSetID.EnvelopePriority, Format.Numeric, "Envelope Priority", false, false, 1, 1, 
+					 Catalog.GetString ("The envelope handling priority between 1 (most urgent) and 9 (least urgent)")),
+			new DataSetInfo (DataSetID.DateSent, Format.Date, "Date Sent", true, false, 8, 8, 
+					 Catalog.GetString ("The year, month and day (CCYYMMDD) the service sent the material")),
+			new DataSetInfo (DataSetID.TimeSent, Format.Date, "Time Sent", false, false, 11, 11, 
+					 Catalog.GetString ("The hour, minute and second (HHMMSS) the service sent the material")),
+			new DataSetInfo (DataSetID.CodedCharacterSet, Format.Time, "Coded Character Set", false, false, 0, 32, 
+					 Catalog.GetString ("The character set designation")), // FIXME
+			new DataSetInfo (DataSetID.UNO, Format.String, "Unique Name of Object", false, false, 14, 80,
+					 Catalog.GetString ("External globally unique object identifier")),
+			// UCD : IPR  : ODE            : OVI
+			//   8 :[1-32]:[61 - IPR count]:[1-9]
+
+			new DataSetInfo (DataSetID.ARMIdentifier, Format.Short, "ARM Identifier", false, false, 2, 2,
+					 Catalog.GetString ("Abstract Relationship Method (ARM) identifier")),
+			new DataSetInfo (DataSetID.ARMVersion, Format.Short, "ARM Version", false, false, 2, 2,
+					 Catalog.GetString ("Abstract Relationship Method (ARM) version number.")),
+			
+			new DataSetInfo (DataSetID.RecordVersion, Format.Short, "Record Version", false, false, 2, 2,
+					 Catalog.GetString ("Number identifying the IIM version this application record uses")),
+			new DataSetInfo (DataSetID.ObjectTypeReference, Format.String, "Object Type Reference", false, false, 3, 64,
+					 Catalog.GetString ("Object type reference")), // FIXME
+			// Object Type Number : Object Type Name
+			//                  2 : [0-64]
+
+			new DataSetInfo (DataSetID.ObjectAttributeReference, Format.String, "Object Attribute Reference", false, true, 4, 68,
+					 Catalog.GetString ("Object attribute reference")), // FIXME
+			
+			// Object Attribute number : Object Attribute Name
+			//                       3 : [0-64]
+			
+			new DataSetInfo (DataSetID.ObjectName, Format.String, "Object Name", false, false, 4, 68,
+					 Catalog.GetString ("Object name"), "dc:title"), // FIXME
+			new DataSetInfo (DataSetID.EditStatus, Format.String, "Edit Status", false, false, 0, 64,
+					 Catalog.GetString ("Status of the objectdata according to the provider")),
+			new DataSetInfo (DataSetID.EditorialUpdate, Format.String, "Object Name", false, false, 4, 68,
+					 Catalog.GetString ("Object name")), // FIXME
+			new DataSetInfo (DataSetID.Sublocation, Format.String, "Location", false, false, 0, 32,
+					 Catalog.GetString ("Location within a city or area where the object originates"),
+					 "Iptc4xmpCore:Location"),
+			new DataSetInfo (DataSetID.City, Format.String, "City", false, false, 0, 32,
+					 Catalog.GetString ("Name of the city the content is focussing on"),
+					 "photoshop:City"),
+			new DataSetInfo (DataSetID.CopyrightNotice, Format.String, "Copyright Notice", false, false, 0, 128,
+					 Catalog.GetString ("Copyright information for"),
+					 "dc:rights"),
+			new DataSetInfo (DataSetID.PrimaryLocationName, Format.String, "Country", false, false, 0, 64,
+					 Catalog.GetString ("Full name of the country of the focus of the content"),
+					 "photoshop:Country"),
+			new DataSetInfo (DataSetID.PrimaryLocationCode, Format.String, "ISO Country Code", false, false, 0, 3,
+					 Catalog.GetString ("Two or three letter ISO3166 code of the country of the focus of the content"),
+					 "Iptc4xmpCore:CountryCode"),
+			new DataSetInfo (DataSetID.ByLine, Format.String, "Creator", false, false, 0, 32,
+					 Catalog.GetString ("Creator of the content"),  // FIXME
+					 "dc:creator"),
+			new DataSetInfo (DataSetID.Credit, Format.String, "Provider", false, false, 0, 32,
+					 Catalog.GetString ("Provider of the object"),
+					 "photoshop:Credit"),
+			new DataSetInfo (DataSetID.ByLineTitle, Format.String, "Creator's Jobtitle", false, true, 0, 32,
+					 Catalog.GetString ("The title of the author or creator"),
+					 "photoshop:AuthorsPosition"),
+			new DataSetInfo (DataSetID.WriterEditor, Format.String, "Caption/Description writer", false, true, 0, 32,
+					 Catalog.GetString ("The person involved in writing, editing or " +
+								       "correcting the object data or caption/abstract"),
+					 "photoshop:CaptionWriter"),
+			new DataSetInfo (DataSetID.Headline, Format.String, "Headline", false, false, 0, 256,
+					 Catalog.GetString ("Headline of the content"),
+					 "photoshop:Headline"),
+			new DataSetInfo (DataSetID.SpecialInstructions, Format.String, "Instructions", false, false, 0, 256,
+					 Catalog.GetString ("Instructions from the creator to the receiver not covered by other fields"),
+					 "photoshop:Instructions"),
+			new DataSetInfo (DataSetID.ObjectAttributeReference, Format.String, "Intellectual genre", false, true, 4, 68,
+					 Catalog.GetString ("Intellectual genre of the object"),
+					 "Iptc4xmpCore:IntellectualGenre"),
+			// Object Attribute number : Object Attribute Name
+			//                       3 : [0-64]
+		};		
+
+		public static DataSetInfo FindInfo (DataSetID id)
+		{
+			foreach (DataSetInfo info in datasets)
+				if (id == (DataSetID)info.ID)
+					return info;
+						
+			return new DataSetInfo (id, Format.Unknown, "Unknown", false, false, 3, 64,
+						Catalog.GetString ("Unknown IIM DataSet"));
+		}
+
+		protected DataSetInfo (DataSetID id, Format format, string name, bool mandatory, bool repeatable, uint min, uint max, string description) : this (id, format, name, mandatory, repeatable, min, max, description, null)
+		{ }
+
+		protected DataSetInfo (DataSetID id, Format format, string name, bool mandatory, bool repeatable, uint min, uint max, string description, string xmpname)
+		{
+			ID = id;
+			Name = name;
+			Description = description;
+			Format = format;
+		        Mandatory = mandatory;
+			Repeatable = repeatable;
+			MinSize = min;
+			MaxSize = max;
+			XmpName = xmpname;
+		}
+	}
+
+	public class DataSet 
+	{
+		public byte RecordNumber;
+		public byte DataSetNumber;
+		public byte [] Data;
+		
+		const byte TagMarker = 0x1c;
+		const ushort LengthMask = 1 << 15;
+
+		public void Load (System.IO.Stream stream)
+		{
+			byte [] rec = new byte [5];
+			int len = stream.Read (rec, 0, rec.Length);
+			if (rec [0] != TagMarker) {
+				throw new System.Exception (System.String.Format ("Invalid tag marker found {0} != {1} with {2} bytes remaining {3}", 
+							    rec[0].ToString ("x"), TagMarker.ToString ("x"), stream.Length - stream.Position, len));
+			}
+			RecordNumber = rec [1];
+			DataSetNumber = rec [2];
+
+			ulong length = FSpot.BitConverter.ToUInt16 (rec, 3, false);			
+
+			if ((length & (LengthMask)) > 0) {
+				// Note: if the high bit of the length is set the record is more than 32k long
+				// and the length is stored in what would normaly be the record data, so we read
+				// that data convert it to a long and continue on.
+				ushort lsize = (ushort)((ushort)length & ~LengthMask);
+				if (lsize > 8)
+					throw new System.Exception ("Wow, that is a lot of data");
+
+				byte [] ldata = new byte [8];
+				stream.Read (ldata, 8 - lsize, lsize);
+				length = FSpot.BitConverter.ToUInt64 (ldata, 0, false);
+			}
+
+			// FIXME if the length is greater than 32768 we re
+			Data = new byte [length];
+			stream.Read (Data, 0, Data.Length);
+		}
+
+		public DataSetID ID {
+			get {
+				return (DataSetID) (RecordNumber << 8 | DataSetNumber);
+			}
+		}
+
+		public string XmpPredicate {
+			get {
+				DataSetInfo info = (DataSetInfo) DataSetInfo.IDTable [this.ID];
+				if (info != null && info.XmpName != null) {
+					return MetadataStore.Namespaces.Resolve (info.XmpName);
+				}
+				return null;
+			}
+		}
+
+		public void Save (System.IO.Stream stream)
+		{
+			stream.WriteByte (TagMarker);
+			stream.WriteByte (RecordNumber);
+			stream.WriteByte (DataSetNumber);
+			if (Data.Length < LengthMask) {
+				byte [] len = FSpot.BitConverter.GetBytes ((ushort)Data.Length, false);
+				stream.Write (len, 0, len.Length);
+			} else {
+				byte [] len =  FSpot.BitConverter.GetBytes ((ushort)LengthMask & 8, false);
+				stream.Write (len, 0, len.Length);
+				len = FSpot.BitConverter.GetBytes ((ulong) Data.Length, false);
+				stream.Write (len, 0, len.Length);
+			}
+			stream.Write (Data, 0, Data.Length);
+		}
+
+		public string XmpObject 
+		{
+			get {
+				//DataSetInfo info = (DataSetInfo) DataSetInfo.IDTable [this.ID];
+				//if (info != null && info.Format == Format.String) {
+					return System.Text.Encoding.UTF8.GetString (this.Data);
+					//}
+					//return null;
+			}
+		}
+	}
+
+	public class IptcFile : SemWeb.StatementSource
+	{
+		System.Collections.ArrayList sets = new System.Collections.ArrayList ();
+
+                // False seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+		
+		public IptcFile (System.IO.Stream stream)
+		{
+			Load (stream);
+		}
+
+		public void Select (SemWeb.StatementSink sink)
+		{
+			Entity keywords = null;
+
+			foreach (DataSet data in sets) {
+				switch (data.ID) {
+				case DataSetID.CopyrightNotice:
+					MetadataStore.AddLiteral (sink, "dc:rights", "rdf:Alt", new SemWeb.Literal (data.XmpObject, "x-default", null));
+					break;
+				case DataSetID.ByLine:
+					MetadataStore.AddLiteral (sink, "dc:creator", "rdf:Seq", new SemWeb.Literal (data.XmpObject, "x-default", null));
+					break;
+				case DataSetID.CaptionAbstract:
+					MetadataStore.AddLiteral (sink, "dc:description", "rdf:Alt", new SemWeb.Literal (data.XmpObject, "x-default", null));
+					break;
+				case DataSetID.ObjectName:
+					MetadataStore.AddLiteral (sink, "dc:title", "rdf:Alt", new SemWeb.Literal (data.XmpObject, "x-default", null));
+					break;
+				case DataSetID.Keywords:
+					if (keywords == null) {
+						keywords = new BNode ();
+						sink.Add (new Statement (MetadataStore.FSpotXMPBase, 
+									 MetadataStore.Namespaces.Resolve ("dc:subject"),
+									 keywords)); 
+						sink.Add (new Statement (keywords, 
+									 (Entity)MetadataStore.Namespaces.Resolve ("rdf:type"),
+									 (Entity)MetadataStore.Namespaces.Resolve ("rdf:Bag")));
+					}
+					sink.Add (new Statement (keywords, 
+								 MetadataStore.Namespaces.Resolve ("rdf:li"), 
+								 new SemWeb.Literal (data.XmpObject, "x-default", null)));
+					break;
+				default:
+					if (data.XmpPredicate != null) {
+						sink.Add (new Statement (MetadataStore.FSpotXMPBase, 
+									 (Entity)data.XmpPredicate, 
+									 new SemWeb.Literal (data.XmpObject)));
+					}
+					break;
+				}
+			}
+		}
+		
+		public void Load (System.IO.Stream stream)
+		{
+			while (stream.Position < stream.Length) {
+				DataSet data = new DataSet ();
+				
+				try {
+					data.Load (stream);
+				} catch (System.Exception) {
+					//System.Console.WriteLine (e.ToString ());
+				}
+				//DataSetInfo info = DataSetInfo.FindInfo (data.ID);
+				//System.Console.WriteLine ("{0}:{1} - {2} {3}", data.RecordNumber, data.DataSetNumber, 
+				//			  data.ID.ToString (), info.Description);
+				sets.Add (data);
+			}
+		}
+
+		public void Save (System.IO.Stream stream) 
+		{
+			foreach (DataSet data in sets) {
+				data.Save (stream);
+			}
+		}
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/JpegFile.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/JpegFile.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,401 @@
+#define USE_TIFF
+using System;
+using System.IO;
+using FSpot.Xmp;
+using FSpot.Tiff;
+
+#if ENABLE_NUNIT
+using NUnit.Framework;
+#endif
+
+namespace FSpot {
+	public interface IThumbnailContainer {
+#if false
+		Gdk.Pixbuf GetEmbeddedThumbnail ();
+#endif
+	}
+
+	public class JpegFile : ImageFile, IThumbnailContainer, SemWeb.StatementSource {
+		private Exif.ExifData exif_data;
+		private XmpFile xmp;
+		private JpegHeader header;
+		private FSpot.Tiff.Header exif_header;
+
+                // False seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+		
+		public JpegFile (Uri uri) : base (uri)
+		{
+			try {
+				// Console.WriteLine ("approximate quality = {0}", Header.GuessQuality ());
+			} catch (Exception e) {
+				System.Console.WriteLine (e);
+			}
+		}
+		
+		public JpegFile (string path) : base (path) 
+		{
+		}
+
+		public JpegHeader Header {
+			get {
+				if (header == null) {
+					using (Stream stream = this.Open ()) {
+						header = new JpegHeader (stream, true);
+					}
+				}
+				return header;
+			}
+		}
+
+		public FSpot.Tiff.Header ExifHeader {
+			get {
+				if (exif_header == null) {
+					exif_header = Header.GetExifHeader ();
+				}
+				return exif_header;
+			}
+		}
+
+		public void Select (SemWeb.StatementSink sink)
+		{
+			Header.Select (sink);
+		}
+
+#if false
+		public override Cms.Profile GetProfile ()
+		{
+			return Header.GetProfile ();
+		}
+#endif
+		public override string Description {
+			get {
+#if USE_TIFF
+				try {
+					SubdirectoryEntry sub = (SubdirectoryEntry) ExifHeader.Directory.Lookup (TagId.ExifIfdPointer);
+					if (sub != null) {
+						DirectoryEntry entry = sub.Directory [0].Lookup (TagId.UserComment);
+						if (entry != null)
+							return entry.ValueAsString [0];
+					}
+				} catch (System.Exception e) {
+					Console.WriteLine (e);
+				}
+				return null;
+#else
+				try {
+					Exif.ExifContent exif_content = this.ExifData.GetContents (Exif.Ifd.Exif);
+					Exif.ExifEntry entry = exif_content.Lookup (Exif.Tag.UserComment);
+					
+					if (entry == null)
+						return null;
+					
+					UserComment comment = new UserComment (entry.Data, entry.ByteOrder == Exif.ByteOrder.Intel);
+					return comment.Value;
+				} catch (Exception e) {
+					// errors here shouldn't be fatal
+					return null;
+				}
+#endif				
+			}
+		}
+
+		public void SetDescription (string value)
+		{
+			Exif.ExifContent exif_content = this.ExifData.GetContents (Exif.Ifd.Exif);			
+			Exif.ExifEntry entry = exif_content.GetEntry (Exif.Tag.UserComment);
+
+			UserComment comment = new UserComment (value);
+			byte [] data = comment.GetBytes (entry.ByteOrder == Exif.ByteOrder.Intel);
+			entry.SetData (data);
+		}
+		
+		public void SetXmp (XmpFile xmp)
+		{
+			this.xmp = xmp;
+		}
+
+		private void UpdateMeta ()
+		{
+			Exif.ExifContent image_content = this.ExifData.GetContents (Exif.Ifd.Zero);
+			image_content.GetEntry (Exif.Tag.Software).SetData ("F-Spot" + " version " + "r3823");
+
+			// set the write time in the datetime tag
+			image_content.GetEntry (Exif.Tag.DateTime).Reset ();
+		}
+
+		private void SaveMetaData (System.IO.Stream input, System.IO.Stream output)
+		{
+			JpegHeader header = new JpegHeader (input);
+			UpdateMeta ();
+			
+			// Console.WriteLine ("updated metadata");
+			header.SetExif (this.ExifData);
+			// Console.WriteLine ("set exif");
+			if (xmp != null)
+				header.SetXmp (xmp);
+			// Console.WriteLine ("set xmp");
+			header.Save (output);
+			// Console.WriteLine ("saved");
+		}
+		
+#if false
+		public void SaveMetaData (string path)
+		{
+			UpdateMeta ();
+
+			//Get file permissions... (mkstemp does not keep permissions or ownership)
+			Mono.Unix.Native.Stat stat;
+			int stat_err = Mono.Unix.Native.Syscall.stat (path, out stat);
+
+			string  temp_path = path;
+			using (System.IO.FileStream stream = System.IO.File.OpenRead (path)) {
+				using (System.IO.Stream output = FSpot.Utils.Unix.MakeSafeTemp (ref temp_path)) {
+					SaveMetaData (stream, output);
+				}
+			}
+
+			File.SetAttributes (temp_path, File.GetAttributes (path));
+
+			if (FSpot.Utils.Unix.Rename (temp_path, path) < 0) {
+				System.IO.File.Delete (temp_path);
+				throw new System.Exception (System.String.Format ("Unable to rename {0} to {1}",
+										  temp_path, path));
+			}
+
+			//Set file permissions and gid...
+			if (stat_err == 0) 
+				try {
+					Mono.Unix.Native.Syscall.chmod (path, stat.st_mode |
+									      Mono.Unix.Native.FilePermissions.S_IRUSR | 
+									      Mono.Unix.Native.FilePermissions.S_IWUSR);
+					Mono.Unix.Native.Syscall.chown(path, Mono.Unix.Native.Syscall.getuid (), stat.st_gid);
+				} catch (Exception) {}
+		}
+
+		public void SetThumbnail (Gdk.Pixbuf source)
+		{
+			// Then create the thumbnail
+			// The DCF spec says thumbnails should be 160x120 always
+			Gdk.Pixbuf thumbnail = PixbufUtils.ScaleToAspect (source, 160, 120);
+			byte [] thumb_data = PixbufUtils.Save (thumbnail, "jpeg", null, null);
+			
+			// System.Console.WriteLine ("saving thumbnail");				
+
+			// now update the exif data
+			ExifData.Data = thumb_data;
+		}
+#endif
+
+		public void SetDimensions (int width, int height)
+		{
+			Exif.ExifEntry e;
+			Exif.ExifContent thumb_content;
+			
+			// update the thumbnail related image fields if they exist.
+			thumb_content = this.ExifData.GetContents (Exif.Ifd.One);
+			e = thumb_content.Lookup (Exif.Tag.RelatedImageWidth);
+			if (e != null)
+				e.SetData ((uint)width);
+
+			e = thumb_content.Lookup (Exif.Tag.RelatedImageHeight);
+			if (e != null)
+				e.SetData ((uint)height);
+			
+			Exif.ExifContent image_content;
+			image_content = this.ExifData.GetContents (Exif.Ifd.Zero);
+			image_content.GetEntry (Exif.Tag.Orientation).SetData ((ushort)PixbufOrientation.TopLeft);
+			//image_content.GetEntry (Exif.Tag.ImageWidth).SetData ((uint)pixbuf.Width);
+			//image_content.GetEntry (Exif.Tag.ImageHeight).SetData ((uint)pixbuf.Height);
+			image_content.GetEntry (Exif.Tag.PixelXDimension).SetData ((uint)width);
+			image_content.GetEntry (Exif.Tag.PixelYDimension).SetData ((uint)height);
+		}
+
+#if false
+		public override void Save (Gdk.Pixbuf pixbuf, System.IO.Stream stream)
+		{
+
+			// Console.WriteLine ("starting save");
+			// First save the imagedata
+			int quality = Header.GuessQuality ();
+			quality = quality == 0 ? 75 : quality;
+			byte [] image_data = PixbufUtils.Save (pixbuf, "jpeg", new string [] {"quality" }, new string [] { quality.ToString () });
+			System.IO.MemoryStream buffer = new System.IO.MemoryStream ();
+			buffer.Write (image_data, 0, image_data.Length);
+			buffer.Position = 0;
+			
+			// Console.WriteLine ("setting thumbnail");
+			SetThumbnail (pixbuf);
+			SetDimensions (pixbuf.Width, pixbuf.Height);
+			pixbuf.Dispose ();
+			
+			// Console.WriteLine ("saving metatdata");
+			SaveMetaData (buffer, stream);
+			// Console.WriteLine ("done");
+			buffer.Close ();
+		}
+		
+		public Gdk.Pixbuf GetEmbeddedThumbnail ()
+		{
+			if (this.ExifData.Data.Length > 0) {
+				MemoryStream mem = new MemoryStream (this.ExifData.Data);
+				Gdk.Pixbuf thumb = new Gdk.Pixbuf (mem);
+				Gdk.Pixbuf rotated = PixbufUtils.TransformOrientation (thumb, this.Orientation);
+				
+				if (rotated != thumb)
+					thumb.Dispose ();
+				
+				mem.Close ();
+				return rotated;
+			}
+			return null;
+		}
+#endif	
+		public Exif.ExifData ExifData {
+			get {
+				if (exif_data == null) {
+					exif_data = Header.Exif;
+
+					if (exif_data == null || exif_data.Handle.Handle == System.IntPtr.Zero)
+						exif_data = new Exif.ExifData ();
+				}
+				// System.Console.WriteLine ("loading exif data");
+				return exif_data;
+			}
+			set {
+				this.exif_data = value;
+			}
+		}
+
+		public override PixbufOrientation GetOrientation () 
+		{
+			PixbufOrientation orientation = PixbufOrientation.TopLeft;
+#if USE_TIFF
+			try {
+				DirectoryEntry e = ExifHeader.Directory.Lookup (TagId.Orientation);
+				orientation = (PixbufOrientation)e.ValueAsLong [0];
+			} catch {
+				System.Console.WriteLine ("error checking orientation");
+			}
+#else						     
+			Exif.ExifEntry e = this.ExifData.GetContents (Exif.Ifd.Zero).Lookup (Exif.Tag.Orientation);
+			
+			if (e != null) {
+				ushort [] value = e.GetDataUShort ();
+				orientation = (PixbufOrientation) value [0];
+			}
+#endif			
+			if (orientation < PixbufOrientation.TopLeft || orientation > PixbufOrientation.LeftBottom)
+				orientation = PixbufOrientation.TopLeft;
+
+			return orientation;
+		}
+		
+		public void SetOrientation (PixbufOrientation orientation)
+		{
+			Exif.ExifEntry e = this.ExifData.GetContents (Exif.Ifd.Zero).GetEntry (Exif.Tag.Orientation);
+			// System.Console.WriteLine ("Saving orientation as {0}", orientation);
+			e.SetData ((ushort)orientation);
+		       
+			e = this.ExifData.GetContents (Exif.Ifd.One).Lookup (Exif.Tag.Orientation);
+			if (e != null)
+				e.SetData ((ushort)orientation);
+		}
+		
+		public void SetDateTimeOriginal (DateTime time)
+		{
+			Exif.ExifEntry e = ExifData.LookupFirst (Exif.Tag.DateTimeOriginal);
+			if (e != null)
+				e.SetData (time);
+			else {
+				Exif.ExifContent exif_content = this.ExifData.GetContents (Exif.Ifd.Exif);
+				Exif.ExifEntry entry = exif_content.GetEntry (Exif.Tag.DateTimeOriginal);
+				entry.SetData (time);
+			}		
+		}
+
+		public override System.DateTime Date {
+			get {
+				System.DateTime time;
+				try {
+#if USE_TIFF
+					SubdirectoryEntry sub = (SubdirectoryEntry) ExifHeader.Directory.Lookup (TagId.ExifIfdPointer);
+					DirectoryEntry e;
+					
+					if (sub != null) {
+						e = sub.Directory [0].Lookup (TagId.DateTimeOriginal);
+						
+						if (e != null)
+							return DirectoryEntry.DateTimeFromString (e.StringValue).ToUniversalTime ();
+					}
+					
+					e = ExifHeader.Directory.Lookup (TagId.DateTime);
+					
+					if (e != null)
+						return DirectoryEntry.DateTimeFromString (e.StringValue).ToUniversalTime ();
+					
+					return base.Date;
+#else
+					string time_str = "";				
+					time_str = ExifData.LookupFirstValue (Exif.Tag.DateTimeOriginal);
+					
+					if (time_str == null || time_str == "") 
+						time_str = ExifData.LookupFirstValue (Exif.Tag.DateTime);
+					
+					time = Exif.ExifUtil.DateTimeFromString (time_str).ToUniversalTime (); 
+#endif
+				} catch (System.Exception e) {
+					Console.WriteLine (e);
+					time = base.Date;
+				}
+				return time;
+			}
+		}
+
+#if ENABLE_NUNIT
+		[TestFixture]
+		public class Tests {
+			public Tests ()
+			{
+				Gnome.Vfs.Vfs.Initialize ();
+				Gtk.Application.Init ();
+			}
+			
+#if false
+			[Test]
+			public void TestLoad ()
+			{
+				JpegFile jimg = new JpegFile ("/home/lewing/start.swe.jpeg");
+				Assert.AreEqual (PixbufOrientation.TopLeft, jimg.Orientation);
+			}
+#endif
+			[Test]
+			public void TestSave ()
+			{
+				string desc = "this is an example description";
+				string desc2 = "\x00a9 Novell Inc.";
+				PixbufOrientation orient = PixbufOrientation.TopRight;
+				Gdk.Pixbuf test = new Gdk.Pixbuf (null, "f-spot-32.png");
+				string path = ImageFile.TempPath ("joe.jpg");
+				
+				PixbufUtils.SaveJpeg (test, path, 75, new Exif.ExifData ());
+				JpegFile jimg = new JpegFile (path);
+				jimg.SetDescription (desc);
+				jimg.SetOrientation (orient);
+				jimg.SaveMetaData (path);
+				JpegFile mod = new JpegFile (path);
+				Assert.AreEqual (mod.Orientation, orient);
+				Assert.AreEqual (mod.Description, desc);
+				jimg.SetDescription (desc2);
+				jimg.SaveMetaData (path);
+				mod = new JpegFile (path);
+				Assert.AreEqual (mod.Description, desc2);
+				
+				File.Delete (path);
+			}
+		}
+#endif
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/JpegHeader.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/JpegHeader.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,731 @@
+/*
+ * Copyright (c) 2006 Novell Inc. 
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+using System;
+using System.IO;
+using System.Collections;
+using FSpot.Xmp;
+using FSpot;
+
+#if ENABLE_NUNIT
+using NUnit.Framework;
+#endif
+
+public class JpegHeader : SemWeb.StatementSource {
+	public enum JpegMarker {
+		Tem = 0x01,
+		Rst0 = 0xd0,  // RstN used for resync, ignore
+		Rst1 = 0xd1,
+		Rst2 = 0xd2,
+		Rst3 = 0xd3,
+		Rst4 = 0xd4,
+		Rst5 = 0xd5,
+		Rst6 = 0xd6,
+		Rst7 = 0xd7,
+		
+		Sof0 = 0xc0, // SOFn Start of frame 0-1 common
+		Sof1 = 0xc1,
+		Sof2 = 0xc2,
+		Sof3 = 0xc3,
+		
+		Dht = 0xc4,  // Define Huffman Table
+		
+		Sof5 = 0xc5,
+		Sof6 = 0xc6,
+		Sof7 = 0xc7,
+		
+		Jpg = 0xc8, // reserved
+		
+		Sof9 = 0xc9,
+		Sof10 = 0xca,
+		Sof11 = 0xcb,
+		Sof12 = 0xcc,
+		Sof13 = 0xcd,
+		Sof14 = 0xce,
+		Sof15 = 0xcf,
+
+		// These tags all consist of a marker and then a length.
+		
+		// These are the major structure tags.
+		Soi  = 0xd8,  // Start of Image
+		Eoi  = 0xd9,  // End of Image
+		Sos  = 0xda,  // Start of Scan
+		
+		Dnl = 0xdc,
+		Dri = 0xdd,  // Define restart interval
+		Dhp = 0xde,
+		Exp = 0xdf, 
+		
+		Dqt = 0xdb, // Define Quantization Table
+		
+		// These are the app marker tags that contain the application metadata
+		// in its various forms.
+		App0 = 0xe0,  // AppN Markers for application data
+		App1 = 0xe1,
+		App2 = 0xe2,
+		App3 = 0xe3,
+		App4 = 0xe4,
+		App5 = 0xe5,
+		App6 = 0xe6,
+		App7 = 0xe7,
+		App8 = 0xe8,
+		App9 = 0xe9,
+		App10 = 0xea,
+		App11 = 0xeb,
+		App12 = 0xec,
+		App13 = 0xed,
+		App14 = 0xee,
+		App15 = 0xef,
+		
+		Jpg0 = 0xf0,
+		Jpg1 = 0xf1,
+		Jpg2 = 0xf2,
+		Jpg3 = 0xf3,
+		Jpg4 = 0xf4,
+		Jpg5 = 0xf5,
+		Jpg6 = 0xf6,
+		Jpg7 = 0xf7,
+		Jpg8 = 0xf8,
+		Jpg9 = 0xf9,
+		Jpg10 = 0xfa,
+		Jpg11 = 0xfb,
+		Jpg12 = 0xfc,
+		Jpg13 = 0xfd,
+		
+		Com = 0xfe // Comment
+	}	
+
+	private System.Collections.ArrayList marker_list = new System.Collections.ArrayList ();	
+	private byte [] image_data;
+
+                // False seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+
+
+	public class Marker {
+		public JpegMarker Type;
+		public byte [] Data;
+		
+		public Marker (JpegMarker type, byte [] data)
+		{
+			this.Type = type;
+			this.Data = data;
+		}
+
+		public bool IsApp {
+			get {
+				return (this.Type >= JpegMarker.App0 && this.Type <= JpegMarker.App15);
+			}
+		}
+
+		public bool Matches (Signature sig) 
+		{
+			if (Type == sig.Id) {
+				if (sig.Name == null)
+					return true;
+				
+				byte [] name = System.Text.Encoding.ASCII.GetBytes (sig.Name);
+
+				for (int i = 0; i < name.Length; i++)
+					if (Data [i] != name [i])
+						return false;
+				
+				return true;
+			}
+			return false;
+		}
+
+		public string GetName ()
+		{
+			if (!this.IsApp)
+				return null;
+			
+			int j;
+			for (j = 0; j < this.Data.Length; j++) {
+				if (this.Data [j] == 0x00)
+					break;
+					
+			}
+			
+			if (j > 0)
+				return System.Text.Encoding.ASCII.GetString (this.Data, 0, j);
+			else 
+				return null;
+		}
+		
+		private static int Read (Stream stream, byte [] dest, int start, int len)
+		{
+			int pos = 0;
+
+			while (pos < len) {
+				int read = stream.Read (dest, pos + start, len - pos);
+				if (read <= 0)
+					break;
+
+				pos += read;
+			}
+			return pos;
+		}
+
+		public static Marker Load (Stream stream) {
+			byte [] raw = new byte [2];
+			ushort length;
+		       
+			if (stream.Length - stream.Position < 2)
+				return null;
+
+			// FIXME there is a potential loop here.
+			
+			int read = Read (stream, raw, 0, 2);
+			if (read < 2 || raw [0] != 0xff)
+				throw new System.Exception (System.String.Format ("Invalid marker found {0}", raw [0]));
+			
+			JpegMarker id = (JpegMarker) raw [1];
+			switch (id) {
+			case JpegMarker.Soi:
+			case JpegMarker.Eoi:
+			case JpegMarker.Rst0:
+			case JpegMarker.Rst1:
+			case JpegMarker.Rst2:
+			case JpegMarker.Rst3:
+			case JpegMarker.Rst4:
+			case JpegMarker.Rst5:
+			case JpegMarker.Rst6:
+			case JpegMarker.Rst7:
+			case JpegMarker.Tem: 
+			case (JpegMarker) 0:
+				return new Marker (id, null);
+			default:
+				Read (stream, raw, 0, 2);
+				length = FSpot.BitConverter.ToUInt16 (raw, 0, false);
+				
+				byte [] data = new byte [length - 2];
+				Read (stream, data, 0, data.Length);
+				return new Marker (id, data);
+			}
+			
+		}
+
+		public void Save (System.IO.Stream stream) {
+			/* 
+			 * It is possible we should just base this choice off the existance
+			 * of this.Data, but I'm not sure so I'll do it this way for now
+			 */
+			
+			switch (this.Type) {
+			case JpegMarker.Soi:
+			case JpegMarker.Eoi:
+				stream.WriteByte (0xff);				
+				stream.WriteByte ((byte)this.Type);
+				break;
+			default:
+				stream.WriteByte (0xff);				
+				stream.WriteByte ((byte)this.Type);
+				ushort length = (ushort)(this.Data.Length + 2);
+				
+				byte [] len = FSpot.BitConverter.GetBytes (length, false);
+				stream.Write (len, 0, len.Length);
+
+				//workaround for mono bug: http://bugzilla.ximian.com/show_bug.cgi?id=82836
+				if (this.Data.Length > 0)
+					stream.Write (this.Data, 0, this.Data.Length);
+				break;
+			}
+		}
+	}
+
+	public static Signature JfifSignature = new Signature (JpegMarker.App0, "JFIF\0");
+	public static Signature JfxxSignature = new Signature (JpegMarker.App0, "JFXX\0");
+	public static Signature XmpSignature = new Signature (JpegMarker.App1, "http://ns.adobe.com/xap/1.0/\0";);
+	public static Signature ExifSignature = new Signature (JpegMarker.App1, "Exif\0\0");
+	public static Signature IccProfileSignature = new Signature (JpegMarker.App2, "ICC_PROFILE\0");
+	public static Signature PhotoshopSignature = new Signature (JpegMarker.App13, "Photoshop 3.0\0");
+	public static Signature QuantizationSignature = new Signature (JpegMarker.Dqt, null);
+
+	public class Signature {
+		public JpegMarker Id;
+		public string Name;
+
+		public Signature (JpegMarker marker, string name)
+		{
+			Id = marker;
+			Name = name;
+		}
+
+		public int WriteName (Stream stream)
+		{
+			byte [] sig = System.Text.Encoding.ASCII.GetBytes (Name);
+			stream.Write (sig, 0, sig.Length);
+			return sig.Length;
+		}
+	}	
+
+	public Marker FindMarker (Signature sig)
+	{
+		foreach (Marker m in Markers) {
+			if (m.Matches (sig))
+				return m;
+		}
+
+		return null;
+	}
+
+
+	public Marker FindMarker (JpegMarker id, string name)
+	{
+		return FindMarker (new Signature (id, name));
+	}
+
+#if false
+	public Cms.Profile GetProfile ()
+	{
+		Marker m = FindMarker (IccProfileSignature);
+		string name = IccProfileSignature.Name;
+		try {
+			if (m != null)
+				return new Cms.Profile (m.Data, name.Length, m.Data.Length - name.Length); 
+		} catch (System.Exception e) {
+			System.Console.WriteLine (e);
+		}
+		
+		FSpot.Tiff.Header exif = GetExifHeader ();
+		if (exif != null)
+			return exif.Directory.GetProfile ();
+		
+		return null;
+	}
+#endif
+	public FSpot.Tiff.Header GetExifHeader ()
+	{
+		string name = ExifSignature.Name;
+		Marker marker = FindMarker (ExifSignature);
+
+		if (marker == null)
+			return null;
+		
+		using (System.IO.Stream exifstream = new System.IO.MemoryStream (marker.Data, name.Length, marker.Data.Length - name.Length, false)) {
+			FSpot.Tiff.Header exif = new FSpot.Tiff.Header (exifstream);
+			return exif;
+		}
+	}
+
+	public XmpFile GetXmp ()
+	{
+		string name = XmpSignature.Name;
+		Marker marker = FindMarker (XmpSignature);
+		if (marker != null) {
+			int len = name.Length;
+			//System.Console.WriteLine (System.Text.Encoding.ASCII.GetString (marker.Data, len, 
+			//								marker.Data.Length - len));
+			using (System.IO.Stream xmpstream = new System.IO.MemoryStream (marker.Data, len, 
+											marker.Data.Length - len, false)) {
+			
+				XmpFile xmp = new XmpFile (xmpstream);					
+				return xmp;
+			}
+		}
+		return null;
+	}
+
+	public void Select (SemWeb.StatementSink sink)
+	{
+		FSpot.Tiff.Header exif = GetExifHeader ();
+		if (exif != null)
+			exif.Select (sink);
+		
+		XmpFile xmp = GetXmp ();
+		if (xmp != null)
+			xmp.Select (sink);
+		
+		string name = PhotoshopSignature.Name;
+		JpegHeader.Marker marker = FindMarker (PhotoshopSignature);
+		if (marker != null) {
+			int len = name.Length;
+			using (System.IO.Stream bimstream = new System.IO.MemoryStream (marker.Data, len, marker.Data.Length - len, false)) {
+				FSpot.Bim.BimFile bim = new FSpot.Bim.BimFile (bimstream);
+				bim.Select (sink);
+			}
+		}
+	}
+
+	public Exif.ExifData Exif {
+		get {
+			Marker m = FindMarker (ExifSignature);
+
+			if (m  == null)
+				return null;
+
+			return new Exif.ExifData (m.Data);
+		}
+	}
+
+	public void Replace (Signature sig, Marker data)
+	{
+		bool added = false;
+		for (int i = 1; i < Markers.Count; i++) {
+			Marker m = (Marker) Markers [i];
+			
+			if (m.Matches (sig)) {
+				
+				if (!added) {
+					Markers [i] = data;
+					added = true;
+				} else
+					Markers.RemoveAt (i--);
+				
+			} else if (!m.IsApp || m.Type > sig.Id) {
+				if (!added) {
+					Markers.Insert (i, data); 
+					added = true;
+				}
+			}
+		}
+
+		if (!added)
+			throw new System.Exception (String.Format ("unable to replace {0} marker", sig.Name));
+	}
+
+	public void SetExif (Exif.ExifData value)
+	{
+		// Console.WriteLine ("before save");
+		byte [] raw_data = value.Save ();
+		// Console.WriteLine ("saved");
+		Marker exif = new Marker (ExifSignature.Id, raw_data);
+		// Console.WriteLine ("new");
+		Replace (ExifSignature, exif);
+		// Console.WriteLine ("replaced");
+	}	
+	
+	public void SetXmp (XmpFile xmp)
+	{
+		using (MemoryStream stream = new MemoryStream ()) {
+			
+			XmpSignature.WriteName (stream);
+			xmp.Save (stream);
+			
+			Marker xmp_marker = new Marker (XmpSignature.Id, stream.ToArray ());
+			Replace (XmpSignature, xmp_marker);
+		}
+	}
+	
+	public System.Collections.ArrayList Markers {
+		get {
+			return marker_list;
+		}
+	}
+
+	public void Save (System.IO.Stream stream)
+	{
+		foreach (Marker marker in marker_list) {
+			// System.Console.WriteLine ("saving marker {0} {1}", marker.Type, 
+			//			  (marker.Data != null) ? marker.Data.Length .ToString (): "(null)");
+			marker.Save (stream);
+			if (marker.Type == JpegMarker.Sos)
+				stream.Write (ImageData, 0, ImageData.Length);
+		}
+	}
+
+	public JpegHeader (System.IO.Stream stream)
+	{
+		Load (stream, false);
+	}
+
+	public JpegHeader (System.IO.Stream stream, bool metadata_only)
+	{
+		try {
+			Load (stream, metadata_only);
+		} catch (System.Exception e) {
+			Console.WriteLine ("Exeption while reading jpeg headers");
+			Console.WriteLine(e);
+		}
+	}
+
+	private void Load (System.IO.Stream stream, bool metadata_only) 
+	{
+		marker_list.Clear ();
+		image_data = null;
+		bool at_image = false;
+
+		Marker marker = Marker.Load (stream);
+		if (marker.Type != JpegMarker.Soi)
+			throw new System.Exception ("This doesn't appear to be a jpeg stream");
+		
+		this.Markers.Add (marker);
+		while (!at_image) {
+			marker = Marker.Load (stream);
+
+			if (marker == null)
+				break;
+
+			// System.Console.WriteLine ("loaded marker {0} length {1}", marker.Type, marker.Data.Length);
+
+			this.Markers.Add (marker);
+			
+			if (marker.Type == JpegMarker.Sos) {
+				at_image = true;
+
+				if (metadata_only) {
+					// System.Console.WriteLine ("read = {0}", stream.Position);
+					return;
+				}
+			}
+		}
+
+		long image_data_length = stream.Length - stream.Position;
+		this.image_data = new byte [image_data_length];
+
+		if (stream.Read (image_data, 0, (int)image_data_length) != image_data_length)
+			throw new System.Exception ("truncated image data or something");
+	}
+
+	static int [] StandardLuminanceQuantization = new int [] {
+	        16,  11,  12,  14,  12,  10,  16,  14,
+		13,  14,  18,  17,  16,  19,  24,  40,
+		26,  24,  22,  22,  24,  49,  35,  37,
+		29,  40,  58,  51,  61,  60,  57,  51,
+		56,  55,  64,  72,  92,  78,  64,  68,
+		87,  69,  55,  56,  80, 109,  81,  87,
+		95,  98, 103, 104, 103,  62,  77, 113,
+		121, 112, 100, 120,  92, 101, 103,  99
+	};
+
+	static int [] StandardChrominanceQuantization = new int [] {
+		17,  18,  18,  24,  21,  24,  47,  26,
+		26,  47,  99,  66,  56,  66,  99,  99,
+		99,  99,  99,  99,  99,  99,  99,  99,
+		99,  99,  99,  99,  99,  99,  99,  99,
+		99,  99,  99,  99,  99,  99,  99,  99,
+		99,  99,  99,  99,  99,  99,  99,  99,
+		99,  99,  99,  99,  99,  99,  99,  99,
+		99,  99,  99,  99,  99,  99,  99,  99
+	};
+	
+	/* 
+	 * GuessQuality is taken from the jpegdump utility
+	 * Copyright (c) 1992 Handmade Software, Inc.
+	 * by Allan N. Hessenflow licenced as GPL with the authors
+	 * permission.  Many Thanks.
+	 */
+	public int GuessQuality ()
+	{
+		Marker dqt = FindMarker (QuantizationSignature);
+		int quality = 0;
+		int position = 0;
+
+		while (position < dqt.Data.Length) {
+			int tableindex;
+			int [] table = null;
+			double cumsf = 0.0;
+			double cumsf2 = 0.0;
+			bool allones = true;
+			int row, col;
+			
+			tableindex = dqt.Data [position ++];
+
+			switch (tableindex & 0x0f) {
+			case 0:
+				table = StandardLuminanceQuantization;
+				break;
+			case 1:
+				table = StandardChrominanceQuantization;
+				break;
+			default:
+				table = null;
+				break;
+			}
+
+			for (row=0; row<8; row++) {
+				for (col=0; col<8; col++) {
+					uint val;
+					
+					if ((tableindex >> 4) > 0) {
+					        val = FSpot.BitConverter.ToUInt16 (dqt.Data, position, false);
+						position += 2;
+					} else
+						val = (uint) dqt.Data [position ++];
+
+					if (table != null) {
+						double x;
+
+						/* scaling factor in percent */
+						x = 100.0 * (double)val / (double)table [row*8+col];
+						cumsf += x;
+						cumsf2 += x * x;
+
+						/* separate check for all-ones table (Q 100) */
+						if (val != 1) 
+							allones = false;
+					}
+				}
+			}
+
+			if (table != null) {
+				double local_quality;
+				
+				cumsf /= 64.0;	/* mean scale factor */
+				cumsf2 /= 64.0;
+
+				//double variance;
+				//variance = cumsf2 - (cumsf * cumsf);
+
+				if (allones) /* special case for all-ones table */
+					local_quality = 100.0;
+				else if (cumsf <= 100.0)
+					local_quality = (200.0 - cumsf) / 2.0;
+				else
+					local_quality = 5000.0 / cumsf;
+				
+				quality = Math.Max (quality, (int)local_quality);
+			}
+		}
+		return quality;
+	}
+	
+	public byte [] ImageData {
+		get {
+			return image_data;
+		}
+	}
+
+#if ENABLE_NUNIT
+	[TestFixture]
+	public class Tests {
+		int quality =  75;
+
+		public string CreateFile ()
+		{
+			Gdk.Pixbuf test = new Gdk.Pixbuf (null, "f-spot-32.png");
+			string path = FSpot.ImageFile.TempPath ("joe.jpg");
+			string desc = "\x00a9 Novell Inc.";
+			PixbufOrientation orient = PixbufOrientation.TopRight;
+
+			PixbufUtils.SaveJpeg (test, path, quality, new Exif.ExifData ());
+			FSpot.JpegFile jimg = new FSpot.JpegFile (path);
+			jimg.SetDescription (desc);
+			jimg.SetOrientation (orient);
+			jimg.SaveMetaData (path);
+			
+			return path;
+		}
+
+		[Test]
+		public void Load ()
+		{
+			string path = CreateFile ();
+
+			using (Stream stream = File.OpenRead (path)) {
+				JpegHeader jhead = new JpegHeader (stream);
+
+				Assert.AreEqual (((Marker)jhead.Markers [0]).Type, JpegMarker.Soi);
+				Assert.AreEqual (((Marker)jhead.Markers [1]).GetName (), "JFIF");
+				Assert.AreEqual (((Marker)jhead.Markers [1]).Type, JpegMarker.App0);
+				Assert.AreEqual (((Marker)jhead.Markers [2]).GetName (), "Exif");
+				Assert.AreEqual (((Marker)jhead.Markers [2]).Type, JpegMarker.App1);
+
+				// NOTE the currently we don't store the Eoi as the last marker
+				Assert.AreEqual (((Marker)jhead.Markers [jhead.Markers.Count -1]).Type, JpegMarker.Sos);
+
+				// NOTE this is kind of sill but it might help
+				Assert.IsTrue (Math.Abs (jhead.GuessQuality () - quality) <= 1);
+
+				Assert.IsNotNull (jhead.GetExifHeader ());
+			}
+
+			File.Delete (path);
+		}
+
+		[Test]
+		public void Save ()
+		{
+			string in_path = CreateFile ();
+			string out_path = ImageFile.TempPath ("output.jpg");
+			JpegHeader source;
+			JpegHeader dest;
+
+			using (Stream orig = File.OpenRead (in_path)) {
+				source = new JpegHeader (orig);
+				
+				using (Stream output = File.OpenWrite (out_path)) {
+					source.Save (output);
+				}
+
+				using (Stream result = File.OpenRead (out_path)) {
+					dest = new JpegHeader (result);
+					
+					Assert.AreEqual (source.Markers.Count, dest.Markers.Count);
+					Assert.AreEqual (source.GuessQuality (), dest.GuessQuality ());
+					Assert.AreEqual (orig.Length, result.Length);
+					for (int i = 0; i < source.Markers.Count; i++) {
+						Marker d = (Marker) dest.Markers [i];
+						Marker s = (Marker) source.Markers [i];
+
+						Assert.AreEqual (d.Type, s.Type);
+						Assert.AreEqual (d.GetName (), s.GetName ());
+
+						if (d.Data != null) {
+							Assert.AreEqual (d.Data.Length, s.Data.Length);
+						
+							for (int j = 0; j < d.Data.Length; j++) {
+								Assert.AreEqual (d.Data [j], s.Data [j]);
+							}
+						} else {
+							Assert.AreEqual (d.Data, s.Data);
+						}
+					}
+				}
+			}
+
+			File.Delete (in_path);
+			File.Delete (out_path);
+		}
+	}
+#endif
+
+#if false
+	public static int Main (string [] args)
+	{
+		JpegHeader data = new JpegHeader (args [0]);
+		byte [] value = data.GetRawXmp ();
+
+		if (value != null) {
+			string xml = System.Text.Encoding.UTF8.GetString (value, 29, value.Length - 29);
+			System.Console.WriteLine (xml);
+		}
+		
+		value = data.GetRaw ("ICC_PROFILE");
+		if (value != null) {
+			System.IO.FileStream stream = new System.IO.FileStream ("profile.icc", System.IO.FileMode.Create);
+			stream.Write (value, 12, value.Length - 12);
+			stream.Close ();
+		}
+
+		value = data.GetRawExif ();
+		
+		
+		//System.IO.Stream ostream = System.IO.File.Open ("/home/lewing/test.jpg", System.IO.FileMode.OpenOrCreate);
+		//data.Save (ostream);
+		//ostream.Position = 0;
+		//data = new JpegHeader (ostream);
+
+		return 0;
+	}
+#endif
+}

Added: trunk/beagle/Util/F-Spot/Imaging/JpegUtils.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/JpegUtils.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,67 @@
+using System.Runtime.InteropServices;
+using System;
+using Gdk;
+
+public class JpegUtils {
+	[DllImport ("libfspot")]
+	static extern IntPtr f_load_scaled_jpeg (string path,
+						 int target_width,
+						 int target_height,
+						 out int original_width_return,
+						 out int original_height_return);
+
+	/* FIXME */
+	[DllImport("libgobject-2.0-0.dll")]
+	static extern void g_object_unref (IntPtr raw);
+
+	public static Pixbuf LoadScaled (string path, int target_width, int target_height,
+					 out int original_width, out int original_height)
+	{
+		Pixbuf pixbuf = new Pixbuf (f_load_scaled_jpeg (path, target_width, target_height,
+								out original_width, out original_height));
+		g_object_unref (pixbuf.Handle);
+		return pixbuf;
+	}
+
+	public static Pixbuf LoadScaled (string path, int target_width, int target_height)
+	{
+		int unused;
+		return LoadScaled (path, target_width, target_height, out unused, out unused);
+	}
+
+	[DllImport ("libfspot")]
+	static extern void f_save_jpeg_exif (string path, HandleRef data);
+
+	public static void SaveExif (string path, Exif.ExifData data)
+	{
+		f_save_jpeg_exif (path, data.Handle);
+	}		
+
+	[DllImport ("libfspot")]
+	static extern void f_get_jpeg_size (string path, out int width_return, out int height_return);
+
+	public static void GetSize (string path, out int width_return, out int height_return)
+	{
+		f_get_jpeg_size (path, out width_return, out height_return);
+	}
+
+	public enum TransformType {
+		Rotate90,
+		Rotate180,
+		Rotate270,
+		FlipH,
+		FlipV
+	};
+
+	[DllImport ("libfspot")]
+	static extern bool f_transform_jpeg (string source_path, string destination_path, TransformType transform,
+					     out string error_message_return);
+
+	public static void Transform (string source_path, string destination_path, TransformType transform)
+	{
+		string error_message;
+
+		if (! f_transform_jpeg (source_path, destination_path, transform, out error_message))
+			throw new Exception (error_message);
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/MrwFile.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/MrwFile.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,253 @@
+using FSpot.Tiff;
+
+namespace FSpot.Mrw {
+	// Minolta raw format
+	// see http://www.dalibor.cz/minolta/raw_file_format.htm for details
+	// note that the blocks can be in any order.
+
+	public class Block {
+		protected byte [] name;
+		protected uint  Length;
+		protected long Start;
+		protected System.IO.Stream stream;
+		byte [] data;
+
+		public Block (System.IO.Stream stream)
+		{
+			this.stream = stream;
+			Start = stream.Position;
+			name = new byte [4];
+			byte [] tmp = new byte [8];
+			stream.Read (tmp, 0, tmp.Length);
+			System.Array.Copy (tmp, name, name.Length);
+			System.Console.WriteLine (this.Name);
+			Length = BitConverter.ToUInt32 (tmp, name.Length, false);
+			stream.Position = stream.Position + Length;
+		}
+
+		public string Name {
+			get {
+				return System.Text.Encoding.ASCII.GetString (this.name, 1, 3);
+			}
+		}
+
+		public byte [] Data {
+			get {
+				if (data == null)
+					data = ReadData ();
+				
+				return data;
+			}
+		}
+
+		public static Block Create (System.IO.Stream stream)
+		{
+			byte [] tmp = new byte [4];
+			stream.Read (tmp, 0, tmp.Length);
+			stream.Position -= 4;
+			string name = System.Text.Encoding.ASCII.GetString (tmp, 1, 3);
+			switch (name) {
+			case "TTW":
+				return new TtwBlock (stream);
+			case "PRD":
+				return new PrdBlock (stream);
+			default:
+				return new Block (stream);
+			}
+		}
+
+		protected byte [] ReadData ()
+		{
+			stream.Position = Start + 8;
+			byte [] data = new byte [this.Length];
+			stream.Read (data, 0, data.Length);
+
+			return data;
+		}
+	}
+
+	public class PrdBlock : Block {
+		public PrdBlock (System.IO.Stream stream) : base (stream)
+		{
+
+		}
+
+		public ulong Version {
+			get {
+				return BitConverter.ToUInt64 (this.Data, 0, false);
+			}
+		}
+		
+		public ushort CCDSizeY {
+			get {
+				return BitConverter.ToUInt16 (this.Data, 8, false);
+			}
+		}
+
+		public ushort CCDSizeX {
+			get {
+				return BitConverter.ToUInt16 (this.Data, 10, false);
+			}
+		}
+
+		public ushort ImageSizeY {
+			get {
+				return BitConverter.ToUInt16 (this.Data, 12, false);
+			}
+		}
+
+		public ushort ImageSizeX {
+			get {
+				return BitConverter.ToUInt16 (this.Data, 14, false);
+			}
+		}
+		
+		public byte Depth {
+			get {
+				return this.Data [16];
+			}
+		}
+
+		public byte SampleDepth {
+			get {
+				return this.Data [17];
+			}
+		}
+	}
+
+	internal class TtwBlock : Block {
+		FSpot.Tiff.Header header;
+
+		public TtwBlock (System.IO.Stream stream) : base (stream)
+		{
+			if (this.Name != "TTW")
+				throw new System.Exception (System.String.Format ("invalid block name {0}", this.Name));
+		}
+		
+		public FSpot.Tiff.Header TiffHeader {
+			get {
+				if (header == null) {
+					try {
+						System.IO.MemoryStream mem = new System.IO.MemoryStream (this.Data);
+						System.Console.WriteLine ("before header");
+						header = new Header (mem);
+					} catch (System.Exception e) {
+						System.Console.WriteLine (e.ToString ());
+					}
+				}
+				
+				return header;
+			}
+		}
+	}
+
+	internal class MrmBlock : Block {
+		Block [] blocks;
+
+		public MrmBlock (System.IO.Stream stream) : base (stream) {}
+
+		protected void Load ()
+		{
+			stream.Position = Start + 8;
+			System.Collections.ArrayList list = new System.Collections.ArrayList ();
+			
+			while (stream.Position < Start + 8 + Length) {
+				list.Add (Block.Create (stream));
+			}
+			blocks = (Block []) list.ToArray (typeof (Block));
+		}
+
+		public Block [] Blocks {
+			get {
+				if (blocks == null) {
+					Load ();
+				}
+
+				return blocks;
+			}
+		}
+		
+	}
+	
+	public class MrwFile : ImageFile, SemWeb.StatementSource {
+		MrmBlock mrm;
+		FSpot.Tiff.Header header;
+
+		public MrwFile (System.Uri uri) : base (uri)
+		{
+		}
+
+                // false seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+
+		public MrwFile (string path) : base (path)
+		{
+		}
+
+		public FSpot.Tiff.Header Header {
+			get {
+				if (mrm == null)
+					LoadBlocks ();
+				
+				return header;
+			}
+		}
+
+		public override System.DateTime Date
+		{
+			get {
+				DirectoryEntry e = this.Header.Directory.Lookup (TagId.DateTime);
+				
+				if (e != null)
+					return DirectoryEntry.DateTimeFromString (e.StringValue).ToUniversalTime ();
+				else
+					return base.Date;
+			}
+		}
+		
+		public void Select (SemWeb.StatementSink sink)
+		{
+			this.Header.Select (sink);
+		}
+
+		public override System.IO.Stream PixbufStream ()
+		{
+			return DCRawFile.RawPixbufStream (uri);
+		}
+		
+#if false
+		public override Gdk.Pixbuf Load ()
+		{
+			using (System.IO.Stream stream = Open ()) {
+				return new Gdk.Pixbuf (PixbufStream ());
+			}
+		}
+
+		public override Gdk.Pixbuf Load (int width, int height)
+		{
+			return PixbufUtils.ScaleToMaxSize (this.Load (), width, height);
+		}
+#endif
+		protected void LoadBlocks () 
+		{
+			using (System.IO.Stream file = Open ()) {
+				mrm = new MrmBlock (file);
+				try {
+					foreach (Block b in mrm.Blocks) {
+						if (b is TtwBlock) {
+							TtwBlock ttw = (TtwBlock) b;
+							header = ttw.TiffHeader;
+							//Header.Dump ("TTW:");
+							break;
+						}
+					}
+				} catch (System.Exception e) {
+					System.Console.WriteLine (e.ToString ());
+				}
+			}
+		}
+	}
+
+}

Added: trunk/beagle/Util/F-Spot/Imaging/OrderedWriter.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/OrderedWriter.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,35 @@
+using System.IO;
+
+namespace FSpot {
+	public class OrderedWriter {
+		Stream stream;
+		bool is_little;
+
+		public Stream Stream {
+			get { return stream; }
+		}
+
+		public OrderedWriter (Stream stream, bool is_little)
+		{
+			this.stream = stream;
+			this.is_little = is_little;
+		}
+
+		public void Write (byte b)
+		{
+			stream.WriteByte (b);
+		}
+
+		public void Write (uint val)
+		{
+			byte [] value = FSpot.BitConverter.GetBytes (val, is_little);
+			stream.Write (value, 0, value.Length);
+		}
+
+		public void Write (ushort val)
+		{
+			byte [] value = FSpot.BitConverter.GetBytes (val, is_little);
+			stream.Write (value, 0, value.Length);
+		}
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/PngFile.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/PngFile.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,1551 @@
+using ICSharpCode.SharpZipLib.Zip.Compression;
+using SemWeb;
+using System.IO;
+using FSpot.Xmp;
+using System.Collections;
+using System.Reflection;
+
+#if ENABLE_NUNIT
+using NUnit.Framework;
+#endif
+
+namespace FSpot.Png {
+	public class PngFile : ImageFile, SemWeb.StatementSource {
+		PngHeader header;
+
+                // false seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+
+		private PngHeader Header {
+			get {
+				if (header == null) {
+					using (System.IO.Stream input = Open ()) {
+					        header = new PngHeader (input);
+					}
+				}
+				
+				return header;
+			}
+		}
+
+		public System.Collections.ArrayList Chunks {
+			get { return Header.Chunks; }
+		}
+
+		public PngFile (System.Uri uri) : base (uri)
+		{
+		}
+
+		public PngFile (string path) : base (path)
+		{
+		}
+
+
+		/**
+		   Title 	Short (one line) title or caption for image 
+		   Author 	Name of image's creator
+		   Description 	Description of image (possibly long)
+		   Copyright 	Copyright notice
+		   Creation Time 	Time of original image creation
+		   Software 	Software used to create the image
+		   Disclaimer 	Legal disclaimer
+		   Warning 	Warning of nature of content
+		   Source 	Device used to create the image
+		   Comment 	Miscellaneous comment
+		   
+		   xmp is XML:com.adobe.xmp
+
+		   Other keywords may be defined for other purposes. Keywords of general interest can be registered with th
+		*/
+		public void Select (SemWeb.StatementSink sink)
+		{
+			foreach (Chunk c in Chunks) {
+				if (c is IhdrChunk) {
+					IhdrChunk ih = c as IhdrChunk;
+					MetadataStore.AddLiteral (sink, "tiff:ImageWidth", ih.Width.ToString ());
+					MetadataStore.AddLiteral (sink, "tiff:ImageLength", ih.Height.ToString ());
+				} else if(c is TimeChunk) {
+					TimeChunk tc = c as TimeChunk;
+
+					MetadataStore.AddLiteral (sink, "xmp:ModifyDate", tc.Time.ToString ("yyyy-MM-ddThh:mm:ss"));
+				} else if (c is TextChunk) {
+					TextChunk text = c as TextChunk;
+
+					switch (text.Keyword) {
+					case "XMP":
+					case "XML:com.adobe.xmp":
+						using (System.IO.Stream xmpstream = new System.IO.MemoryStream (text.TextData)) {
+							FSpot.Xmp.XmpFile xmp = new FSpot.Xmp.XmpFile (xmpstream);
+							xmp.Select (sink);
+						}
+						break;
+					case "Comment":
+						MetadataStore.AddLiteral (sink, "exif:UserComment", text.Text);
+						break;
+					case "Software":
+						MetadataStore.AddLiteral (sink, "xmp:CreatorTool", text.Text);
+						break;
+					case "Title":
+						MetadataStore.AddLiteral (sink, "dc:title", "rdf:Alt", new SemWeb.Literal (text.Text, "x-default", null));
+						break;
+					case "Author":
+						MetadataStore.AddLiteral (sink, "dc:creator", "rdf:Seq", new SemWeb.Literal (text.Text));
+						break;
+					case "Copyright":
+						MetadataStore.AddLiteral (sink, "dc:rights", "rdf:Alt", new SemWeb.Literal (text.Text, "x-default", null));
+						break;
+					case "Description":
+						MetadataStore.AddLiteral (sink, "dc:description", "rdf:Alt", new SemWeb.Literal (text.Text, "x-default", null));
+						break;
+					case "Creation Time":
+						try {
+							System.DateTime time = System.DateTime.Parse (text.Text);
+							MetadataStore.AddLiteral (sink, "xmp:CreateDate", time.ToString ("yyyy-MM-ddThh:mm:ss"));
+						} catch (System.Exception e) {
+							System.Console.WriteLine (e.ToString ());
+						}
+						break;
+					}
+				} else if (c is ColorChunk) {
+					ColorChunk color = (ColorChunk)c;
+					string [] whitepoint = new string [2];
+					whitepoint [0] = color.WhiteX.ToString ();
+					whitepoint [1] = color.WhiteY.ToString ();
+					MetadataStore.Add (sink, "tiff:WhitePoint", "rdf:Seq", whitepoint);
+					int i = 0;
+					string [] rgb = new string [6];
+					rgb [i++] = color.RedX.ToString ();
+					rgb [i++] = color.RedY.ToString ();
+					rgb [i++] = color.GreenX.ToString ();
+					rgb [i++] = color.GreenY.ToString ();
+					rgb [i++] = color.BlueX.ToString ();
+					rgb [i++] = color.BlueY.ToString ();
+					MetadataStore.Add (sink, "tiff:PrimaryChromaticities", "rdf:Seq", rgb);
+				} else if (c.Name == "sRGB") {
+					MetadataStore.AddLiteral (sink, "exif:ColorSpace", "1");
+				} else if (c is PhysChunk) {
+					PhysChunk phys = (PhysChunk)c;
+					uint denominator = (uint) (phys.InMeters ? 100 : 1);
+					
+					MetadataStore.AddLiteral (sink, "tiff:ResolutionUnit", phys.InMeters ? "3" : "1");
+					MetadataStore.AddLiteral (sink, "tiff:XResolution", new FSpot.Tiff.Rational (phys.PixelsPerUnitX, denominator).ToString ());
+					MetadataStore.AddLiteral (sink, "tiff:YResolution", new FSpot.Tiff.Rational (phys.PixelsPerUnitY, denominator).ToString ());
+				}
+			}
+		}
+
+		public class ZtxtChunk : TextChunk {
+			//public static string Name = "zTXt";
+
+			protected bool compressed = true;
+			public bool Compressed {
+				get {
+					return compressed;
+				}
+			}
+			
+			byte compression;
+			public byte Compression {
+			        get {
+					return compression;
+				}
+				set {
+					if (compression != 0)
+						throw new System.Exception ("Unknown compression method");
+				}
+			}
+
+			public ZtxtChunk (string keyword, string text) : base ()
+			{
+				Name = "zTXt";
+				Compression = 0;
+				this.keyword = keyword;
+			}
+
+			public ZtxtChunk (string name, byte [] data) : base (name, data)
+			{
+			}			
+
+			protected ZtxtChunk ()
+			{
+			}
+
+			public override void SetText (string text)
+			{
+				/* FIXME this is broken */
+				text_data = encoding.GetBytes (text);
+				data = Chunk.Deflate (text_data, 0, text_data.Length);
+			}
+			
+			public override void Load (byte [] data) 
+			{
+				int i = 0;
+				keyword = GetString (ref i);
+				i++;
+				Compression = data [i++];
+
+				text_data = Chunk.Inflate (data, i, data.Length - i);
+			}
+		}
+
+		public class PhysChunk : Chunk {
+			public PhysChunk (string name, byte [] data) : base (name, data) {}
+			
+			public uint PixelsPerUnitX {
+				get {
+					return BitConverter.ToUInt32 (data, 0, false);
+				}
+			}
+
+			public uint PixelsPerUnitY {
+				get {
+					return BitConverter.ToUInt32 (data, 4, false);
+				}
+			}
+			
+			public bool InMeters {
+				get {
+					return data [8] == 0;
+				}
+			}
+		}
+		
+		public class TextChunk : Chunk {
+			//public static string Name = "tEXt";
+
+			protected string keyword;
+			protected byte [] text_data;
+			protected System.Text.Encoding encoding = Latin1;
+
+			public static System.Text.Encoding Latin1 = System.Text.Encoding.GetEncoding (28591);
+
+			public TextChunk (string name, byte [] data) : base (name, data) 
+			{
+			}
+
+			protected TextChunk ()
+			{
+			}
+
+
+			public TextChunk (string keyword, string text)
+			{
+				this.Name = "tEXt";
+				this.keyword = keyword;
+				SetText (text);
+			}
+
+			public override void Load (byte [] data)
+			{
+				int i = 0;
+
+				keyword = GetString (ref i);
+				i++;
+				int len = data.Length - i;
+				text_data = new byte [len];
+				System.Array.Copy (data, i, text_data, 0, len);
+			}
+
+			public string Keyword {
+				get {
+					return keyword;
+				}
+			}
+
+			public byte [] TextData {
+				get {
+					return text_data;
+				}
+			}
+			
+			public virtual void SetText (string text)
+			{
+				text_data = encoding.GetBytes (text);
+
+				byte [] keyword_data = Latin1.GetBytes (keyword);
+				data = new byte [keyword_data.Length + 1 + text_data.Length];
+				System.Array.Copy (keyword_data, 0, data, 0, keyword_data.Length);
+				data [keyword_data.Length] = 0;
+				System.Array.Copy (text_data, 0, data, keyword_data.Length + 1, text_data.Length);
+			}
+
+			public string Text {
+				get {
+					return encoding.GetString (text_data, 0, text_data.Length);
+				}
+			}
+		}
+		
+		public class IccpChunk : Chunk {
+			string keyword;
+			byte [] profile;
+
+			public IccpChunk (string name, byte [] data) : base (name, data) {}
+			
+			public override void Load (byte [] data)
+			{
+				int i = 0;
+				keyword = GetString (ref i);
+				i++;
+				int compression = data [i++];
+				if (compression != 0)
+					throw new System.Exception ("Unknown Compression type");
+
+				profile = Chunk.Inflate (data, i, data.Length - i);
+			}
+
+			public string Keyword {
+				get {
+					return keyword;
+				}
+			}
+			
+			public byte [] Profile {
+				get {
+					return profile;
+				}
+			}
+		}
+
+		public class ItxtChunk : ZtxtChunk{
+			//public static string Name = "zTXt";
+
+			string Language;
+			string LocalizedKeyword;
+
+			public override void Load (byte [] data)
+			{
+				int i = 0;
+				keyword = GetString (ref i);
+				i++;
+				compressed = (data [i++] != 0);
+				Compression = data [i++];
+				Language = GetString (ref i);
+				i++;
+				LocalizedKeyword = GetString (ref i, System.Text.Encoding.UTF8);
+				i++;
+
+				if (Compressed) {
+					text_data = Chunk.Inflate (data, i, data.Length - i);
+				} else {
+					int len = data.Length - i;
+					text_data = new byte [len];
+					System.Array.Copy (data, i, text_data, 0, len);
+				}
+			}
+
+			public override void SetText (string text)
+			{
+				byte [] raw = System.Text.Encoding.UTF8.GetBytes (text);
+				SetText (raw);
+			}
+
+			public void SetText (byte [] raw)
+			{
+				using (MemoryStream stream = new MemoryStream ()) {
+					byte [] tmp;
+					
+					text_data = raw;
+
+					tmp = Latin1.GetBytes (keyword);
+					stream.Write (tmp, 0, tmp.Length);
+					stream.WriteByte (0);
+					
+					stream.WriteByte ((byte)(compressed ? 1 : 0));
+					stream.WriteByte (Compression);
+					
+					if (Language != null && Language != System.String.Empty) {
+						tmp = Latin1.GetBytes (Language);
+						stream.Write (tmp, 0, tmp.Length);
+					}
+					stream.WriteByte (0);
+					
+					if (LocalizedKeyword != null && LocalizedKeyword != System.String.Empty) {
+						tmp = System.Text.Encoding.UTF8.GetBytes (LocalizedKeyword);
+						stream.Write (tmp, 0, tmp.Length);
+					}
+					stream.WriteByte (0);
+				
+					if (compressed) {
+						tmp = Deflate (text_data, 0, text_data.Length);
+						stream.Write (tmp, 0, tmp.Length);
+					} else {
+						stream.Write (text_data, 0, text_data.Length);
+					}
+					this.data = stream.ToArray ();
+				}
+			}
+
+			public ItxtChunk (string name, byte [] data) : base (name, data) 
+			{
+				this.Name = name;
+				encoding = System.Text.Encoding.UTF8;
+			}
+
+			public ItxtChunk (string keyword, string language, bool compressed) : base ()
+			{
+				encoding = System.Text.Encoding.UTF8;
+				this.Name = "iTXt";
+				this.keyword = keyword;
+				this.Language = language;
+				this.LocalizedKeyword = System.String.Empty;
+				this.compressed = compressed;
+				this.Compression = 0;
+			}
+		}
+
+		public class TimeChunk : Chunk {
+			//public static string Name = "tIME";
+
+			System.DateTime time;
+
+			public System.DateTime Time {
+				get {
+					return new System.DateTime (FSpot.BitConverter.ToUInt16 (data, 0, false),
+								    data [2], data [3], data [4], data [5], data [6]);
+
+				}
+				set {
+					byte [] year = BitConverter.GetBytes ((ushort)value.Year, false);
+					data [0] = year [0];
+					data [1] = year [1];
+					data [2] = (byte) value.Month;
+					data [3] = (byte) value.Day;
+					data [4] = (byte) value.Hour;
+					data [6] = (byte) value.Minute;
+					data [7] = (byte) value.Second;
+				}
+			}
+			
+			public TimeChunk (string name, byte [] data) : base (name, data) {}
+			
+			public TimeChunk ()
+			{
+				this.Name = "tIME";
+				this.Time = System.DateTime.Now;
+			}
+		}
+#if false
+		public class StandardRgbChunk : Chunk {
+			public StandardRgbChunk (string name, byte [] data) : base (name, data) {}
+			
+			public Cms.Intent RenderingIntent {
+				get {
+					return (Cms.Intent) data [0];
+				}
+			}
+		}
+#endif
+		public class GammaChunk : Chunk {
+			public GammaChunk (string name, byte [] data) : base (name, data) {}
+			private const int divisor = 100000;
+
+			public double Gamma {
+				get {
+					return FSpot.BitConverter.ToUInt32 (data, 0, false) / (double) divisor;
+				}
+			}
+		}
+		
+		public class ColorChunk : Chunk {
+			// FIXME this should be represented like a tiff rational
+			public const uint Denominator = 100000;
+
+			public ColorChunk (string name, byte [] data) : base (name, data) {}
+
+			public FSpot.Tiff.Rational WhiteX {
+				get {
+					return new FSpot.Tiff.Rational (FSpot.BitConverter.ToUInt32 (data, 0, false), Denominator);
+				}
+			}
+			public FSpot.Tiff.Rational WhiteY {
+				get { 
+					return new FSpot.Tiff.Rational (FSpot.BitConverter.ToUInt32 (data, 4, false), Denominator);
+				}
+			}
+			public FSpot.Tiff.Rational RedX {
+				get { 
+					return new FSpot.Tiff.Rational (FSpot.BitConverter.ToUInt32 (data, 8, false), Denominator);
+				}
+			}
+			public FSpot.Tiff.Rational RedY {
+				get { 
+					return new FSpot.Tiff.Rational (FSpot.BitConverter.ToUInt32 (data, 12, false), Denominator);
+				}
+			}
+			public FSpot.Tiff.Rational GreenX {
+				get { 
+					return new FSpot.Tiff.Rational (FSpot.BitConverter.ToUInt32 (data, 16, false), Denominator);
+				}
+			}
+			public FSpot.Tiff.Rational GreenY {
+				get { 
+					return new FSpot.Tiff.Rational (FSpot.BitConverter.ToUInt32 (data, 20, false), Denominator);
+				}
+			}
+			public FSpot.Tiff.Rational BlueX {
+				get { 
+					return new FSpot.Tiff.Rational (FSpot.BitConverter.ToUInt32 (data, 24, false), Denominator);
+				}
+			}
+			public FSpot.Tiff.Rational BlueY {
+				get { 
+					return new FSpot.Tiff.Rational (FSpot.BitConverter.ToUInt32 (data, 28, false), Denominator);
+				}
+			}
+		}
+
+		public enum ColorType : byte {
+			Gray = 0,
+			Rgb = 2,
+			Indexed = 3,
+			GrayAlpha = 4,	
+			RgbA = 6
+		};
+		
+		public enum CompressionMethod : byte {
+			Zlib = 0
+		};
+		
+		public enum InterlaceMethod : byte {
+			None = 0,
+			Adam7 = 1
+		};
+
+		public enum FilterMethod : byte {
+			Adaptive = 0
+		}
+
+		// Filter Types Show up as the first byte of each scanline
+		public enum FilterType  {
+			None = 0,
+			Sub = 1,
+			Up = 2,
+			Average = 3,
+			Paeth = 4
+		};
+
+		public class IhdrChunk : Chunk {
+			public uint Width;
+			public uint Height;
+			public byte Depth;
+			public ColorType Color;
+			public PngFile.CompressionMethod Compression;
+			public FilterMethod Filter;
+			public InterlaceMethod Interlace;
+
+			public IhdrChunk (string name, byte [] data) : base (name, data) {}
+			
+			public override void Load (byte [] data)
+			{
+				Width = BitConverter.ToUInt32 (data, 0, false);
+				Height = BitConverter.ToUInt32 (data, 4, false);
+				Depth = data [8];
+				Color = (ColorType) data [9];
+				//if (Color != ColorType.Rgb)
+				//	throw new System.Exception (System.String.Format ("unsupported {0}", Color));
+
+				this.Compression = (CompressionMethod) data [10];
+				if (this.Compression != CompressionMethod.Zlib)
+					throw new System.Exception (System.String.Format ("unsupported {0}", Compression));
+
+				Filter = (FilterMethod) data [11];
+				if (Filter != FilterMethod.Adaptive)
+					throw new System.Exception (System.String.Format ("unsupported {0}", Filter));
+					
+				Interlace = (InterlaceMethod) data [12];
+				//if (Interlace != InterlaceMethod.None)
+				//	throw new System.Exception (System.String.Format ("unsupported {0}", Interlace));
+
+			}
+
+			public int ScanlineComponents {
+				get {
+					switch (Color) {
+					case ColorType.Gray:
+					case ColorType.Indexed:
+						return 1;
+					case ColorType.GrayAlpha:
+						return 2;
+					case ColorType.Rgb:
+						return 3;
+					case ColorType.RgbA:
+						return 4;
+					default:
+						throw new System.Exception (System.String.Format ("Unknown format {0}", Color));
+					}
+				}
+			}
+
+			public uint GetScanlineLength (int pass)
+			{
+				uint length = 0;
+				if (Interlace == InterlaceMethod.None) {
+					int bits = ScanlineComponents * Depth;
+					length = (uint) (this.Width * bits / 8);
+
+					// and a byte for the FilterType
+					length ++;
+				} else {
+					throw new System.Exception (System.String.Format ("unsupported {0}", Interlace));
+				}
+
+				return length;
+			}
+		}
+
+		public class Crc {
+			static uint [] lookup;
+			uint value = 0xffffffff;
+			uint length;
+			System.IO.Stream stream;
+
+			public uint Value {
+				get { return (value ^ 0xffffffff); }
+			}
+
+			public uint Length {
+				get { return length; }
+			}
+
+			static Crc () {
+				lookup = new uint [265];
+				uint c, n;
+				int k;
+				
+				for (n = 0; n < 256; n++) {
+					c = n;
+					for (k = 0; k < 8; k++) {
+						if ((c & 1) != 0)
+							c = 0xedb88320 ^ (c >> 1);
+						else
+							c = c >> 1;
+					}
+					lookup [n] = c;
+				}
+			}
+
+			public Crc ()
+			{
+			}
+
+			public Crc (System.IO.Stream stream)
+			{
+				this.stream = stream;
+			}
+			
+			public void Write (byte [] buffer)
+			{
+				Write (buffer, 0, buffer.Length);
+			}
+
+			public void Write (byte [] buffer, int offset, int len)
+			{
+				for (int i = offset; i < len; i++) 
+					value = lookup [(value ^ buffer[i]) & 0xff] ^ (value >> 8); 
+
+				length += (uint)len;
+
+				if (stream != null)
+					stream.Write (buffer, offset, len);
+			}
+
+			public void WriteSum ()
+			{
+				byte [] data = BitConverter.GetBytes (Value, false);
+				stream.Write (data, 0, data.Length);
+			}
+		}
+
+		public class Chunk {
+			public string Name;
+			protected byte [] data;
+			protected static System.Collections.Hashtable name_table;
+
+			public byte [] Data {
+				get {
+					return data;
+				}
+				set {
+					Load (value);
+				}
+			}
+			
+			static Chunk () 
+			{
+
+				name_table = new System.Collections.Hashtable ();
+				name_table ["iTXt"] = typeof (ItxtChunk);
+				name_table ["tXMP"] = typeof (ItxtChunk);
+				name_table ["tEXt"] = typeof (TextChunk);
+				name_table ["zTXt"] = typeof (ZtxtChunk);
+				name_table ["tIME"] = typeof (TimeChunk);
+				name_table ["iCCP"] = typeof (IccpChunk);
+				name_table ["IHDR"] = typeof (IhdrChunk);
+				name_table ["cHRM"] = typeof (ColorChunk);
+				name_table ["pHYs"] = typeof (PhysChunk);
+				name_table ["gAMA"] = typeof (GammaChunk);
+#if false
+				name_table ["sRGB"] = typeof (StandardRgbChunk);
+#endif
+			}
+			
+			protected Chunk ()
+			{
+			}
+			
+			public Chunk (string name, byte [] data) 
+			{
+				this.Name = name;
+				this.data = data;
+				Load (data);
+			}
+			
+			protected string GetString  (ref int i, System.Text.Encoding enc) 
+			{
+				for (; i < data.Length; i++) {
+					if (data [i] == 0)
+						break;
+				}	
+				
+				return enc.GetString (data, 0, i);
+			}
+
+			protected string GetString  (ref int i) 
+			{
+				return GetString (ref i, TextChunk.Latin1);
+			}
+
+			public virtual void Load (byte [] data)
+			{
+				
+			}
+			
+			public virtual void Save (System.IO.Stream stream)
+			{
+				byte [] name_bytes = System.Text.Encoding.ASCII.GetBytes (Name);
+				byte [] length_bytes = BitConverter.GetBytes ((uint)data.Length, false);
+				stream.Write (length_bytes, 0, length_bytes.Length);
+				Crc crc = new Crc (stream);
+				crc.Write (name_bytes);
+				crc.Write (data);
+				crc.WriteSum ();
+			}
+
+			public bool Critical {
+				get {
+					return !System.Char.IsLower (Name, 0);
+				}
+			}
+
+			public bool Private {
+				get {
+					return System.Char.IsLower (Name, 1);
+				}
+			}
+			
+			public bool Reserved {
+				get {
+					return System.Char.IsLower (Name, 2);
+				}
+			}
+			
+			public bool Safe {
+				get {
+					return System.Char.IsLower (Name, 3);
+				}
+			}
+
+			public bool CheckCrc (uint value)
+			{
+				byte [] name = System.Text.Encoding.ASCII.GetBytes (Name);
+				Crc crc = new Crc ();
+				crc.Write (name);
+				crc.Write (data);
+
+				return crc.Value == value;
+			}
+
+			public static Chunk Generate (string name, byte [] data)
+			{
+				System.Type t = (System.Type) name_table [name];
+
+				Chunk chunk;
+				if (t != null)
+					chunk = (Chunk) System.Activator.CreateInstance (t, new object[] {name, data});
+				else
+				        chunk = new Chunk (name, data);
+
+				return chunk;
+			}
+
+			public static byte [] Inflate (byte [] input, int start, int length)
+			{
+				System.IO.MemoryStream output = new System.IO.MemoryStream ();
+				Inflater inflater = new Inflater ();
+				
+				inflater.SetInput (input, start, length);
+				
+				byte [] buf = new byte [1024];
+				int inflate_length;
+				while ((inflate_length = inflater.Inflate (buf)) > 0)
+					output.Write (buf, 0, inflate_length);
+				
+				output.Close ();
+				return output.ToArray ();
+			}
+
+			public static byte [] Deflate (byte [] input, int offset, int length)
+			{
+				System.IO.MemoryStream output = new System.IO.MemoryStream ();
+				Deflater deflater = new Deflater ();
+				deflater.SetInput (input, offset, length);
+				
+				byte [] buf = new byte [1024];
+				int deflate_length;
+				while ((deflate_length = deflater.Deflate (buf)) > 0)
+					output.Write (buf, 0, deflate_length);
+
+				output.Close ();
+				return output.ToArray ();
+			}
+		}
+
+		public class ChunkInflater {
+			private Inflater inflater;
+			private System.Collections.ArrayList chunks;
+
+			public ChunkInflater ()
+			{
+				inflater = new Inflater ();
+				chunks = new System.Collections.ArrayList ();
+			}
+
+			public bool Fill () 
+			{
+				while (inflater.IsNeedingInput && chunks.Count > 0) {
+					inflater.SetInput (((Chunk)chunks[0]).Data);
+					//System.Console.WriteLine ("adding chunk {0}", ((Chunk)chunks[0]).Data.Length);
+					chunks.RemoveAt (0);
+				}
+				return true;
+			}
+			
+			public int Inflate (byte [] data, int start, int length)
+			{
+				int result = 0;
+				do {
+					Fill ();
+					result += inflater.Inflate (data, start + result, length - result);
+					//System.Console.WriteLine ("Attempting Second after fill Inflate {0} {1} {2}", attempt, result, length - result);
+				} while (result < length && chunks.Count > 0);
+				
+				return result;
+			}
+		       
+			public void Add (Chunk chunk)
+			{
+				chunks.Add (chunk);
+			}
+		}
+
+#if false
+		public class ScanlineDecoder {
+			int width;
+			int height;
+			int row;
+			int col;
+			ChunkInflater inflater;
+			byte [] buffer;
+
+			public ScanlineDecoder (ChunkInflater inflater, uint width, uint height)
+			{
+				this.inflater = inflater;
+				this.row = 0;
+				this.height = (int)height;
+				this.width = (int)width;
+				
+				buffer = new byte [width * height];
+
+				Fill ();
+			}
+
+			public void Fill () 
+			{
+				for (; row < height; row ++) { 
+					col = inflater.Inflate (buffer, row * width, width);
+					
+					if (col < width) {
+						inflater.Fill ();
+						System.Console.WriteLine ("short read missing {0} {1} {2}", width - col, row, height);
+					}
+				}
+			}
+			
+			private static byte PaethPredict (byte a, byte b, byte c)
+			{
+				int p = a + b - c;
+				int pa = System.Math.Abs (p - a);
+				int pb = System.Math.Abs (p - b);
+				int pc = System.Math.Abs (p - c);
+				if (pa <= pb && pa <= pc)
+					return a;
+				else if (pb <= pc)
+					return b;
+				else 
+					return c;
+			}
+
+			public void ReconstructRow (int row, int channels)
+			{
+				int offset = row * width;
+				FilterType type = (FilterType) buffer [offset];
+				byte a = 0;
+				byte x;
+				byte b;
+				byte c = 0;
+				
+				offset++;
+				//buffer [offset++] = 0;
+				
+				int prev_line;
+
+				//System.Console.WriteLine ("type = {0}", type);
+				for (int col = 1; col < this.width;  col++) {
+					x = buffer [offset];
+
+					prev_line = offset - width;
+
+					a = col <= channels ? (byte) 0 : (byte) buffer [offset - channels];
+					b = (prev_line) < 0 ? (byte) 0 : (byte) buffer [prev_line];
+					c = (prev_line) < 0 || (col <= channels) ? (byte) 0 : (byte) buffer [prev_line - channels];
+
+#if false
+					switch (type) {
+					case FilterType.None:
+						break;
+					case FilterType.Sub:
+						x = (byte) (x + a);
+						break;
+					case FilterType.Up:
+						x = (byte) (x + b);
+						break;
+					case FilterType.Average:
+						x = (byte) (x + ((a + b) >> 1));
+						break;
+					case FilterType.Paeth:
+						x = (byte) (x + PaethPredict (a, b, c));
+						break;
+					default:					
+						throw new System.Exception (System.String.Format ("Invalid FilterType {0}", type));
+					}
+#else
+					if (type == FilterType.Sub) {
+						x = (byte) (x + a);
+					} else if (type == FilterType.Up) {
+						x = (byte) (x + b);
+					} else if (type == FilterType.Average) {
+						x = (byte) (x + ((a + b) >> 1));
+					} else if (type == FilterType.Paeth) {
+						int p = a + b - c;
+						int pa = System.Math.Abs (p - a);
+						int pb = System.Math.Abs (p - b);
+						int pc = System.Math.Abs (p - c);
+						if (pa <= pb && pa <= pc)
+							x = (byte)(x + a);
+						else if (pb <= pc)
+							x = (byte)(x + b);
+						else 
+							x = (byte)(x + c);
+					}
+#endif
+					//System.Console.Write ("{0}.", x);
+					buffer [offset ++] = x;
+				}
+
+			}
+
+			public unsafe void UnpackRGBIndexedLine (Gdk.Pixbuf dest, int line, int depth, byte [] palette, byte [] alpha)
+			{
+				int pos = line * width + 1;
+				byte * pixels = (byte *) dest.Pixels;
+				
+				pixels += line * dest.Rowstride;
+				int channels = dest.NChannels;
+				int div = (8 / depth);
+				byte mask = (byte)(0xff >> (8 - depth));
+
+				for (int i = 0; i < dest.Width; i++) {
+					int val = buffer [pos + i / div];
+					int shift = (8 - depth) - (i % div) * depth;
+
+					val = (byte) ((val & (byte)(mask << shift)) >> shift);
+
+					pixels [i * channels] = palette [val * 3];
+					pixels [i * channels + 1] = palette [val * 3 + 1];
+					pixels [i * channels + 2] = palette [val * 3 + 2];
+
+					if (channels > 3 && alpha != null) 
+						pixels [i * channels + 3] = val < alpha.Length ? alpha [val] : (byte)0xff; 
+				}
+			}
+
+			public unsafe void UnpackRGB16Line (Gdk.Pixbuf dest, int line, int channels)
+			{
+				int pos = line * width + 1;
+				byte * pixels = (byte *) dest.Pixels;
+				
+				pixels += line * dest.Rowstride;
+				
+				if (dest.NChannels != channels)
+					throw new System.Exception ("bad pixbuf format");
+
+				int i = 0;
+				int length = dest.Width * channels;
+				while (i < length) {
+					pixels [i++] = (byte) (BitConverter.ToUInt16 (buffer, pos, false) >> 8);
+					pos += 2;
+				}
+
+			}
+
+			public unsafe void UnpackRGB8Line (Gdk.Pixbuf dest, int line, int channels)
+			{
+				int pos = line * width + 1;
+				byte * pixels = (byte *) dest.Pixels;
+
+				pixels += line * dest.Rowstride;
+				if (dest.NChannels != channels)
+					throw new System.Exception ("bad pixbuf format");
+
+				System.Runtime.InteropServices.Marshal.Copy (buffer, pos, 
+									     (System.IntPtr)pixels, dest.Width * channels);
+
+			}
+
+			public unsafe void UnpackGrayLine (Gdk.Pixbuf dest, int line, int depth, bool alpha)
+			{
+				int pos = line * width + 1;
+				byte * pixels = (byte *) dest.Pixels;
+				
+				pixels += line * dest.Rowstride;
+				int div = (8 / depth);
+				byte mask = (byte)(0xff >> (8 - depth));
+				int length = dest.Width * (alpha ? 2 : 1);
+				
+				for (int i = 0; i < length; i++) {
+					byte val = buffer [pos + i / div];
+					int shift = (8 - depth) - (i % div) * depth;
+
+					if (depth != 8) {
+						val = (byte) ((val & (byte)(mask << shift)) >> shift);
+						val = (byte) (((val * 0xff) + (mask >> 1)) / mask); 
+					}
+					
+					if (!alpha || i % 2 == 0) {
+						pixels [0] = val;
+						pixels [1] = val;
+						pixels [2] = val;
+						pixels += 3;
+					} else {
+						pixels [0] = val;
+						pixels ++;
+					}
+				}
+			}
+
+			public unsafe void UnpackGray16Line (Gdk.Pixbuf dest, int line, bool alpha)
+			{
+				int pos = line * width + 1;
+				byte * pixels = (byte *) dest.Pixels;
+
+				pixels += line * dest.Rowstride;
+
+				int i = 0;
+				while (i < dest.Width) {
+					byte val = (byte) (BitConverter.ToUInt16 (buffer, pos, false) >> 8);
+					pixels [0] = val;
+					pixels [1] = val;
+					pixels [2] = val;
+					if (alpha) {
+						pos += 2;
+						pixels [3] = (byte)(BitConverter.ToUInt16 (buffer, pos, false) >> 8);
+					}
+					pos += 2;
+					pixels += dest.NChannels;
+					i++;
+				}
+			}
+
+			
+		}
+		
+		public Gdk.Pixbuf GetPixbuf ()
+		{
+			ChunkInflater ci = new ChunkInflater ();
+			Chunk palette = null;
+			Chunk transparent = null;
+
+			foreach (Chunk chunk in Chunks) {
+				if (chunk.Name == "IDAT")
+					ci.Add (chunk);
+				else if (chunk.Name == "PLTE") 
+					palette = chunk;
+				else if (chunk.Name == "tRNS")
+					transparent = chunk;
+			}
+
+			IhdrChunk ihdr = (IhdrChunk) Chunks [0];
+			System.Console.WriteLine ("Attempting to to inflate photo {0}.{1}({2}, {3})", ihdr.Color, ihdr.Depth, ihdr.Width, ihdr.Height);
+			ScanlineDecoder decoder = new ScanlineDecoder (ci, ihdr.GetScanlineLength (0), ihdr.Height);
+			decoder.Fill ();
+			//Gdk.Pixbuf pixbuf = decoder.GetPixbuf ();
+
+			//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXXXXXXXX Inflate ############################");
+
+			bool alpha = (ihdr.Color == ColorType.GrayAlpha || ihdr.Color == ColorType.RgbA || transparent != null);
+
+			Gdk.Pixbuf pixbuf = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, 
+							    alpha, 8, (int)ihdr.Width, (int)ihdr.Height);
+			
+			for (int line = 0; line < ihdr.Height; line++) {
+				switch (ihdr.Color) {
+				case ColorType.Rgb:
+					if (ihdr.Depth == 16) {
+						decoder.ReconstructRow (line, 6);
+						decoder.UnpackRGB16Line (pixbuf, line, 3);
+					} else {
+						decoder.ReconstructRow (line, 3);
+						decoder.UnpackRGB8Line (pixbuf, line, 3);
+					}
+					break;
+				case ColorType.RgbA:
+					if (ihdr.Depth == 16) {
+						decoder.ReconstructRow (line, 8);
+						decoder.UnpackRGB16Line (pixbuf, line, 4);						
+					} else {
+						decoder.ReconstructRow (line, 4);
+						decoder.UnpackRGB8Line (pixbuf, line, 4);
+					}
+					break;
+				case ColorType.GrayAlpha:
+					switch (ihdr.Depth) {
+					case 16:
+						decoder.ReconstructRow (line, 4);
+						decoder.UnpackGray16Line (pixbuf, line, true);
+						break;
+					default:
+						decoder.ReconstructRow (line, 2);
+						decoder.UnpackGrayLine (pixbuf, line, ihdr.Depth, true);
+						break;
+					}
+					break;
+				case ColorType.Gray:
+					switch (ihdr.Depth) {
+					case 16:
+						decoder.ReconstructRow (line, 2);
+						decoder.UnpackGray16Line (pixbuf, line, false);
+						break;
+					default:
+						decoder.ReconstructRow (line, 1);
+						decoder.UnpackGrayLine (pixbuf, line, ihdr.Depth, false);
+						break;
+					}
+					break;
+				case ColorType.Indexed:
+					decoder.ReconstructRow (line, 1);
+					decoder.UnpackRGBIndexedLine (pixbuf, 
+								      line, 
+								      ihdr.Depth, 
+								      palette.Data, 
+								      transparent != null ? transparent.Data : null);
+					break;
+				default:
+					throw new System.Exception (System.String.Format ("unhandled color type {0}", ihdr.Color));
+				}
+			}
+			return pixbuf;
+		}
+#endif
+		private static byte [] magic = new byte [] { 137, 80, 78, 71, 13, 10, 26, 10 };
+
+				
+		public class PngHeader {
+			ArrayList chunk_list;
+			
+			public ArrayList Chunks { 
+				get { return chunk_list; }
+			}
+			
+			public PngHeader (Stream stream) 
+			{
+				byte [] heading = new byte [8];
+				byte [] crc_data = new byte [4];
+				stream.Read (heading, 0, heading.Length);
+				
+				for (int i = 0; i < heading.Length; i++)
+				if (heading [i] != magic [i])
+					throw new System.Exception ("Invalid PNG magic number");
+				
+				chunk_list = new System.Collections.ArrayList ();
+				
+				for (int i = 0; stream.Read (heading, 0, heading.Length) == heading.Length; i++) {
+					uint length = BitConverter.ToUInt32 (heading, 0, false);
+					string name = System.Text.Encoding.ASCII.GetString (heading, 4, 4);
+					byte [] data = new byte [length];
+					if (length > 0)
+						stream.Read (data, 0, data.Length);
+					
+					stream.Read (crc_data, 0, 4);
+					uint crc = BitConverter.ToUInt32 (crc_data, 0, false);
+					
+					Chunk chunk = Chunk.Generate (name, data);
+					if (! chunk.CheckCrc (crc))
+						throw new System.Exception ("chunk crc check failed");
+					
+					//System.Console.Write ("read one {0} {1}", chunk, chunk.Name);
+					chunk_list.Add (chunk);
+					
+#if false			       
+					if (chunk is TextChunk) {
+						TextChunk text = (TextChunk) chunk;
+						System.Console.Write (" Text Chunk {0} {1}", 
+								      text.Keyword, System.String.Empty, System.String.Empty);
+					}
+					
+					TimeChunk time = chunk as TimeChunk;
+					if (time != null)
+						System.Console.Write(" Time {0}", time.Time);
+
+					System.Console.WriteLine (System.String.Empty);
+#endif
+					
+					if (chunk.Name == "IEND")
+						break;
+				}
+			}
+
+			internal string LookupText (string keyword)
+			{
+				TextChunk chunk = LookupTextChunk (keyword);
+				if (chunk != null)
+					return chunk.Text;
+
+				return null;
+			}
+			
+			internal TextChunk LookupTextChunk (string keyword)
+			{
+				foreach (Chunk chunk in Chunks) {
+					TextChunk text = chunk as TextChunk;
+					if (text != null && text.Keyword == keyword)
+						return text;
+				}
+				return null;	
+			}
+			
+			internal void Insert (Chunk chunk)
+			{
+				// FIXME The point of this function is to enforce ordering constraints
+				// it obviously isn't complete right now.
+				if (chunk is IhdrChunk)
+				Chunks.Insert (0, chunk);
+				else if (chunk is TextChunk)
+					Chunks.Insert (1, chunk);
+				else
+					throw new System.Exception ("Uknown ordering for chunk");
+			}
+			
+			public void Save (System.IO.Stream stream)
+			{
+				stream.Write (magic, 0, magic.Length);
+				foreach (Chunk chunk in Chunks) {
+					chunk.Save (stream);
+				}
+			}
+		}
+
+
+		public void Save (System.IO.Stream stream)
+		{
+			Header.Save (stream);
+		}
+
+#if false
+		public void Save (string path)
+		{
+			string  temp_path = path + ".tmp.png";
+			using (System.IO.Stream output = System.IO.File.OpenWrite (temp_path)) {
+				Save (output);
+			}
+			if (FSpot.Utils.Unix.Rename (temp_path, path) < 0) {
+				System.IO.File.Delete (temp_path);
+				throw new System.Exception (System.String.Format ("Unable to rename {0} to {1}", temp_path, path));
+			}
+		}
+
+		public override void Save (Gdk.Pixbuf pixbuf, System.IO.Stream stream)
+		{
+			byte [] buffer = PixbufUtils.Save (pixbuf, "png", null, null);
+			using (MemoryStream mem = new MemoryStream (buffer)) {
+				PngHeader converted = new PngHeader (mem);
+				
+				/* FIXME we need to update the XMP metadata here */
+				foreach (Chunk c in Chunks) {
+					if (c is TextChunk) {
+						converted.Insert (c);
+					}
+				}
+				
+				converted.Save (stream);
+			}
+		}
+
+		public override Cms.Profile GetProfile ()
+		{
+			ColorChunk color = null;
+			IccpChunk icc = null;
+			GammaChunk gamma = null;
+			StandardRgbChunk srgb = null;
+			double gamma_value = 2.2;
+			ColorCIExyY red = new ColorCIExyY (0.64, 0.33, 1.0);
+			ColorCIExyY green = new ColorCIExyY (0.3, 0.6, 1.0);
+			ColorCIExyY blue = new ColorCIExyY (0.15, 0.06, 1.0);
+			ColorCIExyY whitepoint = new ColorCIExyY (0.3127, 0.329, 1.0);
+			ColorCIExyYTriple chroma = new ColorCIExyYTriple (red, green, blue);
+
+			//System.Console.WriteLine ("Trying to get profile");
+
+			foreach (Chunk chunk in Chunks) {
+				if (color == null) 
+					color = chunk as ColorChunk;
+				if (icc == null)
+					icc = chunk as IccpChunk;
+				if (srgb == null)
+					srgb = chunk as StandardRgbChunk;
+				if (gamma == null)
+					gamma = chunk as GammaChunk;
+			}
+			
+			//System.Console.WriteLine ("color: {0} icc: {1} srgb: {2} gamma: {3}", color, icc, srgb, gamma);
+
+			if (icc != null) {
+				try {
+					return new Profile (icc.Profile);
+				} catch (System.Exception ex) {
+					System.Console.WriteLine ("Error trying to decode embedded profile" + ex.ToString ());
+				}
+			}
+
+			if (srgb != null)
+				return Profile.CreateStandardRgb ();
+
+			if (gamma != null)
+				gamma_value = 1 / gamma.Gamma;
+			
+			if (color != null) {
+				whitepoint = new ColorCIExyY (color.WhiteX.Value, color.WhiteY.Value, 1.0);
+				red = new ColorCIExyY (color.RedX.Value, color.RedY.Value, 1.0);
+				green = new ColorCIExyY (color.GreenX.Value, color.GreenY.Value, 1.0);
+				blue = new ColorCIExyY (color.BlueX.Value, color.BlueY.Value, 1.0);
+				chroma = new ColorCIExyYTriple (red, green, blue);
+			}
+
+			if (color != null || gamma != null) {
+				GammaTable table = new GammaTable (1024, gamma_value);
+				return new Profile (whitepoint, chroma, new GammaTable [] {table, table, table});
+			}
+			
+			return null;
+		}
+#endif
+		public override string Description {
+			get {
+				string description = Header.LookupText ("Description");
+
+				if (description != null)
+					return description;
+				else
+					return Header.LookupText ("Comment");
+			}
+		}
+
+		public void SetDescription (string description) 
+		{
+			TextChunk text = null;
+			text = Header.LookupTextChunk ("Description");
+			
+			if (text != null)
+				text.SetText (description);
+			else 
+				Header.Insert (new TextChunk ("Description", description));
+		}
+
+		public XmpFile GetXmp ()
+		{
+			TextChunk xmpchunk  = Header.LookupTextChunk ("XML:com.adobe.xmp");
+			if (xmpchunk == null)
+				xmpchunk = Header.LookupTextChunk ("XMP");
+
+			if (xmpchunk == null)
+				return null;
+			
+			using (MemoryStream stream = new MemoryStream (xmpchunk.TextData)) {
+				return new XmpFile (stream);
+			}
+		}
+
+		public void SetXmp (XmpFile xmp)
+		{
+			TextChunk text = null;
+
+			text = Header.LookupTextChunk ("XML:com.adobe.xmp");
+			if (text != null)
+				Chunks.Remove (text);
+			
+			text = Header.LookupTextChunk ("XMP");
+			if (text != null)
+				Chunks.Remove (text);
+
+			ItxtChunk itext = new ItxtChunk ("XML:com.adobe.xmp", "en", false);
+			using (MemoryStream stream = new MemoryStream ()) {
+				xmp.Save (stream);
+				itext.SetText (stream.ToArray ());
+			}
+			Header.Insert (itext);
+		}
+
+		public override System.DateTime Date {
+			get {
+				// FIXME: we should first try parsing the
+				// LookupText ("Creation Time") as a valid date
+
+				foreach (Chunk chunk in Chunks) {
+					TimeChunk time = chunk as TimeChunk;
+					if (time != null)
+						return time.Time.ToUniversalTime ();
+				}
+				return base.Date;
+			}
+		}
+		
+#if ENABLE_NUNIT
+		[TestFixture]
+		public class Tests {
+			public Tests ()
+			{
+				Gnome.Vfs.Vfs.Initialize ();
+				Gtk.Application.Init ();
+			}
+
+			[Test]
+			public void Save ()
+			{
+				Gdk.Pixbuf test = new Gdk.Pixbuf (null, "f-spot-32.png");
+				string path = ImageFile.TempPath ("joe.png");
+				test.Save (path, "png");
+				PngFile pimg = new PngFile (path);
+
+				string desc = "this is a png test";
+				string desc2 = "\000xa9 Novell Inc.";
+				pimg.SetDescription (desc);
+				using (Stream stream = File.OpenWrite (path)) {
+					pimg.Save (stream);
+				}
+				PngFile mod = new PngFile (path);
+				Assert.AreEqual (mod.Orientation, PixbufOrientation.TopLeft);
+				Assert.AreEqual (mod.Description, desc);
+				pimg.SetDescription (desc2);
+
+				using (Stream stream = File.OpenWrite (path)) {
+					pimg.Save (stream);
+				}
+				mod = new PngFile (path);
+				Assert.AreEqual (mod.Description, desc2);
+				
+				File.Delete (path);
+			}
+
+			[Test]
+			public void Load ()
+			{
+				string desc = "(c) 2004 Jakub Steiner\n\nCreated with The GIMP";
+				Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly ();
+				string path  = ImageFile.TempPath ("maddy.png");
+				using (Stream output = File.OpenWrite (path)) {
+					using (Stream source = assembly.GetManifestResourceStream ("f-spot-adjust-colors.png")) {
+						byte [] buffer = new byte [256];
+						while (source.Read (buffer, 0, buffer.Length) > 0) {
+							output.Write (buffer, 0, buffer.Length);
+						}
+					}
+				}
+				PngFile pimg = new PngFile (path);
+				Assert.AreEqual (pimg.Description, desc);
+
+				File.Delete (path);
+			}
+		}
+#endif
+
+#if false
+		public class ImageFile {
+			string Path;
+			public ImageFile (string path)
+			{
+				this.Path = path;
+			}
+		}
+
+		public static void Main (string [] args) 
+		{
+			System.Collections.ArrayList failed = new System.Collections.ArrayList ();
+			Gtk.Application.Init ();
+			foreach (string path in args) {
+				Gtk.Window win = new Gtk.Window (path);
+				Gtk.HBox box = new Gtk.HBox ();
+				box.Spacing = 12;
+				win.Add (box);
+				Gtk.Image image;
+				image = new Gtk.Image ();
+
+				System.DateTime start = System.DateTime.Now;
+				System.TimeSpan one = start - start;
+				System.TimeSpan two = start - start;
+				try {
+					start = System.DateTime.Now;
+					image.Pixbuf = new Gdk.Pixbuf (path);
+					one = System.DateTime.Now - start;
+				}  catch (System.Exception e) {
+				}
+				box.PackStart (image);
+
+				image = new Gtk.Image ();
+				try {
+					start = System.DateTime.Now;
+					PngFile png = new PngFile (path);
+					image.Pixbuf = png.GetPixbuf ();
+					two = System.DateTime.Now - start;
+				} catch (System.Exception e) {
+					failed.Add (path);
+					//System.Console.WriteLine ("Error loading {0}", path);
+					System.Console.WriteLine (e.ToString ());
+				}
+
+				System.Console.WriteLine ("{2} Load Time {0} vs {1}", one.TotalMilliseconds, two.TotalMilliseconds, path); 
+				box.PackStart (image);
+				win.ShowAll ();
+			}
+			
+			System.Console.WriteLine ("{0} Failed to Load", failed.Count);
+			foreach (string fail_path in failed) {
+				System.Console.WriteLine (fail_path);
+			}
+
+			Gtk.Application.Run ();
+		}
+#endif
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/PnmFile.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/PnmFile.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,319 @@
+using SemWeb;
+using System;
+using System.IO;
+
+#if ENABLE_NUNIT
+using NUnit.Framework;
+#endif
+
+namespace FSpot.Pnm {
+	public class PnmFile : ImageFile, StatementSource {
+
+                // false seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+
+		public PnmFile (Uri uri) : base (uri) 
+		{
+		}
+
+		public PnmFile (string path) : base (path) 
+		{
+		}
+
+		public class Header {
+			public string Magic;
+			public int Width;
+			public int Height;
+			public ushort Max;
+			
+			public Header (Stream stream)
+			{
+				Magic = GetString (stream);
+				Width = int.Parse (GetString (stream));
+				Height = int.Parse (GetString (stream));
+				Max = ushort.Parse (GetString (stream));
+			}
+
+			public bool IsDeep {
+				get {
+					return Max > 256;
+				}
+			}
+
+			public void Dump ()
+			{
+				System.Console.WriteLine ("Loading ({0} - {1},{2} - {3})", 
+							  Magic, Width, Height, Max);
+			}
+		}
+
+		public void Select (StatementSink sink)
+		{
+			using (Stream stream = Open ()) {
+				Header header = new Header (stream);
+				MetadataStore.AddLiteral (sink, "tiff:ImageWidth", header.Width.ToString ());
+				MetadataStore.AddLiteral (sink, "tiff:ImageLength", header.Height.ToString ());
+				string bits = header.IsDeep ? "16" : "8";
+				MetadataStore.Add (sink, "tiff:BitsPerSample", "rdf:Seq", new string [] { bits, bits, bits });
+			}
+		}
+		
+		public override Stream PixbufStream ()
+		{
+			Stream stream = Open ();
+			Header header = new Header (stream);
+			if (header.IsDeep)
+				return null;
+
+			stream.Position = 0;
+			return stream;
+		}
+
+		static char EatComment (Stream stream)
+		{
+			char c;
+			do {
+				c = (char)stream.ReadByte ();
+				
+			} while (c != '\n' && c != '\n');
+			
+			return c;
+		}
+
+		static string GetString (Stream stream)
+		{
+			System.Text.StringBuilder builder = new System.Text.StringBuilder ();
+
+			char c;
+			do {
+				c = (char)stream.ReadByte ();
+				if (c == '#')
+					c = EatComment (stream);
+
+			} while (char.IsWhiteSpace (c));
+			
+			while (! char.IsWhiteSpace (c)) {
+				builder.Append (c);
+				c = (char)stream.ReadByte ();				
+			}
+			
+			return builder.ToString ();
+		}
+
+		public static ushort [] ReadShort (Stream stream, int width, int height, int channels)
+		{
+			int length = width * height * channels;
+			ushort [] data = new ushort [length];
+			byte [] tmp = new byte [2];
+
+			for (int i = 0; i < length; i++)
+			{
+				stream.Read (tmp, 0, tmp.Length);
+				data [i] = BitConverter.ToUInt16 (tmp, 0, false);
+			}
+			return data;
+		}
+
+#if false
+		static Gdk.Pixbuf LoadRGB16 (Stream stream, int width, int height)
+		{
+			Gdk.Pixbuf pixbuf = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, false, 8, width, height);
+			unsafe {
+				byte *pixels = (byte *)pixbuf.Pixels;
+				int length = width * 6;
+				byte [] buffer = new byte [length];
+				
+				for (int row = 0; row < height; row++) {
+					stream.Read (buffer, 0, buffer.Length);
+					for (int i = 0; i < width * 3; i++) {
+						pixels [i] = (byte) (BitConverter.ToUInt16 (buffer, i * 2, false) >> 8);
+					}
+					pixels += pixbuf.Rowstride;
+				}
+			}
+			return pixbuf;
+		}
+
+		static Gdk.Pixbuf LoadRGB8 (Stream stream, int width, int height)
+		{
+			Gdk.Pixbuf pixbuf = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, false, 8, width, height);
+			unsafe {
+				byte *pixels = (byte *)pixbuf.Pixels;
+				byte [] buffer = new byte [width * 3];
+				
+				for (int i = 0; i < height; i++) {
+					stream.Read (buffer, 0, buffer.Length);
+					    
+					System.Runtime.InteropServices.Marshal.Copy (buffer, 0, 
+										     (System.IntPtr)pixels, buffer.Length);
+					
+					pixels += pixbuf.Rowstride; 
+				}
+			}
+			return pixbuf;
+		}
+
+		static PixelBuffer LoadBufferRGB16 (Stream stream, int width, int height)
+		{
+			PixelBuffer pix = new UInt16Buffer (width, height);
+			int count = width * 3;
+			byte [] buffer = new byte [count * 2];
+
+			for (int row = 0; row < height; row++) {
+				int len = 0;
+				while (len < buffer.Length) {
+					int read = stream.Read (buffer, len, buffer.Length - len);
+					if (read < 0)
+						break;
+					len += read;
+				}
+
+				pix.Fill16 (row, 0, buffer, 0, count, false);
+			}
+
+			return pix;
+		}
+
+		static PixelBuffer LoadBufferRGB8 (Stream stream, int width, int height)
+		{
+			PixelBuffer pix = new UInt16Buffer (width, height);
+			int length = width * 3;
+			byte [] buffer = new byte [length];
+			
+			for (int row = 0; row < height; row++) {
+				stream.Read (buffer, 0, buffer.Length);
+				pix.Fill8 (row, 0, buffer, 0, buffer.Length);
+			}
+
+			return pix;
+		}
+
+		public static FSpot.Imaging.PixelBuffer LoadBuffer (Stream stream)
+		{
+
+			Header header = new Header (stream);
+			header.Dump (); 
+
+			switch (header.Magic) {
+			case "P6":
+				if (header.IsDeep)
+					return LoadBufferRGB16 (stream, header.Width, header.Height);
+				else
+					return LoadBufferRGB8 (stream, header.Width, header.Height);
+			default:
+				throw new System.Exception (System.String.Format ("unknown pnm type {0}", header.Magic));
+			}			
+		}
+
+		public override Gdk.Pixbuf Load ()
+		{
+			try {
+				using (Stream stream = Open ()) {
+					Gdk.Pixbuf pixbuf = PnmFile.Load (stream);
+					return pixbuf;
+				}
+			} catch (System.Exception e) {
+				System.Console.WriteLine (e.ToString ());
+			}
+			return null;
+		}
+
+		public override Gdk.Pixbuf Load (int width, int height)
+		{
+			return PixbufUtils.ScaleToMaxSize (this.Load (), width, height);
+		}
+
+		public override void Save (Gdk.Pixbuf pixbuf, System.IO.Stream stream)
+		{
+			if (pixbuf.HasAlpha)
+				throw new NotImplementedException ();
+
+			// FIXME this should be part of the header class
+			string header = String.Format ("P6\n"
+						       + "#Software: {0} {1}\n"
+						       + "{2} {3}  #Width and Height\n"
+						       + "255\n", 
+						       FSpot.Defines.PACKAGE,
+						       FSpot.Defines.VERSION,
+						       pixbuf.Width, 
+						       pixbuf.Height);
+						       
+			byte [] header_bytes = System.Text.Encoding.UTF8.GetBytes (header);
+			stream.Write (header_bytes, 0, header.Length);
+										 
+			unsafe {
+				byte * src_pixels = (byte *) pixbuf.Pixels;
+				int src_stride = pixbuf.Rowstride;
+				int count = pixbuf.Width * pixbuf.NChannels;
+				int height = pixbuf.Height;
+
+				for (int y = 0; y < height; y++) {
+					for (int x = 0; x < count; x++) {
+						stream.WriteByte (* (src_pixels + x));
+					}
+					src_pixels += src_stride;
+				}
+			}
+		}
+
+		public static Gdk.Pixbuf Load (Stream stream)
+		{
+			Header header = new Header (stream);
+			header.Dump ();
+
+			switch (header.Magic) {
+			case "P6":
+				if (header.IsDeep) {
+#if SKIP_BUFFER					
+					return LoadRGB16 (stream, header.Width, header.Height);
+#else
+					stream.Position = 0;
+					FSpot.Imaging.PixelBuffer image = FSpot.Pnm.PnmFile.LoadBuffer (stream);
+					Gdk.Pixbuf result = image.ToPixbuf (Cms.Profile.CreateStandardRgb ());
+					return result;
+#endif
+				} else
+					return LoadRGB8 (stream, header.Width, header.Height);
+			default:
+				throw new System.Exception (System.String.Format ("unknown pnm type {0}", header.Magic));
+			}			
+		}
+#endif
+	}
+
+#if ENABLE_NUNIT
+	[TestFixture]
+	public class Tests {
+		[Test]
+		public void SaveLoad ()
+		{
+			using (Gdk.Pixbuf pixbuf = new Gdk.Pixbuf (null, "f-spot-32.png")) {
+				Gdk.Pixbuf source = pixbuf;
+				if (pixbuf.HasAlpha)
+					source = PixbufUtils.Flatten (pixbuf);
+
+				string path = ImageFile.TempPath ("test.ppm");
+				PnmFile pnm = new PnmFile (path);
+				using (Stream stream = File.OpenWrite (path)) {
+					pnm.Save (source, stream);
+				}
+
+				pnm = new PnmFile (path);
+
+				using (Gdk.Pixbuf saved = pnm.Load ()) {
+					Assert.IsNotNull (saved);
+					Assert.AreEqual (saved.Width, source.Width);
+					Assert.AreEqual (saved.Height, source.Height);
+				}
+				
+				if (source != pixbuf)
+					source.Dispose ();
+
+				File.Delete (path);
+			}
+		}
+	}	
+#endif
+}

Added: trunk/beagle/Util/F-Spot/Imaging/RafFile.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/RafFile.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,122 @@
+namespace FSpot.Raf {
+	// This is reverse engineered from looking at the sample files I have
+	// from what I can tell the file is always BigEndian, although the embedded jpeg may not be
+	// and there is a start long offset at 0x54 (or possibly 0x56 if it is a short) that points to
+	// the start of the embedded jpeg and followed by a long length that gives the length of the jpeg
+	// data.   
+	//
+	// Following that there seem to be more offsets and lengths (probably for the raw data) that I haven't
+	// completely figured out yet.  More to follow.
+
+	// ALL the sample files I have begin with "FUJIFILMCCD-RAW "
+
+	
+	public class WhiteBalance {
+		// see dcraw parse_fuli
+		public WhiteBalance (System.IO.Stream stream)
+		{
+
+		}
+	}
+	
+	public class RafFile : ImageFile, SemWeb.StatementSource {
+
+                // false seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+
+		public RafFile (System.Uri uri) : base (uri)
+		{
+		}
+
+		private Exif.ExifData exif_data;
+		public Exif.ExifData ExifData {
+			get {
+				if (exif_data == null)
+					exif_data = new Exif.ExifData(uri.LocalPath);
+				System.Console.WriteLine ("loading exif data");
+				return exif_data;
+			}
+		}
+		
+		public override PixbufOrientation GetOrientation (){
+			PixbufOrientation orientation = PixbufOrientation.TopLeft;
+
+			Exif.ExifEntry e = this.ExifData.GetContents (Exif.Ifd.Zero).Lookup (Exif.Tag.Orientation);
+			if (e != null) {
+				ushort [] value = e.GetDataUShort ();
+				orientation = (PixbufOrientation) value [0];
+			}
+
+			if (orientation < PixbufOrientation.TopLeft || orientation > PixbufOrientation.LeftBottom)
+				orientation = PixbufOrientation.TopLeft;
+
+			return orientation;
+		}
+
+		public RafFile (string path) : base (path)
+		{
+		}
+
+		public override System.IO.Stream PixbufStream ()
+		{
+			byte [] data = GetEmbeddedJpeg ();
+			
+			if (data != null)
+				return new System.IO.MemoryStream (data);
+			else
+				return DCRawFile.RawPixbufStream (uri);
+		}
+ 
+#if false
+		public override Gdk.Pixbuf Load ()
+		{
+			return new Gdk.Pixbuf (PixbufStream ());
+		}
+
+		public override Gdk.Pixbuf Load (int width, int height)
+		{
+			Gdk.Pixbuf full = this.Load ();
+			Gdk.Pixbuf rotated = PixbufUtils.TransformOrientation (full, this.GetOrientation(), true);
+			Gdk.Pixbuf scaled  = PixbufUtils.ScaleToMaxSize (rotated, width, height);
+			full.Dispose ();
+			return scaled;
+		}
+#endif
+		public void Select (SemWeb.StatementSink sink)
+		{
+			byte [] data = GetEmbeddedJpeg ();
+			if (data != null) {
+				System.IO.Stream stream = new System.IO.MemoryStream (data);
+				JpegHeader header = new JpegHeader (stream);
+				header.Select (sink);
+			}
+		}
+
+		private byte [] GetEmbeddedJpeg ()
+		{
+			using (System.IO.Stream stream = Open ()) {
+				stream.Position = 0x54;
+				byte [] data = new byte [24];
+				stream.Read (data, 0, data.Length);
+				uint jpeg_offset = BitConverter.ToUInt32 (data, 0, false);
+				uint jpeg_length = BitConverter.ToUInt32 (data, 4, false);
+
+				// FIXME implement wb parsing
+				//uint wb_offset = BitConverter.ToUInt32 (data, 8, false);
+				//uint wb_length = BitConverter.ToUInt32 (data, 12, false);
+				
+				// FIXME implement decoding
+				//uint raw_offset = BitConverter.ToUInt32 (data, 16, false);
+				//uint raw_length = BitConverter.ToUInt32 (data, 20, false);
+
+				byte [] image = new byte [jpeg_length];
+				stream.Position = jpeg_offset;
+				stream.Read (image, 0, image.Length);
+				return image;
+			}
+
+		}
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/SvgFile.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/SvgFile.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,62 @@
+using System;
+
+namespace FSpot.Svg {
+	public class SvgFile : ImageFile // SemWeb.StatementSource 
+	{
+		MetadataStore store;
+
+                // false seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+
+		public SvgFile (Uri uri) : base (uri)
+		{
+		}
+
+		public SvgFile (string path) : base (path) 
+		{
+		}
+
+		public MetadataStore Store {
+			get {
+				if (store == null) {
+					store = new MetadataStore ();
+					using (System.IO.Stream input = Open ()) {
+						Load (input);
+					}
+				}
+				return store;
+			}
+		}
+
+		public void Load (System.IO.Stream stream)
+		{
+			try {
+				store.Import (new SemWeb.RdfXmlReader (stream));
+				store.Dump ();
+
+			} catch (System.Exception e) {
+				System.Console.WriteLine (e.ToString ());
+			}
+		}
+
+#if BROKEN_RSVG
+		public override Gdk.Pixbuf Load (int max_width, int max_height)
+		{
+			// FIXME this is a hack to work around a crash in the scaled
+			// gdk pixbuf rsvg loader.  We load it without scaling it then scale the image
+			using (System.IO.Stream stream = Open ()) {
+				using (Gdk.Pixbuf pixbuf = new Gdk.Pixbuf (stream)) {
+					Gdk.Pixbuf scaled = PixbufUtils.ScaleToMaxSize (pixbuf, max_width, max_height);
+					return scaled;
+				}
+			}
+		}
+#endif
+		public void Select (SemWeb.StatementSink sink)
+		{
+			Store.Select (sink);
+		}
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/Tiff.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/Tiff.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,2399 @@
+//#define DEBUG_LOADER
+using FSpot;
+using SemWeb;
+using System;
+using System.IO;
+using System.Collections.Generic;
+
+#if ENABLE_NUNIT
+using NUnit.Framework;
+#endif
+
+namespace FSpot.Tiff {
+
+	// This is primarily to preserve the names from the specification
+	// because they differ from the tiff standard names
+	public enum NiffId : ushort {
+		SubfileType                     = 0x00fe,
+		PelPathLength                   = 0x0100,
+		LineProgressionLength           = 257,
+		BitsPerSample                   = 0x0101,
+		PhotometricInterpretation       = 0x0106,
+		DataOffset                      = 0x0111,
+		SamplesPerPixel 		= 0x0115,
+		DataByteCounts                  = 0x0117,
+		PelPathResolution               = 0x011a,
+		LineProgressionResolution       = 0x011b,
+		ResolutionUnit  		= 0x0128,
+		ColumnsPerPelPath               = 322,
+		RowsPerLineProgression          = 323,
+	        Rotation                        = 33465,
+	        NavyCompression                 = 33466,
+		TileIndex                       = 33467
+	}
+
+	public enum TagGPS : ushort {
+		GPSVersionID                    = 0x0000,
+		GPSLatitudeRef                  = 0x0001,
+		GPSLatitude                     = 0x0002,
+			GPSLongitudeRef                    = 0x0003,
+			GPSLongitue                        = 0x0004,
+			GPSAltitudeRef                     = 0x0005,
+			GPSAltitude                        = 0x0006,
+			GPSTimeStamp                       = 0x0007,
+			GPSSatellites                      = 0x0008,
+			GPSStatus                          = 0x0009,
+			GPSMeasureMode                     = 0x000a,
+			GPSDOP                             = 0x000b,
+			GPSSpeedRef                        = 0x000c,
+			GPSSpeed                           = 0x000d,
+			GPSTrackRef                        = 0x000e,
+			GPSTrack                           = 0x000f,
+			GPSImgDirectionRef                 = 0x0010,
+			GPSImgDirection                    = 0x0011,
+			GPSMapDatum                        = 0x0012,
+			GPSDestLatitudeRef                 = 0x0013,
+			GPSDestLatitude                    = 0x0014,
+			GPSDestLongitudeRef                = 0x0015,
+			GPSDestLongitude                   = 0x0016,
+			GPSDestBearingRef                  = 0x0017,
+			GPSDestBearing                     = 0x0018,
+			GPSDestDistanceRef                 = 0x0019,
+			GPSDestDistance                    = 0x001a,
+			GPSProcessingMethod                = 0x001b,
+			GPSAreaInformation                 = 0x001c,
+			GPSDateStamp                       = 0x001d,
+			GPSDifferential                    = 0x001e
+	}
+
+	public enum TagId : ushort {
+		InteroperabilityIndex		= 0x0001,
+		InteroperabilityVersion	        = 0x0002,
+		
+		NewSubfileType                  = 0x00fe,
+		SubfileType                     = 0x00ff,
+		
+		ImageWidth 			= 0x0100,
+		ImageLength 			= 0x0101,
+		BitsPerSample 	         	= 0x0102,
+		Compression 			= 0x0103,
+		PhotometricInterpretation 	= 0x0106,
+		FillOrder 			= 0x010a,
+		DocumentName 			= 0x010d,
+		ImageDescription 		= 0x010e,
+		Make 				= 0x010f,
+		Model 				= 0x0110,
+		StripOffsets 			= 0x0111,
+		Orientation 			= 0x0112,
+		SamplesPerPixel 		= 0x0115,
+		RowsPerStrip    		= 0x0116,
+		StripByteCounts 		= 0x0117,
+		XResolution 			= 0x011a,
+		YResolution 			= 0x011b,
+		PlanarConfiguration 		= 0x011c,
+
+		T4Options                       = 0x0124,
+		T6Options                       = 0x0125,
+
+		ResolutionUnit  		= 0x0128,
+		TransferFunction 		= 0x012d,
+		Software 			= 0x0131,
+		DateTime			= 0x0132,
+		Artist				= 0x013b,
+		WhitePoint			= 0x013e,
+		PrimaryChromaticities		= 0x013f,
+			
+		HalftoneHints                   = 0x0141,
+		// Tiled images
+		TileWidth                       = 0x0142,
+		TileLength                      = 0x0143,
+		TileOffsets                     = 0x0144,
+	        TileByteCounts                  = 0x0145,
+
+		SubIFDs                         = 0x014a, // TIFF-EP
+
+		// CMYK images
+		InkSet                          = 0x014c,
+		NumberOfInks                    = 0x014e,
+	        InkNames                        = 0x014d,
+		DotRange                        = 0x0150,
+		TargetPrinter                   = 0x0151,
+		ExtraSamples                    = 0x0152,
+		SampleFormat                    = 0x0153,
+		SMinSampleValue                 = 0x0154,
+		SMaxSampleValue                 = 0x0155,
+		
+		TransferRange			= 0x0156,
+		
+		ClipPath                        = 0x0157, // TIFF PageMaker Technote #2.
+		
+		JPEGTables                      = 0x015b, // TIFF-EP
+		
+		JPEGProc			= 0x0200,
+		JPEGInterchangeFormat	        = 0x0201,
+		JPEGInterchangeFormatLength	= 0x0202,
+	        JPEGRestartInterval             = 0x0203,
+	        JPEGLosslessPredictors          = 0x0205,
+		JPEGPointTransforms             = 0x0206,
+		JPEGQTables                     = 0x0207,
+		JPEGDCTables                    = 0x0208,
+		JPEGACTables                    = 0x0209,
+
+		YCbCrCoefficients		= 0x0211,
+		YCbCrSubSampling		= 0x0212,
+		YCbCrPositioning		= 0x0213,
+
+		ReferenceBlackWhite		= 0x0214,
+		RelatedImageFileFormat   	= 0x1000,
+		RelatedImageWidth		= 0x1001,
+		RelatedImageLength		= 0x1002,
+		CFARepeatPatternDim		= 0x828d,
+		CFAPattern			= 0x828e,
+		BatteryLevel			= 0x828f,
+		Copyright			= 0x8298,
+		ExposureTime			= 0x829a,
+		FNumber 			= 0x829d,
+
+		// These are from the NIFF spec and only really valid when the header begins with IIN1
+		// see the NiffTag enum for the specifcation specific names
+			Rotation                        = 0x82b9,
+			NavyCompression                 = 0x82ba,
+			TileIndex                       = 0x82bb,
+		// end NIFF specific
+			
+		IPTCNAA	        		= 0x83bb,
+
+		PhotoshopPrivate                = 0x8649,
+
+		ExifIfdPointer      		= 0x8769,
+		InterColorProfile		= 0x8773,
+		ExposureProgram 		= 0x8822,
+		SpectralSensitivity		= 0x8824,
+		GPSInfoIfdPointer		= 0x8825,
+		ISOSpeedRatings	        	= 0x8827,
+		OECF				= 0x8828,
+		ExifVersion			= 0x9000,
+		DateTimeOriginal		= 0x9003,
+		DateTimeDigitized		= 0x9004,
+		ComponentsConfiguration	        = 0x9101,
+		CompressedBitsPerPixel	        = 0x9102,
+		ShutterSpeedValue		= 0x9201,
+		ApertureValue			= 0x9202,
+		BrightnessValue  		= 0x9203,
+		ExposureBiasValue		= 0x9204,
+		MaxApertureValue		= 0x9205,
+		SubjectDistance 		= 0x9206,
+		MeteringMode			= 0x9207,
+		LightSource			= 0x9208,
+		Flash				= 0x9209,
+		FocalLength			= 0x920a,
+			
+		FlashEnergy_TIFFEP              = 0x920b,// TIFF-EP 
+		SpacialFrequencyResponse        = 0x920c,// TIFF-EP 
+		Noise                           = 0x920d,// TIFF-EP 
+		FocalPlaneXResolution_TIFFEP    = 0x920e,// TIFF-EP 
+		FocalPlaneYResolution_TIFFEP    = 0x920f,// TIFF-EP 
+		FocalPlaneResolutionUnit_TIFFEP = 0x9210,// TIFF-EP 
+		ImageName                       = 0x9211,// TIFF-EP 
+		SecurityClassification          = 0x9212,// TIFF-EP 
+		
+		ImageHistory                    = 0x9213, // TIFF-EP null separated list
+
+	        SubjectArea			= 0x9214,
+
+		ExposureIndex_TIFFEP            = 0x9215, // TIFF-EP
+		TIFFEPStandardID                = 0x9216, // TIFF-EP
+		SensingMethod_TIFFEP            = 0x9217, // TIFF-EP
+			
+		MakerNote			= 0x927c,
+		UserComment			= 0x9286,
+		SubSecTime			= 0x9290,
+		SubSecTimeOriginal		= 0x9291,
+		SubSecTimeDigitized		= 0x9292,
+		FlashPixVersion 		= 0xa000,
+		ColorSpace			= 0xa001,
+		PixelXDimension 		= 0xa002,
+		PixelYDimension 		= 0xa003,
+		RelatedSoundFile		= 0xa004,
+		InteroperabilityIfdPointer	= 0xa005,
+		FlashEnergy			= 0xa20b,
+		SpatialFrequencyResponse	= 0xa20c,
+		FocalPlaneXResolution	        = 0xa20e,
+		FocalPlaneYResolution	        = 0xa20f,
+		FocalPlaneResolutionUnit	= 0xa210,
+		SubjectLocation 		= 0xa214,
+		ExposureIndex			= 0xa215,
+		SensingMethod			= 0xa217,
+		FileSource			= 0xa300,
+		SceneType			= 0xa301,
+		ExifCFAPattern		        = 0xa302,
+		CustomRendered  		= 0xa401,
+		ExposureMode			= 0xa402,
+		WhiteBalance			= 0xa403,
+		DigitalZoomRatio		= 0xa404,
+		FocalLengthIn35mmFilm	        = 0xa405,
+		SceneCaptureType		= 0xa406,
+		GainControl			= 0xa407,
+		Contrast			= 0xa408,
+		Saturation			= 0xa409,
+		Sharpness			= 0xa40a,
+		DeviceSettingDescription	= 0xa40b,
+		SubjectDistanceRange		= 0xa40c,
+		ImageUniqueId   		= 0xa420,
+
+		// The Following IDs are not described the EXIF spec
+		Gamma                           = 0xa500,
+
+		// The XMP spec declares that XMP data should live 0x2bc when
+		// embedded in tiff images.
+		XMP                             = 0x02bc,
+		
+		// from the dng spec
+		DNGVersion                      = 0xc612, // Ifd0
+		DNGBackwardVersion              = 0xc613, // Ifd0
+		UniqueCameraModel               = 0xc614, // Ifd0
+		LocalizedCameraModel            = 0xc615, // Ifd0
+		CFAPlaneColor                   = 0xc616, // RawIfd
+		CFALayout                       = 0xc617, // RawIfd
+		LinearizationTable              = 0xc618, // RawIfd
+		BlackLevelRepeatDim             = 0xc619, // RawIfd
+		BlackLevel                      = 0xc61a, // RawIfd
+		BlackLevelDeltaH                = 0xc61b, // RawIfd
+		BlackLevelDeltaV                = 0xc61c, // RawIfd
+		WhiteLevel                      = 0xc61d, // RawIfd
+		DefaultScale                    = 0xc61e, // RawIfd		
+		DefaultCropOrigin               = 0xc61f, // RawIfd
+		DefaultCropSize                 = 0xc620, // RawIfd
+		ColorMatrix1                    = 0xc621, // Ifd0
+		ColorMatrix2                    = 0xc622, // Ifd0
+		CameraCalibration1              = 0xc623, // Ifd0
+		CameraCalibration2              = 0xc624, // Ifd0
+		ReductionMatrix1                = 0xc625, // Ifd0
+		ReductionMatrix2                = 0xc626, // Ifd0
+		AnalogBalance                   = 0xc627, // Ifd0
+		AsShotNetural                   = 0xc628, // Ifd0
+		AsShotWhiteXY                   = 0xc629, // Ifd0
+		BaselineExposure                = 0xc62a, // Ifd0
+		BaselineNoise                   = 0xc62b, // Ifd0
+		BaselineSharpness               = 0xc62c, // Ifd0
+		BayerGreeSpit                   = 0xc62d, // Ifd0
+		LinearResponseLimit             = 0xc62e, // Ifd0
+		CameraSerialNumber              = 0xc62f, // Ifd0
+		LensInfo                        = 0xc630, // Ifd0
+		ChromaBlurRadius                = 0xc631, // RawIfd
+		AntiAliasStrength               = 0xc632, // RawIfd
+		DNGPrivateData                  = 0xc634, // Ifd0
+		
+		MakerNoteSafety                 = 0xc635, // Ifd0
+
+		// The Spec says BestQualityScale is 0xc635 but it appears to be wrong
+		//BestQualityScale                = 0xc635, // RawIfd 
+		BestQualityScale                = 0xc63c, // RawIfd  this looks like the correct value
+
+		CalibrationIlluminant1          = 0xc65a, // Ifd0
+		CalibrationIlluminant2          = 0xc65b, // Ifd0
+		
+		// Print Image Matching data
+		PimIfdPointer                   = 0xc4a5
+	}
+
+	public struct SRational {
+		public int Numerator;
+		public int Denominator;
+		
+		public SRational (byte [] raw_data, int offset, bool little)
+		{
+			Numerator = BitConverter.ToInt32 (raw_data, offset, little);
+			Denominator = BitConverter.ToInt32 (raw_data, offset, little);
+		}
+
+		public SRational (int numerator, int denominator)
+		{
+			Numerator = numerator;
+			Denominator = denominator;
+		}
+
+		public static SRational BitwiseCopy (Rational rational)
+		{
+			SRational result;
+
+			result.Numerator = unchecked ((int) rational.Numerator);
+			result.Denominator = unchecked ((int) rational.Denominator);
+			return result;
+		}
+
+		public override string ToString ()
+		{
+			if (Numerator == 0 || Denominator == 0)
+				return String.Format ("{0}/{1}", Numerator, Denominator);
+			else if (Numerator % Denominator == 0)
+				return String.Format ("{0}", Numerator / Denominator);
+			else if (Denominator % Numerator == 0)
+				return String.Format ("1/{0}", Denominator / Numerator);
+
+			return String.Format ("{0}/{1}", Numerator, Denominator);
+		}
+		
+		public double Value {
+			get {
+				return Numerator / (double)Denominator;
+			}
+		}
+	}
+
+	public struct Rational {
+		public uint Numerator;
+		public uint Denominator;
+
+		public Rational (uint numerator, uint denominator)
+		{
+			Numerator = numerator;
+			Denominator = denominator;
+		}
+		
+		public Rational (string value)
+		{
+			string [] vals = value.Split ('/');
+			if (vals.Length == 2) {
+				this.Numerator = UInt32.Parse (vals [0]);
+				this.Denominator = UInt32.Parse (vals [1]);
+				return;
+			} if (vals.Length == 1) {
+				double tmp = Double.Parse (value);
+				this.Numerator = (uint) (tmp * 100000);
+				this.Denominator = 100000;
+			} else
+				throw new ParseException ("unable to parse rational value");
+		}
+
+		public override string ToString ()
+		{
+			if (Numerator == 0 || Denominator == 0)
+				return String.Format ("{0}/{1}", Numerator, Denominator);
+			else if (Numerator % Denominator == 0)
+				return String.Format ("{0}", Numerator / Denominator);
+			else if (Denominator % Numerator == 0)
+				return String.Format ("1/{0}", Denominator / Numerator);
+
+			return String.Format ("{0}/{1}", Numerator, Denominator);
+		}
+		
+		public double Value {
+			get {
+				return Numerator / (double)Denominator;
+			}
+		}
+	}
+
+	struct UserComment {
+		string Charset;
+		public string Value;
+
+		public UserComment (string value)
+		{
+			Charset = null;
+			Value = value;
+		}
+
+		public UserComment (byte [] raw_data, bool little)
+		{
+			if (raw_data.Length == 8 || raw_data.Length == 0) { 
+				Charset = null;
+				Value = String.Empty;
+				return;
+			} else if (raw_data.Length < 8) {
+				throw new Exception ("Invalid UserComment value, no charset found");
+			}
+
+			string charset = System.Text.Encoding.ASCII.GetString (raw_data, 0, 8);
+			System.Text.Encoding enc;
+
+			switch (charset) {
+			case "ASCII\0\0\0":
+				enc = System.Text.Encoding.ASCII;
+				break;
+			case "UNICODE\0":
+			case "Unicode\0":
+				enc = new System.Text.UnicodeEncoding (! little, true);
+				break;
+			case "JIS\0\0\0\0\0":
+				// FIXME this requires mono locale extras
+				try {
+					enc = System.Text.Encoding.GetEncoding ("euc-jp");
+				} catch {
+					System.Console.WriteLine ("missing jis0208 encoding");
+					enc = System.Text.Encoding.Default;
+				}
+				break;
+			case "\0\0\0\0\0\0\0\0":
+				// FIXME the spec says to use the local encoding in this case, we could probably
+				// do something smarter, but whatever.
+				enc = System.Text.Encoding.Default;
+				break;
+			default:
+				enc = null;
+				throw new ParseException (System.String.Format ("Invalid charset name: {0}", charset));
+			}
+
+			Charset = charset;
+			// for (int i = 0; i < raw_data.Length; i++)
+			//	System.Console.WriteLine ("{0} - \"{1}\"", raw_data [i].ToString ("x"), raw_data [i]);
+			Value = enc.GetString (raw_data, 8, raw_data.Length - 8).Trim('\0');
+		}
+
+		public byte [] GetBytes (bool is_little)
+		{
+			bool ascii = true;
+			string description = Value;
+			System.Text.Encoding enc;
+			string heading;
+
+			for (int i = 0; i < description.Length; i++) {
+				if (description [i] > 127) {
+					ascii = false;
+					break;
+				}
+			}
+
+			if (ascii) {
+				heading = "ASCII\0\0\0";
+				enc = new System.Text.ASCIIEncoding ();
+			} else {
+				heading = "Unicode\0";
+				enc = new System.Text.UnicodeEncoding (! is_little, true);
+			}
+			
+			int len = enc.GetByteCount (description);
+			byte [] data = new byte [len + heading.Length];
+			System.Text.Encoding.ASCII.GetBytes (heading, 0, heading.Length, data, 0);
+			enc.GetBytes (Value, 0, Value.Length, data, heading.Length);
+			
+			UserComment c = new UserComment (data, is_little);
+			System.Console.WriteLine ("old = \"{0}\" new = \"{1}\" heading = \"{2}\"", c.Value, description, heading);
+			return data;
+		}
+
+		public override string ToString ()
+		{
+			return String.Format ("({0},charset={1})", Value, Charset);
+		}
+	}
+
+	public struct CFAPattern {
+		public ushort Rows;
+		public ushort Columns;
+		public byte [] Values;
+
+		public CFAPattern (byte [] raw_data, bool little)
+		{
+			Columns = BitConverter.ToUInt16 (raw_data, 0, little);
+			Rows = BitConverter.ToUInt16 (raw_data, 2, little);
+
+			Values = new byte [Rows * Columns];
+			// FIXME the contents here may differ from what we
+			// expect, but for now we won't throw an exception
+			// since the entry still may be a valid structure.
+			if (raw_data.Length -4 < Values.Length)
+				System.Array.Copy (raw_data, 4, Values, 0, Values.Length);
+		}
+
+		/* 
+		 * Note the Exif spec defines a CFA pattern tag that includes the row and column counts as shorts
+		 * inside the first four bytes of the entry.  The Tiff-EP standard define the CFARepeatPattern tag
+		 * that contains the row and column counts presumably since the Exif version wouldn't allow you to
+		 * alter the endian of the file without knowing the tag layout.
+		 */
+		
+		public CFAPattern (ushort rows, ushort cols, byte [] raw_data,  bool little)
+		{
+			Columns = rows;
+			Rows = cols;
+			Values = new byte [rows * cols];
+			System.Array.Copy (raw_data, 0, Values, 0, Values.Length);
+		}
+	}
+
+	public struct OECFTable {
+		public ushort Rows;
+		public ushort Columns;
+		public string [] Names;
+		public SRational [] Values;
+		
+		public OECFTable (byte [] raw_data, bool little)
+		{
+			Columns = BitConverter.ToUInt16 (raw_data, 0, little);
+			Rows = BitConverter.ToUInt16 (raw_data, 2, little);
+			Names = new string [Columns];
+			Values = new SRational [Columns * Rows];
+
+			int pos = 2;
+			int i;
+			int type_size = DirectoryEntry.GetTypeSize (EntryType.SRational);
+
+			for (i = 0; i < Names.Length; i++)
+				Names [i] = ReadString (raw_data, ref pos);
+			
+			for (i = 0; i < Values.Length; i++)
+				Values [i] = new SRational (raw_data, pos + i * type_size, little);
+			
+		}
+
+		public string ReadString (byte [] data, ref int pos)
+		{
+			int start = pos;
+			for (; pos < data.Length; pos++) {
+				if (data [pos] == 0)
+					break;
+			}	
+			return System.Text.Encoding.ASCII.GetString (data, start, pos - start);
+		}
+	}
+
+	public enum ExtraSamples {
+		Unspecified = 0,
+		AssociatedAlpha = 1,
+		UnassociatedAlpa = 2
+	}
+
+	public enum FileSource {
+		DCF = 3,
+	}
+
+	public enum PhotometricInterpretation : ushort {
+		WhiteIsZero = 0,
+		BlackIsZero = 1,
+		RGB = 2,
+		PaletteColor = 3,
+		TransparencyMask = 4,
+		Separated = 5,  // CMYK
+		YCbCr = 6,
+		CIELab = 8,
+		ICCLab = 9,
+		ITULab = 10,
+		LogL = 32844, // Log Luminance
+		LogLUV = 32845,
+		ColorFilterArray = 32803,  // ColorFilterArray... the good stuff
+		LinearRaw = 34892  // DBG LinearRaw
+	}
+
+	public enum PlanarConfiguration {
+		Chunky = 1,
+		Planar = 2
+	}
+	
+	public enum Compression {
+		Packed = 1,
+		Huffman = 2,
+		T4 = 3,
+		T6 = 4,
+		LZW = 5,
+		JPEG = 6,
+		JPEGStream = 7,  // TIFF-EP stores full jpeg stream 
+		Deflate = 8,
+		JBIG = 9,
+		JBIG_MRC,
+		PackBits = 32773,
+		NikonCompression = 34713,
+		Deflate_experimental = 0x80b2
+	}
+
+	public enum JPEGProc {
+		BaselineSequencial = 1,
+		LosslessHuffman = 14,
+	}
+
+	public enum SubfileType {
+		FullResolution = 1,
+		ReducedResolution = 2,
+		PageOfMultipage = 3
+	}
+	
+	public enum ExposureProgram {
+		NotDefined = 0,
+		Manual = 1,
+		NormalProgram = 2,  // Normal Program
+		AperturePriority = 3, // Aperture priority
+		ShutterPriorty = 4, // Shutter priority
+		CreativeProgram = 5, // Creative program
+		ActionProgram = 6, // Action program
+		PortraitMode = 7, // Portrait mode
+		LandscapeMode = 8 // Landscape mode
+	}
+
+	public enum ExposureMode {
+		Auto = 0,
+		Manual = 1,
+		AutoBracket = 2
+	}
+
+	public enum CustomRendered : ushort {
+		Normal = 0,
+		Custom = 1
+	}
+
+	public enum SceneType {
+		DirectlyPhotographed = 1
+	}
+
+	public enum MeteringMode {
+		Uknown = 0,
+		Average = 1,
+		CenterWeightedAverage = 2,
+		Spot = 3,
+		MulitSpot = 4,
+		Pattern = 5,
+		Partial = 6,
+	}
+
+	public enum SceneCaptureType : ushort {
+		Standard = 0,
+		Landscape = 1,
+		Portrait = 2,
+		NightScene = 3
+	}
+
+	public enum GainControl : ushort {
+		None = 0,
+		LowGainUp = 1,
+		HighGainUp = 2,
+		LowGainDown = 3,
+		HighGainDown = 4
+	}
+
+	public enum Contrast : ushort {
+		Normal = 0,
+		Soft = 1,
+		Hard = 2
+	}
+	
+	public enum Saturation : ushort {
+		Normal = 0,
+		Low = 1,
+		High = 2
+	}
+
+	public enum WhiteBalance : ushort {
+		Auto = 0,
+	        Manual = 1
+	}
+
+	public enum Sharpness : ushort {
+		Normal = 0,
+		Soft = 1,
+		Hard = 2
+	}
+
+	public enum LightSource {
+		Unknown = 0,
+		Daylight = 1,
+		Fluorescent = 2,
+		Tungsten = 3,
+		Fash = 4,
+		FineWeather = 9,
+		CloudyWeather = 10,
+		Shade = 11,
+		DaylightFluorescent = 12,
+		DaylightWhiteFluorescent = 13,
+		CoolWhiteFluorescent = 14,
+		WhiteFluorescent = 15,
+		StandardLightA = 17,
+		StandardLightB = 18,
+		StandardLightC = 19,
+		D55 = 20,
+		D65 = 21,
+		D75 = 22,
+		D50 = 23,
+		ISOStudioTungsten = 24,
+		OtherSource = 255
+	}
+
+	public enum ColorSpace {
+		StandardRGB = 1,  // sRGB
+		AdobeRGB = 2,
+		Uncalibrated = 0xffff
+	}
+
+	public enum ComponentsConfiguration {
+		DoesNotExist = 0,
+		Y = 1,
+		Cb = 2,
+		Cr = 3,
+		R = 4,
+		G = 6,
+	}
+
+	public enum ResolutionUnit : ushort {
+	        Uncalibrated = 1,
+		Inch = 2,
+		Centimeter = 3
+	}
+	
+	public enum SensingMethod : short {
+		NotDefined = 1,
+		OneChipColorAreaSensor = 2,
+		TwoChipColorAreaSensor = 3,
+		ThreeChipColorAreaSensor = 4,
+		ColorSequentialAreaSensor = 5,
+		TrilinearSensor = 7,
+		ColorSequentialLinearSensor = 8
+	}
+
+	[System.Flags]
+	public enum NewSubfileType : uint {
+		ReducedResolution = 1,
+		PageOfMultipage= 1 << 1,
+		TransparencyMask = 1 << 2
+	}
+
+	public enum EntryType {
+		Byte = 1,
+		Ascii,
+		Short,
+		Long,
+		Rational,
+		SByte,
+		Undefined,
+		SShort,
+		SLong,
+		SRational,
+		Float,
+		Double,
+		Ifd // TIFF-EP - TIFF PageMaker TechnicalNote 2
+	}
+	
+	public class Tag {
+		public ushort Id;
+		public EntryType Type;
+		public int Count;
+		public string Name;
+		public string Description;
+	}
+
+	public class CanonTag : Tag {
+		// http://www.gvsoft.homedns.org/exif/makernote-canon.html
+		
+		public enum CanonId {
+			Unknown1           = 0x0000,
+			CameraSettings1    = 0x0001,
+			Unknown2           = 0x0003,
+			CameraSettings2    = 0x0004,
+			ImageType          = 0x0006,
+			FirmwareVersion    = 0x0007,
+			ImageNumber        = 0x0008,
+			OwnerName          = 0x0009,
+			Unknown3           = 0x000a,
+			CameraSerialNumber = 0x000c,
+			Unknown4           = 0x000d,
+			CustomFunctions    = 0x000f
+		}
+		
+		public CanonTag (CanonId id, EntryType type, int count, string name, string description)
+		{
+			this.Id = (ushort)id;
+			this.Type = type;
+			this.Count = count;
+			this.Name = name;
+			this.Description = description;
+		}
+
+		public static System.Collections.Hashtable Tags;
+
+		static CanonTag () {
+			CanonTag [] tags = { 
+				new CanonTag (CanonId.Unknown1, EntryType.Short, 6, null, null),
+				new CanonTag (CanonId.CameraSettings1, EntryType.Short, -1, "Camera Settings 1", "First Canon MakerNote settings section"),
+				new CanonTag (CanonId.Unknown2, EntryType.Short, 4, null, null),				
+				new CanonTag (CanonId.CameraSettings2, EntryType.Short, -1, "Camera Settings 2", "Second Canon MakerNote settings section"),
+				new CanonTag (CanonId.ImageType, EntryType.Ascii, 32, "Image Type", null), // FIXME description
+				new CanonTag (CanonId.FirmwareVersion, EntryType.Ascii, 24, "Firmware Version", "Version of the firmware installed on the camera"),
+				new CanonTag (CanonId.ImageNumber, EntryType.Long, 1, "Image Number", null), // FIXME description
+				new CanonTag (CanonId.OwnerName, EntryType.Long, 32, "Owner Name", "Name of the Camera Owner"), // FIXME description
+				new CanonTag (CanonId.Unknown4, EntryType.Short, -1, null, null),				
+				new CanonTag (CanonId.CameraSerialNumber, EntryType.Short, 1, "Serial Number", null), //FIXME description
+				new CanonTag (CanonId.Unknown4, EntryType.Short, -1, null, null),				
+				new CanonTag (CanonId.CustomFunctions, EntryType.Short, -1, "Custom Functions", "Camera Custom Functions")
+			};
+					 
+			foreach (CanonTag tag in tags)
+				Tags [tag.Id] = tag;
+		}
+
+	}
+	
+	public enum Endian {
+		Big,
+		Little
+	}
+
+	public class ParseException : System.Exception 
+	{
+		public ParseException (string msg) : base (msg)
+		{
+		}
+	}
+
+	public class ShortReadException : ParseException 
+	{
+		public ShortReadException () : base ("Short Read")
+		{
+		}
+	}
+
+	public class Converter {
+		public static uint ReadUInt (System.IO.Stream stream, Endian endian)
+		{
+			byte [] tmp = new byte [4];
+
+		        if (stream.Read (tmp, 0, tmp.Length) < 4) {
+#if DEBUG_LOADER
+				System.Console.WriteLine ("short read XXXXXXXXXXXXXXXXXXXXXXx");
+#endif
+				throw new ShortReadException ();
+			}
+			return BitConverter.ToUInt32 (tmp, 0, endian == Endian.Little);
+		}
+
+		public static ushort ReadUShort (System.IO.Stream stream, Endian endian)
+		{
+			byte [] tmp = new byte [2];
+
+		        if (stream.Read (tmp, 0, tmp.Length) < 2) {
+#if DEBUG_LOADER
+				System.Console.WriteLine ("Short read");
+#endif
+				throw new ShortReadException ();
+			}
+
+			return BitConverter.ToUInt16 (tmp, 0, endian == Endian.Little);
+		}
+	}
+
+	public class Header : SemWeb.StatementSource {
+		public Endian endian;
+
+		private uint directory_offset;
+		public ImageDirectory Directory;
+
+                // false seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+
+		public Header (System.IO.Stream stream)
+		{
+			//using (new Timer ("new Tiff.Header")) {
+			byte [] data = new byte [8];
+			stream.Read (data, 0, data.Length);
+			if (data [0] == 'M' && data [1] == 'M')
+				endian = Endian.Big;
+			else if (data [0] == 'I' && data [1] == 'I')
+				endian = Endian.Little;
+
+			ushort marker = BitConverter.ToUInt16 (data, 2, endian == Endian.Little);
+			switch (marker) {
+			case 42:
+				//System.Console.WriteLine ("Found Standard Tiff Marker {0}", marker);
+				break;
+			case 0x4f52:
+				System.Console.WriteLine ("Found Olympus Tiff Marker {0}", marker.ToString ("x"));
+				break;
+			case 0x4e31:
+				System.Console.WriteLine ("Found Navy Interchange File Format Tiff Marker {0}", marker.ToString ("x")); 
+				break;
+			default:
+				System.Console.WriteLine ("Found Unknown Tiff Marker {0}", marker.ToString ("x"));
+				break;
+			}
+
+			//System.Console.WriteLine ("Converting Something");
+			directory_offset = BitConverter.ToUInt32 (data, 4, endian == Endian.Little);
+			
+			if (directory_offset < 8)
+				throw new ParseException ("Invalid IFD0 Offset [" + directory_offset.ToString () + "]"); 
+			
+#if DEBUG_LOADER
+			System.Console.WriteLine ("Reading First IFD");
+#endif
+			Directory = new ImageDirectory (stream, directory_offset, endian); 
+			//}
+		}
+		
+		
+		public void Select (SemWeb.StatementSink sink)
+		{
+			//using (new Timer ("Tiff.Header.Select")) {
+				SelectDirectory (Directory, sink);
+			//}
+		}
+
+		public void SelectDirectory (ImageDirectory dir, StatementSink sink)
+		{
+			foreach (DirectoryEntry e in dir.Entries) {
+#if DEBUG_LOADER
+				System.Console.WriteLine ("{0}", e.Id);
+#endif
+				switch (e.Id) {
+				case TagId.IPTCNAA:
+					System.IO.Stream iptcstream = new System.IO.MemoryStream (e.RawData);
+					FSpot.Iptc.IptcFile iptc = new FSpot.Iptc.IptcFile (iptcstream);
+					iptc.Select (sink);
+					break;
+				case TagId.PhotoshopPrivate:
+					System.IO.Stream bimstream = new System.IO.MemoryStream (e.RawData);
+					FSpot.Bim.BimFile bim = new FSpot.Bim.BimFile (bimstream);
+					bim.Select (sink);
+					break;
+				case TagId.XMP:
+					System.IO.Stream xmpstream = new System.IO.MemoryStream (e.RawData);
+					FSpot.Xmp.XmpFile xmp = new FSpot.Xmp.XmpFile (xmpstream);
+					xmp.Select (sink);
+					break;
+				case TagId.ImageDescription:
+					MetadataStore.AddLiteral (sink, "dc:description", "rdf:Alt", 
+								  new SemWeb.Literal (e.ValueAsString [0], "x-default", null));
+					break;
+				case TagId.UserComment:
+					MetadataStore.AddLiteral (sink, "exif:UserComment", "rdf:Alt", 
+								  new SemWeb.Literal (e.ValueAsString [0], "x-default", null));
+					break;
+				case TagId.Copyright:
+					MetadataStore.AddLiteral (sink, "dc:rights", "rdf:Alt", 
+								  new SemWeb.Literal (e.ValueAsString [0], "x-default", null));
+					break;
+				case TagId.Artist:
+					MetadataStore.Add (sink, "dc:creator", "rdf:Seq", e.ValueAsString);
+					break;
+				case TagId.ExifIfdPointer:
+					try {
+						ImageDirectory sub = ((SubdirectoryEntry)e).Directory [0];
+						SelectDirectory (sub, sink);
+					} catch (System.Exception exc) {
+						System.Console.WriteLine (exc);
+					}
+					break;
+				case TagId.Software:
+					MetadataStore.AddLiteral (sink, "xmp:CreatorTool", e.ValueAsString [0]);
+					break;
+				case TagId.DateTime:
+					try {
+
+					MetadataStore.AddLiteral (sink, "xmp:ModifyDate", 
+								  e.ValueAsDate.ToString ("yyyy-MM-ddThh:mm:ss"));
+					} catch (System.Exception ex) {
+						System.Console.WriteLine (String.Format ("error parsing {0}{2}{1}", e.ValueAsString[0], ex, Environment.NewLine));
+					}
+
+					break;
+				case TagId.DateTimeOriginal:
+				case TagId.DateTimeDigitized:
+					// FIXME subsectime needs to be included in these values
+					// FIXME shouldn't DateTimeOriginal be xmp:CreateDate? the spec says no but wtf?
+					try {
+						MetadataStore.AddLiteral (sink, "exif:" + e.Id.ToString (), 
+									  e.ValueAsDate.ToString ("yyyy-MM-ddThh:mm:ss"));
+					} catch (System.Exception ex) {
+						System.Console.WriteLine (String.Format ("error parsing {0}{2}{1}", e.ValueAsString[0], ex, Environment.NewLine));
+					}
+					break;
+					//case TagId.SpatialFrequencyResponse
+				case TagId.ExifCFAPattern:
+					CFAPattern pattern = new CFAPattern (e.RawData, e.IsLittle);
+					Entity empty = new BNode ();
+					Statement top = new Statement (MetadataStore.FSpotXMPBase, 
+								       (Entity)MetadataStore.Namespaces.Resolve ("exif:" + e.Id.ToString ()),
+								       empty);
+					
+					Statement cols = new Statement (empty, 
+									(Entity) MetadataStore.Namespaces.Resolve ("exif:Columns"),
+									new SemWeb.Literal (pattern.Columns.ToString (), null, null));
+					sink.Add (cols);
+					Statement rows = new Statement (empty, 
+									(Entity) MetadataStore.Namespaces.Resolve ("exif:Rows"),
+									new SemWeb.Literal (pattern.Rows.ToString (), null, null));
+					sink.Add (rows);
+					string [] vals = e.ArrayToString (pattern.Values);
+					MetadataStore.Add (sink, empty, "exif:Values", "rdf:Seq", vals);
+					sink.Add (top);
+					break;
+				case TagId.ExifVersion:
+				case TagId.FlashPixVersion:
+				case TagId.ColorSpace:
+				case TagId.CompressedBitsPerPixel:
+				case TagId.PixelYDimension:
+				case TagId.PixelXDimension:
+				case TagId.RelatedSoundFile:
+				case TagId.ExposureTime:
+				case TagId.FNumber:
+				case TagId.ExposureProgram:
+				case TagId.SpectralSensitivity:
+				case TagId.ShutterSpeedValue:
+				case TagId.ApertureValue:
+				case TagId.BrightnessValue:
+				case TagId.ExposureBiasValue:
+				case TagId.MaxApertureValue:
+				case TagId.SubjectDistance:
+				case TagId.MeteringMode:
+				case TagId.LightSource:
+				case TagId.FocalLength:
+				case TagId.FlashEnergy:
+				case TagId.FocalPlaneXResolution:
+				case TagId.FocalPlaneYResolution:
+				case TagId.FocalPlaneResolutionUnit:
+				case TagId.ExposureIndex:
+				case TagId.SensingMethod:
+				case TagId.FileSource:
+				case TagId.SceneType:
+				case TagId.CustomRendered:
+				case TagId.ExposureMode:
+				case TagId.WhiteBalance:
+				case TagId.DigitalZoomRatio:
+				case TagId.FocalLengthIn35mmFilm:
+				case TagId.SceneCaptureType:
+				case TagId.GainControl:
+				case TagId.Contrast:
+				case TagId.Saturation:
+				case TagId.Sharpness:
+					MetadataStore.AddLiteral (sink, "exif:" + e.Id.ToString (), e.ValueAsString [0]);
+					break;
+				case TagId.ComponentsConfiguration:
+				case TagId.ISOSpeedRatings:
+				case TagId.SubjectArea:
+				case TagId.SubjectLocation:
+					MetadataStore.Add (sink, "exif:" + e.Id.ToString (), "rdf:Seq", e.ValueAsString);
+					break;
+				case TagId.TransferFunction:
+				case TagId.YCbCrSubSampling:
+				case TagId.WhitePoint:
+				case TagId.PrimaryChromaticities:
+				case TagId.YCbCrCoefficients:
+				case TagId.ReferenceBlackWhite:
+				case TagId.BitsPerSample:
+					MetadataStore.Add (sink, "tiff:" + e.Id.ToString (), "rdf:Seq", e.ValueAsString);
+					break;
+				case TagId.Orientation:
+				case TagId.Compression:
+				case TagId.PhotometricInterpretation:					
+				case TagId.SamplesPerPixel:
+				case TagId.PlanarConfiguration:
+				case TagId.YCbCrPositioning:
+				case TagId.ResolutionUnit:
+				case TagId.ImageWidth:
+				case TagId.ImageLength:
+				case TagId.Model:
+				case TagId.Make:
+					try {
+						MetadataStore.AddLiteral (sink, "tiff:" + e.Id.ToString (), e.ValueAsString [0]);
+					} catch (System.Exception ex) {
+						System.Console.WriteLine (String.Format ("error parsing {0}{2}{1}", e.Id, ex, Environment.NewLine));
+					}
+					break;
+				}
+			}
+		}
+
+		public void Save (System.IO.Stream out_stream)
+		{
+			OrderedWriter writer = new OrderedWriter (out_stream, endian == Endian.Little);
+			
+			/* Header */
+			if (endian == Endian.Little) {
+				writer.Write ((byte)'I');
+				writer.Write ((byte)'I');
+			} else {
+				writer.Write ((byte)'M');
+				writer.Write ((byte)'M');
+			}
+			
+			writer.Write ((ushort)42);
+			
+			/* First IFD */
+			Directory.Save (writer, 8);
+		}
+
+		public void Dump (string name)
+		{
+			ImageDirectory ifd = Directory;
+			for (int i = 0; ifd != null; i++) {
+				ifd.Dump (System.String.Format ("IFD[{0}]:", i));
+				ifd = ifd.NextDirectory;
+			}
+		}
+	}
+
+	public class ImageDirectory {
+		protected Endian endian;
+		protected ushort num_entries;
+		protected List<DirectoryEntry> entries;
+		protected uint orig_position;
+
+		protected uint next_directory_offset;
+		ImageDirectory next_directory;
+		
+		protected bool has_header;
+		protected bool has_footer;
+
+		public ImageDirectory (System.IO.Stream stream, uint start_position, Endian endian)
+		{
+			this.endian = endian;
+			orig_position = start_position;
+			Load (stream);
+		}
+		
+		public uint Save (OrderedWriter writer, uint position)
+		{
+			writer.Write (position);
+			writer.Stream.Position = position;
+
+			writer.Write ((ushort)entries.Count);
+
+			position += 2;
+			uint  value_position = (uint) (position + 12 * entries.Count + 4);
+
+			for (int i = 0; i < entries.Count; i++) {
+				writer.Stream.Position = position + (12 * i);
+				value_position = (uint)Entries[i].Save (writer, value_position);
+			}
+							
+			writer.Stream.Position = position + (12 * entries.Count);
+			if (next_directory != null)
+				value_position = next_directory.Save (writer, value_position);
+			else 
+				writer.Write ((uint) 0);
+
+			return value_position;
+		}
+
+		protected void Load (System.IO.Stream stream)
+		{
+			ReadHeader (stream);			
+			ReadEntries (stream);
+			ReadFooter (stream);
+			
+			LoadEntries (stream);
+			LoadNextDirectory (stream);
+		}
+
+		public virtual bool ReadHeader (System.IO.Stream stream)
+		{
+			stream.Seek ((long)orig_position, System.IO.SeekOrigin.Begin);
+			return true;
+		}
+
+		protected virtual void ReadEntries (System.IO.Stream stream) 
+		{
+			num_entries = Converter.ReadUShort (stream, endian);
+#if DEBUG_LOADER
+			System.Console.WriteLine ("reading {0} entries", num_entries);
+#endif			
+			entries = new List<DirectoryEntry> (num_entries);
+			int entry_length = num_entries * 12;
+			byte [] content = new byte [entry_length];
+			
+			if (stream.Read (content, 0, content.Length) < content.Length) {
+#if DEBUG_LOADER
+				System.Console.WriteLine ("short read XXXXXXXXXXXXXXXXXXXXXXx");
+#endif
+				throw new ShortReadException ();
+			}
+
+
+			for (int pos = 0; pos < entry_length; pos += 12) {
+				DirectoryEntry entry = CreateEntry (this, content, pos, this.endian);
+				entries.Add (entry);		
+#if DEBUG_LOADER
+				System.Console.WriteLine ("Added Entry {0} {1} - {2} * {3}", entry.Id.ToString (), entry.Id.ToString ("x"), entry.Type, entry.Count);
+#endif
+				if (entry.Id == TagId.NewSubfileType) {
+					
+				}
+			}
+		}
+
+		protected virtual void ReadFooter (System.IO.Stream stream)
+		{
+			next_directory_offset = Converter.ReadUInt (stream, this.endian);
+		}
+
+		protected void LoadEntries (System.IO.Stream stream)
+		{
+			foreach (DirectoryEntry entry in entries) {
+				entry.LoadExternal (stream);
+			}
+		}
+		
+		protected void LoadNextDirectory (System.IO.Stream stream)
+		{
+#if DEBUG_LOADER
+			System.Console.WriteLine ("start_position = {1} next_directory_offset = {0}",
+						  next_directory_offset, orig_position);
+#endif
+			next_directory = null;
+			try {
+				if (next_directory_offset != 0 && next_directory_offset != orig_position)
+					next_directory = new ImageDirectory (stream, next_directory_offset, this.endian);
+				
+			} catch (System.Exception) {
+				//System.Console.WriteLine ("Error loading directory {0}", e.ToString ());
+				next_directory = null;
+				next_directory_offset = 0;
+			}		
+		}
+
+		public ImageDirectory NextDirectory {
+			get {
+				return next_directory;
+			}
+		}
+
+		public List<DirectoryEntry> Entries {
+			get { 
+				return entries;
+			}
+		}
+
+		public DirectoryEntry Lookup (TagId id) 
+		{
+			foreach (DirectoryEntry entry in entries)
+				if (entry.Id == id)
+					return entry;
+			
+
+			return null;
+		}
+
+		private DirectoryEntry GetEntry (int i)
+		{
+			if (i < Entries.Count)
+				return Entries [i];
+			else
+				return null;
+		}
+
+		public DirectoryEntry Lookup (uint id) 
+		{
+			foreach (DirectoryEntry entry in entries)
+				if ((uint)entry.Id == id)
+					return entry;
+
+			return null;
+		}
+
+		public static DirectoryEntry CreateEntry (ImageDirectory parent, byte [] input, int start, Endian header_endian)
+		{
+			TagId tagid;
+			EntryType type;
+
+			DirectoryEntry.ParseHeader (input, start, out tagid, out type, header_endian);
+			//ConstructorFunc ctor = ctors[tagid];			
+			//if (ctor == null) {
+			//	return ctor (input, header_endian);				
+			//}
+			
+			switch (tagid) {
+			case TagId.ExifIfdPointer:
+			case TagId.GPSInfoIfdPointer:
+			case TagId.InteroperabilityIfdPointer:
+			case TagId.SubIFDs:
+				return new SubdirectoryEntry (input, start, header_endian);
+				//case TagId.MakerNote:
+				//return new MakerNoteEntry (input, start, header_endian);
+				//case TagId.PimIfdPointer:
+				//return new 
+				//case TagId.MakerNote:
+				//return new MakerNoteEntry (input, start, header_endian);
+			}
+			
+			switch (type) {
+			case EntryType.Ifd:
+				//System.Console.WriteLine ("Trying to load {0} {1}", tagid, tagid.ToString ("x"));
+				return new SubdirectoryEntry (input, start, header_endian);
+			case EntryType.Byte:
+				return new ByteEntry (input, start, header_endian);
+			case EntryType.Long:
+				return new LongEntry (input, start, header_endian);
+			case EntryType.Short:
+				return new ShortEntry (input, start, header_endian);
+			}
+
+			return new DirectoryEntry (input, start, header_endian);
+		}
+		
+#if false
+		public Cms.Profile GetProfile ()
+		{
+			Cms.ColorCIExyY whitepoint = new Cms.ColorCIExyY (0, 0, 0);
+			Cms.ColorCIExyYTriple primaries = new Cms.ColorCIExyYTriple (whitepoint, whitepoint, whitepoint);
+			Cms.GammaTable [] transfer = null;
+			int bits_per_sample = 8;
+			double gamma = 2.2;
+			
+			foreach (DirectoryEntry e in entries) {
+				switch (e.Id) {
+				case TagId.InterColorProfile:
+					try {
+						return new Cms.Profile (e.RawData);
+					} catch (System.Exception ex) {
+						System.Console.WriteLine (ex);
+					}
+					break;
+				case TagId.ColorSpace:
+					switch ((ColorSpace)e.ValueAsLong [0]) {
+					case ColorSpace.StandardRGB:
+						return Cms.Profile.CreateStandardRgb ();
+					case ColorSpace.AdobeRGB:
+						return Cms.Profile.CreateAlternateRgb ();
+					case ColorSpace.Uncalibrated:
+						System.Console.WriteLine ("Uncalibrated colorspace");
+						break;
+					}
+					break;
+				case TagId.WhitePoint:
+					Rational [] white = e.RationalValue;
+					whitepoint.x = white [0].Value;
+					whitepoint.y = white [1].Value;
+					whitepoint.Y = 1.0;
+					break;
+				case TagId.PrimaryChromaticities:
+					Rational [] colors = e.RationalValue;
+					primaries.Red.x = colors [0].Value;
+					primaries.Red.y = colors [1].Value;
+					primaries.Red.Y = 1.0;
+
+					primaries.Green.x = colors [2].Value;
+					primaries.Green.y = colors [3].Value;
+					primaries.Green.Y = 1.0;
+
+					primaries.Blue.x = colors [4].Value;
+					primaries.Blue.y = colors [5].Value;
+					primaries.Blue.Y = 1.0;
+					break;
+				case TagId.TransferFunction:
+					ushort [] trns = e.ShortValue;
+					ushort gamma_count = (ushort) (1 << bits_per_sample);
+					Cms.GammaTable [] tables = new Cms.GammaTable [3];
+					System.Console.WriteLine ("Parsing transfer function: count = {0}", trns.Length);
+
+					// FIXME we should use the TransferRange here
+					// FIXME we should use bits per sample here
+					for (int c = 0; c < 3; c++) {
+						tables [c] = new Cms.GammaTable (trns, c * gamma_count, gamma_count);
+					}
+
+					transfer = tables;
+					break;
+				case TagId.ExifIfdPointer:
+					SubdirectoryEntry exif = (SubdirectoryEntry) e;
+					DirectoryEntry ee = exif.Directory [0].Lookup ((int)TagId.Gamma);
+					
+					if (ee == null)
+						break;
+
+					Rational rgamma = ee.RationalValue [0];
+					gamma = rgamma.Value;
+					break;
+				}
+			}
+
+			if (transfer == null) {
+				Cms.GammaTable basic = new Cms.GammaTable (1 << bits_per_sample, gamma);
+				transfer = new Cms.GammaTable [] { basic, basic, basic };
+			}
+
+			// if we didn't get a white point or primaries, give up
+			if (whitepoint.Y != 1.0 || primaries.Red.Y != 1.0)
+				return null;
+				
+			return new Cms.Profile (whitepoint, primaries, transfer);
+		}
+#endif
+
+		public void Dump (string name) 
+		{
+			System.Console.WriteLine ("Starting {0}", name);
+			foreach (DirectoryEntry e in this.Entries)
+				e.Dump (name);
+			System.Console.WriteLine ("Ending {0}", name);
+		}
+		
+		public string Dump2 ()
+		{
+			System.Text.StringBuilder builder = new System.Text.StringBuilder ();
+			builder.Append ("Dummping IFD");
+			foreach (DirectoryEntry entry in entries) {
+				builder.Append (entry.ToString ()+ Environment.NewLine);
+
+				if (entry is SubdirectoryEntry)
+					builder.Append ("Found SUBDIRECTORYENTRY" + Environment.NewLine);
+			}
+			
+			if (next_directory != null) {
+				builder.Append ("Dummping Next IFD");
+				builder.Append (next_directory.Dump2 ());
+			}
+
+			return builder.ToString ();
+		}
+	}
+	
+	public class MakerNoteEntry : SubdirectoryEntry {
+		public MakerNoteEntry (byte [] data, int offset, Endian endian) : base (data, offset, endian)
+		{
+		
+		}
+
+		public override uint GetEntryCount ()
+		{
+			return 1;
+		}
+
+		public override void LoadExternal (System.IO.Stream stream)
+		{
+		}
+	}
+
+
+	public class SubdirectoryEntry : DirectoryEntry {
+		public uint directory_offset;
+		public ImageDirectory [] Directory;
+		
+		public SubdirectoryEntry (byte [] data, int offset, Endian endian) : base (data, offset, endian)
+		{
+			if (this.GetEntryCount () > 1) {
+				System.Console.WriteLine ("Count is greater than 1 ({1}) on Subdirectory {0} interesting", tagid, count);
+			}
+		}
+
+		public override uint Save (OrderedWriter writer, uint position)
+		{
+#if DEBUG_LOADER			
+			Console.WriteLine ("writing entry {0} {1} {2} - value offset = {3}", Id, Type, Count, position);
+#endif
+
+			writer.Write ((ushort)Id);
+			writer.Write ((ushort)Type);
+			writer.Write ((uint)Count);
+
+
+			if (Directory.Length == 1) {
+				writer.Write ((uint)position);
+				position = Directory [0].Save (writer, position);
+			} else if (Directory.Length > 1) {
+				writer.Write ((uint)position);
+				uint value_position = (uint) (position + Directory.Length * 4);
+				for (int i = 0; i < Directory.Length; i++) {
+					writer.Stream.Position = position + i * 4;
+					value_position = Directory [i].Save (writer, value_position);
+				}
+				return value_position;
+			} else
+				writer.Write ((uint) 0);
+
+			return position;
+		}
+
+		public virtual uint GetEntryCount ()
+		{
+			return count;
+		}
+
+		public override void LoadExternal (System.IO.Stream stream)
+		{
+			uint entry_count = GetEntryCount ();
+			Directory = new ImageDirectory [entry_count];
+
+			base.LoadExternal (stream);
+
+			for (int i = 0; i <  entry_count; i++) {
+				try {
+					directory_offset = BitConverter.ToUInt32 (raw_data, i * 4, endian == Endian.Little);
+					if (directory_offset == offset_origin + 8)
+						throw new Exception ("recursive ifd");
+					Directory [i] = new ImageDirectory (stream, directory_offset, endian);
+				} catch (System.Exception e) {
+					System.Console.WriteLine ("Error loading Subdirectory {0} at {2} of {3}bytes:{4}{1}", 
+								  this.Id, e, directory_offset, stream.Length, Environment.NewLine);
+				}
+					
+			}
+		}
+
+		public override void Dump (string name)
+		{
+			for (int i = 0; i < GetEntryCount (); i++) {
+				string subdirname = System.String.Format ("{0}{1}[{2}]({3})]", name, tagid, i, directory_offset);
+
+				try {
+					if (Directory [i] != null)
+						Directory [i].Dump (subdirname);
+				} catch (System.Exception e) {
+					System.Console.WriteLine (e);
+				}
+			}
+		}
+	}
+	
+	public class ShortEntry : DirectoryEntry {
+		public ShortEntry (byte [] data, int offset, Endian endian) : base (data, offset, endian)
+		{
+		}
+	}
+	
+	public class LongEntry : DirectoryEntry {
+		public LongEntry (byte [] data, int offset, Endian endian) : base (data, offset, endian)
+		{
+			if (type != EntryType.Long)
+				throw new ParseException (System.String.Format ("Invalid Settings At Birth {0}", tagid));
+		}
+	}
+
+	public class ByteEntry : DirectoryEntry {
+		public ByteEntry (byte [] data, int offset, Endian endian) : base (data, offset, endian)
+		{
+			if (type != EntryType.Byte)
+				throw new ParseException ("Invalid Settings At Birth");
+		}
+	}
+	
+#if false
+	public class ImageLoader {
+		int width;
+		int length;
+		int [] bps;
+		PhotometricInterpretation interpretation;
+		Compression compression;
+		uint [] offsets;
+		uint [] strip_byte_counts;
+		uint rows_per_strip;
+		byte [] strip;
+
+		public ImageLoader (ImageDirectory directory) 
+		{
+			width = directory.Lookup (TagId.ImageWidth).ValueAsLong [0];
+			length = directory.Lookup (TagId.ImageLength).ValueAsLong [0];
+			bps = directory.Lookup (TagId.BitsPerSample).ValueAsLong;
+			
+			compression = (Compression) directory.Lookup (TagId.Compression).ValueAsLong [0];
+			interpretation = (PhotometricInterpretation) directory.Lookup (TagId.PhotometricInterpretation).ValueAsLong [0];
+			
+			offsets = directory.Lookup (TagId.StripOffsets).ValueAsLong;
+			strip_byte_counts = directory.Lookup (TagId.StripByteCounts).ValueAsLong;
+			rows_per_strip = directory.Lookup (TagId.RowsPerStrip).ValueAsLong [0];
+
+			if (interpretation != 
+		}
+
+
+#if false
+		public Gdk.Pixbuf LoadPixbuf (System.IO.Stream stream) 
+		{
+			Gdk.Pixbuf dest = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, false, width, height);
+			strip = new byte [strip_byte_counts];
+			int row;
+			for (int i = 0; i < offsets.Length; i++) {
+				strip = new byte [strip_byte_counts [i]];
+				stream.Read (strip, 0, strip.Length);
+				switch (compression) {
+					case Compression.Notice
+
+				}
+			}
+		}
+#endif
+	}
+#endif
+
+	public class DirectoryEntry {
+		protected TagId  tagid;
+		protected EntryType type;
+		protected uint count;
+		protected uint offset_origin;
+		protected uint data_offset;
+
+		protected byte [] raw_data;
+		protected Endian endian;
+
+		public TagId Id {
+			get {
+				return tagid;
+			}
+		}
+
+		public EntryType Type {
+			get {
+				return type;
+			}
+		}
+		
+		public uint Count {
+			get {
+				return count;
+			}
+		}
+
+		public virtual uint Save (OrderedWriter writer, uint position)
+		{
+#if DEBUG_LOADER			
+			Console.WriteLine ("writing entry {0} {1} {2}", Id, Type, Count);
+#endif
+			writer.Write ((ushort)Id);
+			writer.Write ((ushort)Type);
+			writer.Write ((uint)Count);
+			if (Length > 4) {
+				writer.Write ((uint)position);
+				writer.Stream.Position = position;
+				writer.Stream.Write (RawData, 0, RawData.Length);
+				return (uint) (position + RawData.Length);
+			} else {
+				writer.Stream.Write (RawData, 0, RawData.Length);
+				for (int i = 0; i < 4 - RawData.Length; i++)
+					writer.Write ((byte)0);
+			}
+			return position;
+		}
+
+		public void SetOrigin (uint pos)
+		{
+			offset_origin = pos;
+		}
+
+		public uint Position {
+			get {
+				return offset_origin + data_offset;
+			}
+		}
+
+		public uint Length {
+			get { return (uint)(Count * GetTypeSize ()); }
+		}
+
+		public virtual int GetTypeSize ()
+		{
+			return GetTypeSize (type);
+		}
+
+		public static int GetTypeSize (EntryType type)
+		{
+			switch (type) {
+			case EntryType.Byte:
+			case EntryType.SByte:
+			case EntryType.Undefined:
+			case EntryType.Ascii:
+				return 1;
+			case EntryType.Short:
+			case EntryType.SShort:
+				return 2;
+			case EntryType.Long:
+			case EntryType.SLong:
+			case EntryType.Float:
+				return 4;
+			case EntryType.Double:
+			case EntryType.Rational:
+			case EntryType.SRational:
+				return 8;
+			default:
+				return 1;
+			}
+		}
+
+		public bool IsLittle {
+			get {
+				return (endian == Endian.Little);
+			}
+		}
+
+		public static int ParseHeader (byte [] data, int start, out TagId tagid, out EntryType type, Endian endian)
+		{
+			tagid = (TagId) BitConverter.ToUInt16 (data, start, endian == Endian.Little);
+			type = (EntryType) BitConverter.ToUInt16 (data, start + 2, endian == Endian.Little);
+			return 4;
+		}
+		
+		public DirectoryEntry (byte [] data, int start, Endian endian)
+		{
+			this.endian = endian;
+
+			start += ParseHeader (data, start, out this.tagid, out this.type, endian);
+			ParseStream (data, start);
+		}
+
+		public virtual void LoadExternal (System.IO.Stream stream)
+		{
+			if (data_offset != 0) {
+				stream.Seek ((long)Position, System.IO.SeekOrigin.Begin);
+				byte [] data = new byte [count * GetTypeSize ()];
+				if (stream.Read (data, 0, data.Length) < data.Length) {
+#if DEBUG_LOADER
+					System.Console.WriteLine ("Short read");
+#endif
+					throw new ShortReadException ();
+				}
+				raw_data = data;
+			}
+
+#if DEBUG_LOADER
+			switch ((int)this.Id) {
+			case (int)TagId.NewSubfileType:
+				System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new NewSubFileType {0}", (NewSubfileType) this.ValueAsLong [0]);
+				break;
+			case (int)TagId.SubfileType:
+				System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new SubFileType {0}", (SubfileType) this.ValueAsLong [0]);
+				break;
+			case (int)TagId.Compression:
+				//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new Compression {0}", (Compression) this.ValueAsLong [0]);
+				
+				break;
+			case (int)TagId.JPEGProc:
+				//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new JPEGProc {0}", (JPEGProc) this.ValueAsLong [0]);
+				
+				break;
+			case (int)TagId.PhotometricInterpretation:
+				//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new PhotometricInterpretation {0}", (PhotometricInterpretation) this.ValueAsLong [0]);
+				break;
+			case (int)TagId.ImageWidth:
+			case (int)TagId.ImageLength:
+				//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX new {1} {0}", this.ValueAsLong [0], this.Id);
+				break;
+			case 50648:
+			case 50656:
+			case 50752:
+				System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXX {0}({1}) - {2} {3}", this.Id, this.Id.ToString ("x"), this.type, raw_data.Length);
+				System.Console.WriteLine ("XXXX ", System.Text.Encoding.ASCII.GetString (raw_data));
+				switch (this.type) {
+				case EntryType.Long:
+					foreach (uint val in ((LongEntry)this).Value)
+						System.Console.Write (" {0}", val);
+					break;
+				case EntryType.Short:
+					foreach (ushort val in ((ShortEntry)this).ShortValue)
+						System.Console.Write (" {0}", val);
+					break;
+				case EntryType.Byte:
+					foreach (byte val in this.RawData)
+						System.Console.Write (" {0}", val);
+					break;
+				}
+				System.Console.WriteLine (String.Empty);
+				break;
+			}
+#endif
+		}
+
+		public virtual void Dump (string name)
+		{
+			switch (this.Type) {
+			case EntryType.Short:
+			case EntryType.Long:
+				uint [] vals = this.ValueAsLong;
+				System.Console.Write ("{3}{1}({2}) [{0}] (", vals.Length, this.Id, this.Type, name);
+				for (int i = 0; i < System.Math.Min (15, vals.Length); i++) {
+					System.Console.Write (" {0}", vals [i]);
+				}
+				System.Console.WriteLine (")");
+				break;
+			case EntryType.Ascii:
+				System.Console.WriteLine ("{3}{1}({2}) (\"{0}\")", this.StringValue, this.Id, this.Type, name);
+				break;
+			default:
+				System.Console.WriteLine ("{3}{1}({2}) [{0}]", this.Count, this.Id, this.Type, name);
+				break;
+			}
+		}
+		
+		protected void ParseStream (byte [] data, int start)
+		{
+			int i = start;
+
+			count = BitConverter.ToUInt32 (data, i, endian == Endian.Little); 
+			i += 4;
+			int size = (int)count * GetTypeSize ();
+			if (size > 4)
+				data_offset = BitConverter.ToUInt32 (data, i, endian == Endian.Little);
+			else {
+				data_offset = 0;
+				raw_data = new byte [size];
+				System.Array.Copy (data, i, raw_data, 0, size);
+			}
+		}
+
+		public void SetData (string value)
+		{
+			int len = System.Text.Encoding.UTF8.GetByteCount (value);
+			byte [] tmp = new byte [len + 1];
+			System.Text.Encoding.UTF8.GetBytes (value, 0, value.Length, tmp, 0);
+			tmp[len] = 0;
+			System.Console.WriteLine ("SetData: value = {0} len = {1}", value, len);
+			SetData (tmp);
+		}
+	
+		public static System.DateTime DateTimeFromString (string dt)
+		{
+			// Exif DateTime strings are formatted as
+			//      "YYYY:MM:DD HH:MM:SS"
+			
+			string delimiters = " :";
+			string[] dt_data = dt.Split ( delimiters.ToCharArray(), 6 );
+			System.DateTime result;
+			result = new System.DateTime (System.Int32.Parse(dt_data[0]), 
+						      System.Int32.Parse(dt_data[1]), 
+						      System.Int32.Parse(dt_data[2]),
+						      System.Int32.Parse(dt_data[3]), 
+						      System.Int32.Parse(dt_data[4]), 
+						      System.Int32.Parse(dt_data[5]));
+			
+			return result;
+		}
+		
+		public void SetData (byte [] data)
+		{
+			raw_data = data;
+			count = (uint)raw_data.Length / (uint)GetTypeSize ();
+		}
+
+#if false		
+		public object  GetValue () {
+			switch (Type) {
+			case EntryType.Short:
+				return ShortValue;
+			case EntryType.Long:
+				return LongValue;
+			case  EntryType.Rational:
+				return RationalValue;
+			case EntryType.SRational:
+				return SRationalValue;
+			case EntryType.Ascii:
+				return StringValue.Split ('\0');
+				break;
+			default:
+				System.Console.WriteLine ("{1}({2}) [{0}]", this.Count, this.Id, this.Type);
+				break;
+
+				}
+			}
+		}
+#endif
+
+		public byte [] Value {
+			get {
+				return raw_data;
+			}
+		}
+
+		public byte [] RawData {
+			get { 
+				return raw_data;
+			}
+		}
+
+		public string [] ValueAsString {
+			get {
+				switch (this.Type) {
+				case EntryType.Short:
+				case EntryType.Long:
+					return ArrayToString (this.ValueAsLong);
+				case EntryType.Rational:
+					return ArrayToString (this.RationalValue);
+				case EntryType.SRational:
+					return ArrayToString (this.SRationalValue);
+				case EntryType.Undefined:
+					switch (Id) {
+					case TagId.UserComment:
+						return new string [] { UserCommentValue };
+					case TagId.FlashPixVersion:
+					case TagId.ExifVersion:
+						return new string [] { StringValue };
+					case TagId.FileSource:
+					case TagId.SceneType:
+						return ArrayToString (this.RawData);
+					case TagId.ComponentsConfiguration:
+						return ArrayToString (ValueAsLong);
+					default:
+						System.Console.WriteLine ("Cannot convert type \"{0}\" to string", Id);
+						break;
+					}
+					break;
+				case EntryType.Ascii:
+					return StringValue.Split ('\0');
+				}
+				return null;
+			}
+		}
+
+		public string [] ArrayToString (System.Array array)
+		{
+			string [] vals = new string [array.Length];
+			for (int i = 0; i < array.Length; i++)
+				vals [i] = array.GetValue (i).ToString ();
+
+			return vals;
+		}
+
+		public uint [] ValueAsLong {
+			get {
+				uint [] data = new uint [this.Count];
+				for (int i = 0; i < this.Count; i++) {
+					switch (this.Type) {
+					case EntryType.Long:
+						data [i] = BitConverter.ToUInt32 (raw_data, i * GetTypeSize (), endian == Endian.Little);
+						break;
+					case EntryType.Short:
+						data [i] = BitConverter.ToUInt16 (raw_data, i * GetTypeSize (), endian == Endian.Little);
+						break;
+					case EntryType.Undefined:
+					case EntryType.Byte:
+						data [i] = raw_data [i];
+						break;
+					default:
+						throw new ParseException ("Invalid conversion");
+					}
+				}
+				return data;
+			}
+		}
+		
+		// The following methods are usded to convert the data 
+		// to the various type regardless of the entry
+		// type, they are used internally in processing the data
+		// Use at your own risk.
+
+		public string StringValue {
+			get {
+				return System.Text.Encoding.ASCII.GetString (raw_data);
+			}
+		}
+
+		public System.DateTime ValueAsDate {
+			get {
+				return DirectoryEntry.DateTimeFromString (StringValue);
+			}
+		}
+
+		public string UserCommentValue {
+			get {
+				UserComment comment = new UserComment (raw_data, IsLittle);
+				return comment.Value;
+			}
+		}
+
+		public SRational [] SRationalValue {
+			get {
+				Rational [] vals = RationalValue;
+				SRational [] data = new SRational [vals.Length];
+				
+				for (int i = 0; i < vals.Length; i++)
+					data [i] = SRational.BitwiseCopy (vals [i]);
+
+				return data;
+			}
+		}
+
+		public Rational [] RationalValue {
+			get {
+				uint [] vals = LongValue;
+				Rational [] data = new Rational [vals.Length / 2];
+
+				for (int i = 0; i < vals.Length; i += 2)
+					data [i/2] = new Rational (vals [i], vals [i + 1]);
+				
+				return data;
+			}
+			
+		}
+
+		public uint [] LongValue {
+			get {
+				uint [] data = new uint [raw_data.Length / 4];
+				for (int i = 0; i < raw_data.Length; i+= 4)
+					data [i/4] = BitConverter.ToUInt32 (raw_data, i, endian == Endian.Little);
+
+				return data;
+			}
+		}
+
+		public ushort [] ShortValue {
+			get {
+				ushort [] data = new ushort [raw_data.Length];
+				for (int i = 0; i < raw_data.Length; i+= 2) {
+					data [i] = BitConverter.ToUInt16 (raw_data, i, endian == Endian.Little);
+				}
+				return data;
+			}
+		}
+	}
+
+
+	public class TiffFile : ImageFile, SemWeb.StatementSource {
+		public Header Header;
+
+                // false seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+
+		public TiffFile (string path) : base (path)
+		{
+			try {
+				using (System.IO.Stream input = Open ()) {
+					this.Header = new Header (input);
+				}
+
+#if DEBUG_LOADER
+				Header.Dump (this.ToString () + ":");
+#endif
+			} catch (System.Exception e) {
+				System.Console.WriteLine (e.ToString ());
+			}
+		}
+
+		public TiffFile (Uri uri) : base (uri)
+		{
+			try {
+				using (System.IO.Stream input = Open ()) {
+					this.Header = new Header (input);
+				}
+
+#if DEBUG_LOADER
+				Header.Dump (this.ToString () + ":");
+#endif
+			} catch (System.Exception e) {
+				System.Console.WriteLine (e.ToString ());
+			}
+		}
+
+		public virtual void Select (SemWeb.StatementSink sink)
+		{
+			Header.SelectDirectory (Header.Directory, sink);
+		}
+
+		public override System.DateTime Date {
+			get {
+				SubdirectoryEntry sub = (SubdirectoryEntry) this.Header.Directory.Lookup (TagId.ExifIfdPointer);
+				DirectoryEntry e;
+
+				if (sub != null) {
+					e = sub.Directory [0].Lookup (TagId.DateTimeOriginal);
+					
+					if (e != null)
+						return DirectoryEntry.DateTimeFromString (e.StringValue).ToUniversalTime ();
+				}
+
+				e = this.Header.Directory.Lookup (TagId.DateTime);
+
+				if (e != null)
+					return DirectoryEntry.DateTimeFromString (e.StringValue).ToUniversalTime ();
+				else
+					return base.Date;
+			}
+		}
+		
+		public override System.IO.Stream PixbufStream ()
+		{
+			return Open ();
+		}
+
+		public override PixbufOrientation GetOrientation ()
+		{
+			ShortEntry e = (ShortEntry)(this.Header.Directory.Lookup (TagId.Orientation));
+			if (e != null) 
+				return (PixbufOrientation)(e.ShortValue[0]);
+			else
+				return PixbufOrientation.TopLeft;
+		}
+
+		public System.IO.Stream LookupJpegSubstream (ImageDirectory directory)
+		{
+			uint offset = directory.Lookup (TagId.JPEGInterchangeFormat).ValueAsLong [0];
+			
+			System.IO.Stream file = Open ();
+			file.Position = offset;
+			return file;
+		}
+
+#if false
+		public Gdk.Pixbuf LoadJpegInterchangeFormat (ImageDirectory directory)
+		{
+			uint offset = directory.Lookup (TagId.JPEGInterchangeFormat).ValueAsLong [0];
+			uint length = directory.Lookup (TagId.JPEGInterchangeFormatLength).ValueAsLong [0];
+			   
+			using (System.IO.Stream file = Open ()) {
+				file.Position = offset;
+				
+				byte [] data = new byte [32768];
+				int read;
+
+				Gdk.PixbufLoader loader = new Gdk.PixbufLoader ();
+				
+				while (length > 0) {
+					read = file.Read (data, 0, (int)System.Math.Min ((int)data.Length, length));
+					if (read <= 0)
+						break;
+
+					loader.Write (data, (ulong)read);
+					length -= (uint) read;
+				}
+				Gdk.Pixbuf result = loader.Pixbuf;
+				loader.Close ();
+				return result; 
+			}
+		}
+#endif
+	}
+
+	public class DngFile : TiffFile {
+		public DngFile (string path) : base (path) 
+		{
+		}
+
+		public DngFile (System.Uri uri) : base (uri) 
+		{
+		}
+
+		public override System.IO.Stream PixbufStream ()
+		{
+			try {
+				SubdirectoryEntry sub = (SubdirectoryEntry) Header.Directory.Lookup (TagId.SubIFDs);
+				ImageDirectory directory = sub.Directory [sub.Directory.Length - 1];
+
+				uint offset = directory.Lookup (TagId.StripOffsets).ValueAsLong [0];
+				System.IO.Stream file = Open ();
+				file.Position = offset;
+				return file;
+			} catch {
+				return DCRawFile.RawPixbufStream (uri);
+			}
+		}
+
+		public override void Select (SemWeb.StatementSink sink)
+		{
+			
+			/* this is just a sanity pass, if the first ifd is not a subfile use the normal
+			 * tiff path 
+			 */
+			DirectoryEntry e = Header.Directory.Lookup (TagId.NewSubfileType);
+			if (e == null) {
+				base.Select (sink);
+				return;
+			}
+
+			/*
+			 * Even though Ifd0 doesn't have the full resolution image
+			 * it would have the XMP data so we look for it
+			 */
+			e = Header.Directory.Lookup (TagId.XMP);
+			if (e != null) {
+				System.IO.Stream xmpstream = new System.IO.MemoryStream (e.RawData);
+				FSpot.Xmp.XmpFile xmp = new FSpot.Xmp.XmpFile (xmpstream);
+				xmp.Select (sink);
+			}
+
+			/* 
+			 * Ifd0 will also have the exif directory
+			 */
+			ImageDirectory dir = Header.Directory;
+			SubdirectoryEntry sub = (SubdirectoryEntry) dir.Lookup (TagId.ExifIfdPointer);
+			if (sub != null)
+				Header.SelectDirectory (sub.Directory [0], sink);
+			
+			/*
+			 * now we lookup subifd0 (we should probably scan the newsubfile types here)
+			 * and load the metadata we are interested in from it.
+			 */
+			sub = (SubdirectoryEntry) Header.Directory.Lookup (TagId.SubIFDs);	
+
+			if (e == null)
+				e = dir.Lookup (TagId.NewSubfileType);
+
+			int i = 0;
+			do {
+				uint dirtype = e.ValueAsLong [0];
+				if (dirtype == 0) {
+					Header.SelectDirectory (dir, sink);
+					break;
+				}
+					
+				if (sub == null)
+					break;
+
+				dir = sub.Directory [i];
+				e = dir.Lookup (TagId.NewSubfileType);
+				i++;
+			} while (i < sub.Directory.Length);
+
+			
+		}
+	}	
+	
+	public class NefFile : TiffFile, IThumbnailContainer {
+		public NefFile (string path) : base (path) 
+		{
+		}
+
+		public NefFile (Uri uri) : base (uri)
+		{
+		}
+
+		public override void Select (SemWeb.StatementSink sink)
+		{
+			DirectoryEntry e = Header.Directory.Lookup (TagId.NewSubfileType);
+
+			if (e == null) {
+				base.Select (sink);
+				return;
+			}
+
+			ImageDirectory dir = Header.Directory;
+			SubdirectoryEntry sub = (SubdirectoryEntry) dir.Lookup (TagId.ExifIfdPointer);
+			
+			if (sub != null)
+				Header.SelectDirectory (sub.Directory [0], sink);
+			
+			sub = (SubdirectoryEntry) Header.Directory.Lookup (TagId.SubIFDs);	
+
+			int i = 0;
+			do {
+				uint dirtype = e.ValueAsLong [0];
+				if (dirtype == 0) {
+					Header.SelectDirectory (dir, sink);
+					break;
+				}
+					
+				if (sub == null)
+					break;
+
+				dir = sub.Directory [i];
+				if (dir != null)
+					e = dir.Lookup (TagId.NewSubfileType);
+				i++;
+			} while (i < sub.Directory.Length);
+		}
+
+#if false
+		public Gdk.Pixbuf GetEmbeddedThumbnail ()
+		{
+			using (System.IO.Stream stream = Open ()) {
+				return TransformAndDispose (new Gdk.Pixbuf (stream));
+			}
+		}
+
+		public override System.IO.Stream PixbufStream ()
+		{
+			try {
+				SubdirectoryEntry sub = (SubdirectoryEntry) Header.Directory.Lookup (TagId.SubIFDs);
+				ImageDirectory jpeg_directory = sub.Directory [0];
+				return LookupJpegSubstream (jpeg_directory);
+			} catch (System.Exception) {
+				return DCRawFile.RawPixbufStream (uri);
+			}
+		}
+#endif
+	}
+		
+
+	public class Cr2File : TiffFile, IThumbnailContainer {
+		public Cr2File (string path) : base (path) 
+		{
+		}
+
+		public Cr2File (Uri uri) : base (uri)
+		{
+		}
+
+		/*
+		public override PixbufOrientation GetOrientation ()
+		{
+			return PixbufOrientation.TopLeft;
+		}
+		*/
+
+#if false
+		public Gdk.Pixbuf GetEmbeddedThumbnail ()
+		{
+			ImageDirectory directory;
+			directory = Header.Directory.NextDirectory;
+			return TransformAndDispose (LoadJpegInterchangeFormat (directory));
+		}
+#endif
+
+		public override System.IO.Stream PixbufStream ()
+		{
+			uint offset = Header.Directory.Lookup (TagId.StripOffsets).ValueAsLong [0];
+			System.IO.Stream file = Open ();
+			file.Position = offset;
+			return file;
+		}
+	}
+
+#if ENABLE_NUNIT
+	[TestFixture]
+	public class Tests {
+		public Tests ()
+		{
+			Gnome.Vfs.Vfs.Initialize ();
+			Gtk.Application.Init ();
+		}
+		
+		[Test]
+		public void Save ()
+		{
+			string desc = "this is an example description";
+			string desc2 = "\x00a9 Novell Inc.";
+			PixbufOrientation orient = PixbufOrientation.TopRight;
+			Gdk.Pixbuf test = new Gdk.Pixbuf (null, "f-spot-32.png");
+			string path = ImageFile.TempPath ("joe.jpg");
+			
+			PixbufUtils.SaveJpeg (test, path, 75, new Exif.ExifData ());
+			JpegFile jimg = new JpegFile (path);
+			jimg.SetDescription (desc);
+			jimg.SetOrientation (orient);
+			jimg.SaveMetaData (path);
+			JpegFile mod = new JpegFile (path);
+			Assert.AreEqual (mod.Orientation, orient);
+			Assert.AreEqual (mod.Description, desc);
+			jimg.SetDescription (desc2);
+			jimg.SaveMetaData (path);
+			mod = new JpegFile (path);
+			Assert.AreEqual (mod.Description, desc2);
+			
+			Header header = mod.ExifHeader;
+#if USE_TEST_FILE
+			string tmp = "/home/lewing/test.tiff";
+			if (File.Exists (tmp))
+				File.Delete (tmp);
+			Stream stream = File.Open (tmp, FileMode.Create, FileAccess.ReadWrite);
+			Console.WriteLine ("XXXX saving tiff {0}", tmp);
+#else
+			System.IO.MemoryStream stream = new System.IO.MemoryStream ();
+#endif
+
+			header.Dump ("source");
+			header.Save (stream);
+			stream.Position = 0;
+			System.Console.WriteLine ("----------------------------------------------LOADING TIFF");
+			Header loader = new Header (stream);
+			loader.Dump ("loader");
+			
+			CompareDirectories (header.Directory, loader.Directory);
+
+			System.IO.File.Delete (path);	
+		}
+
+		private void CompareDirectories (ImageDirectory olddir, ImageDirectory newdir)
+		{
+			Assert.AreEqual (olddir.Entries.Count, newdir.Entries.Count);
+			for (int i = 0; i < olddir.Entries.Count; i++) {
+				Assert.AreEqual (olddir.Entries [i].Id, newdir.Entries [i].Id);
+				Assert.AreEqual (olddir.Entries [i].Type, newdir.Entries [i].Type);
+				Assert.AreEqual (olddir.Entries [i].Count, newdir.Entries [i].Count);
+				Assert.AreEqual (olddir.Entries [i].Length, newdir.Entries [i].Length);
+				if (olddir.Entries [i] is SubdirectoryEntry) {
+					SubdirectoryEntry oldsub = olddir.Entries [i] as SubdirectoryEntry;
+					SubdirectoryEntry newsub = newdir.Entries [i] as SubdirectoryEntry;
+					
+					for (int j = 0; j < oldsub.Directory.Length; j++)
+						CompareDirectories (oldsub.Directory [j], newsub.Directory [j]);
+				}
+			}
+		}
+	}
+#endif
+}
+

Added: trunk/beagle/Util/F-Spot/Imaging/X3fFile.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/X3fFile.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,112 @@
+using System;
+using System.IO;
+using FSpot;
+using SemWeb;
+
+namespace FSpot.X3f {
+	internal class Info {
+		ushort major_version;
+		ushort minor_version;
+		byte [] uid;
+		uint mark_bits;
+		uint cols;
+		uint rows;
+		uint rotation;
+
+		public PixbufOrientation Orientation {
+			get {
+				switch (rotation) {
+				case 0:
+					return PixbufOrientation.TopLeft;
+				case 270:
+					return PixbufOrientation.LeftBottom;
+				case 180:
+					return PixbufOrientation.BottomRight;
+				case 90:
+					return PixbufOrientation.RightTop;
+				default:
+					return PixbufOrientation.TopLeft;
+				}
+			}
+		}
+
+		public uint Width {
+			get { return cols; }
+		}
+		
+		public uint Height {
+			get { return rows; }
+		}
+
+		public Info (Stream stream)
+		{
+			byte [] buffer = new byte [4];
+			stream.Read (buffer, 0, buffer.Length);
+			if (System.Text.Encoding.ASCII.GetString (buffer, 0, buffer.Length) != "FOVb")
+				throw new ImageFormatException ("invalid Foveon magic number");
+			
+			stream.Read (buffer, 0, buffer.Length);
+			major_version = BitConverter.ToUInt16 (buffer, 0, true);
+			minor_version = BitConverter.ToUInt16 (buffer, 2, true);
+
+			uid = new byte [16];
+			stream.Read (uid, 0, uid.Length);
+
+			stream.Read (buffer, 0, buffer.Length);
+			mark_bits = BitConverter.ToUInt32 (buffer, 0, true);
+			stream.Read (buffer, 0, buffer.Length);
+			cols = BitConverter.ToUInt32 (buffer, 0, true);
+			stream.Read (buffer, 0, buffer.Length);
+			rows = BitConverter.ToUInt32 (buffer, 0, true);
+			stream.Read (buffer, 0, buffer.Length);
+			rotation = BitConverter.ToUInt32 (buffer, 0, true);
+		}
+
+		public override string ToString ()
+		{
+			return String.Format ("x3file ({0}.{1}, mark_bits={2}) ({3}, {4})",
+					      major_version, minor_version,
+					      mark_bits, Width, Height);
+		}
+
+	}
+
+	public class X3fFile : DCRawFile, SemWeb.StatementSource {
+		Info info;
+
+                // false seems a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+
+		internal Info Info {
+			get {
+				if (info == null)
+					info = new Info (Open ());
+
+				return info;
+			}
+		}
+
+		public X3fFile (string path) : base (path)
+		{
+		}
+
+		public X3fFile (System.Uri uri) : base (uri)
+		{
+		}
+		
+		public void Select (StatementSink sink)
+		{
+			MetadataStore.AddLiteral (sink, "tiff:Orientation", ((int)Info.Orientation).ToString ());
+			MetadataStore.AddLiteral (sink, "tiff:ImageWidth", Info.Width.ToString ());
+			MetadataStore.AddLiteral (sink, "tiff:ImageLength", Info.Height.ToString ());
+		}
+
+		public override PixbufOrientation GetOrientation ()
+		{
+			return Info.Orientation;
+		}
+
+	}
+}

Added: trunk/beagle/Util/F-Spot/Imaging/XmpFile.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/Imaging/XmpFile.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,129 @@
+using System.Xml;
+using System.Collections;
+using SemWeb;
+
+namespace FSpot.Xmp {
+	public class XmpFile : SemWeb.StatementSource, SemWeb.StatementSink
+	{
+		MetadataStore store;
+
+                // false seems like a safe default
+                public bool Distinct {
+                        get { return false; }
+                }
+
+		public MetadataStore Store {
+			get { return store; }
+			set { store = value; }
+		}
+
+		public XmpFile (System.IO.Stream stream) : this ()
+		{
+			Load (stream);
+		}
+		
+		public XmpFile ()
+		{
+			store = new MetadataStore ();
+		}
+		
+		public void Load (System.IO.Stream stream)
+		{
+			try {
+				RdfXmlReader reader = new RdfXmlReader (stream);
+				reader.BaseUri = MetadataStore.FSpotXMPBase;
+				store.Import (reader);
+				//Dump ();
+			} catch (System.Exception e) {
+				System.Console.WriteLine (e.ToString ());
+			}
+		}
+
+		private class XmpWriter : RdfXmlWriter {
+			public XmpWriter (XmlDocument dest) : base (dest)
+			{
+				BaseUri = MetadataStore.FSpotXMPBase;
+			}
+			
+			public override void Add (Statement stmt) 
+			{
+				string predicate = stmt.Predicate.Uri;
+				string prefix;
+				string localname;
+
+				// Fill in the namespaces with nice prefixes
+				if (MetadataStore.Namespaces.Normalize (predicate, out prefix, out localname)) {
+					if (prefix != null)
+						Namespaces.AddNamespace (predicate.Remove (predicate.Length - localname.Length, localname.Length), prefix);
+				}
+				base.Add (stmt);
+			}
+		}
+
+		public void Save (System.IO.Stream stream)
+		{
+			try {
+				XmlTextWriter text;
+				RdfXmlWriter writer;
+                                XmlDocument rdfdoc = new XmlDocument();
+
+                                // first, construct the rdf guts, semweb style
+                                writer = new XmpWriter (rdfdoc);
+				//writer.Namespaces.Parent = MetadataStore.Namespaces;
+				writer.Write (store);
+				writer.Close ();
+			       
+                                // now construct the xmp wrapper packet
+				text = new XmlTextWriter (stream, System.Text.Encoding.UTF8);
+ 				text.Formatting = Formatting.Indented;
+                        
+                                text.WriteProcessingInstruction ("xpacket", "begin=\"\ufeff\" id=\"W5M0MpCehiHzreSzNTczkc9d\"");
+                                text.WriteStartElement ("x:xmpmeta");
+                                text.WriteAttributeString ("xmlns", "x", null, "adobe:ns:meta/");
+
+				((XmlElement)rdfdoc.ChildNodes[1]).RemoveAttribute ("xml:base");
+				rdfdoc.ChildNodes[1].WriteTo (text);
+
+                                // now close off the xmp packet
+                                text.WriteEndElement ();
+                                text.WriteProcessingInstruction ("xpacket", "end=\"r\"");
+				text.Close ();
+				
+			} catch (System.Exception e) {
+				System.Console.WriteLine (e);
+			}
+		}
+
+		public bool Add (Statement stmt)
+		{
+			return ((SemWeb.StatementSink)store).Add (stmt);
+		}
+		
+		public void Select (SemWeb.StatementSink sink)
+		{
+			store.Select (sink);
+		}
+
+		public void Dump ()
+		{
+			foreach (SemWeb.Statement stmt in store) {
+				System.Console.WriteLine(stmt);
+			}
+		}
+
+#if TEST_XMP
+		static void Main (string [] args)
+		{
+			XmpFile xmp = new XmpFile (System.IO.File.OpenRead (args [0]));
+			//xmp.Store.Dump ();
+#if false
+			System.IO.StreamReader stream = new System.IO.StreamReader (System.IO.File.OpenRead (args [0]));
+
+			while (stream.BaseStream.Position < stream.BaseStream.Length) {
+				System.Console.WriteLine (stream.ReadLine ());
+			}
+#endif
+		}
+#endif
+	}
+}

Added: trunk/beagle/Util/F-Spot/MetadataStore.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/MetadataStore.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,361 @@
+using SemWeb;
+using SemWeb.Util;
+using Mono.Unix;
+using FSpot.Xmp;
+
+namespace FSpot {
+        internal class Description {
+		string predicate;
+		string description;
+		string title;
+		ValueFormat formater;
+		
+		static System.Collections.Hashtable table;
+
+		static Description ()
+		{
+			Description [] preset = new Description [] {
+				new Description ("dc:creator", Catalog.GetString ("Creator")),
+				new Description ("dc:title", Catalog.GetString ("Title")),
+				new Description ("dc:rights", Catalog.GetString ("Copyright")),
+				new Description ("dc:subject", Catalog.GetString ("Subject and Keywords")),
+				new Description ("tiff:Compression", Catalog.GetString ("Compression"), 
+						 typeof (FSpot.Tiff.Compression)),
+				new Description ("tiff:PlanarConfiguration", Catalog.GetString ("Planar Configuration"), 
+						 typeof (FSpot.Tiff.PlanarConfiguration)),
+				new Description ("tiff:Orientation", Catalog.GetString ("Orientation"), 
+						 typeof (PixbufOrientation)),
+				new Description ("tiff:PhotometricInterpretation", Catalog.GetString ("Photometric Interpretation"), 
+						 typeof (FSpot.Tiff.PhotometricInterpretation)),
+				new Description ("tiff:ResolutionUnit", Catalog.GetString ("Resolution Unit"),
+						 typeof (FSpot.Tiff.ResolutionUnit)),
+				new Description ("exif:ExposureProgram", Catalog.GetString ("Exposure Program"), 
+						 typeof (FSpot.Tiff.ExposureProgram)),
+				new Description ("exif:MeteringMode", Catalog.GetString ("Metering Mode"), 
+						 typeof (FSpot.Tiff.MeteringMode)),
+				new Description ("exif:ExposureMode", Catalog.GetString ("Exposure Mode"), 
+						 typeof (FSpot.Tiff.ExposureMode)),
+				new Description ("exif:CustomRendered", Catalog.GetString ("Custom Rendered"), 
+						 typeof (FSpot.Tiff.CustomRendered)),
+				new Description ("exif:ComponentsConfiguration", Catalog.GetString ("Components Configuration"),
+						 typeof (FSpot.Tiff.ComponentsConfiguration)),
+				new Description ("exif:LightSource", Catalog.GetString ("Light Source"),
+						 typeof (FSpot.Tiff.LightSource)),
+				new Description ("exif:SensingMethod", Catalog.GetString ("Sensing Method"),
+						 typeof (FSpot.Tiff.SensingMethod)),
+				new Description ("exif:ColorSpace", Catalog.GetString ("Color Space"),
+						 typeof (FSpot.Tiff.ColorSpace)),
+				new Description ("exif:WhiteBalance", Catalog.GetString ("White Balance"),
+						 typeof (FSpot.Tiff.WhiteBalance)),
+				new Description ("exif:FocalPlaneResolutionUnit", Catalog.GetString ("Focal Plane Resolution Unit"),
+						 typeof (FSpot.Tiff.ResolutionUnit)),
+				new Description ("exif:FileSource", Catalog.GetString ("File Source Type"),
+						 typeof (FSpot.Tiff.FileSource)),
+				new Description ("exif:SceneCaptureType", Catalog.GetString ("Scene Capture Type"),
+						 typeof (FSpot.Tiff.SceneCaptureType)),
+				new Description ("exif:GainControl", Catalog.GetString ("Gain Control"),
+						 typeof (FSpot.Tiff.GainControl)),
+				new Description ("exif:Contrast", Catalog.GetString ("Contrast"),
+						 typeof (FSpot.Tiff.Contrast)),
+				new Description ("exif:Saturation", Catalog.GetString ("Saturation"),
+						 typeof (FSpot.Tiff.Saturation)),
+				new Description ("exif:Sharpness", Catalog.GetString ("Sharpness"),
+						 typeof (FSpot.Tiff.Sharpness)),
+				new Description ("exif:SceneType", Catalog.GetString ("Scene Type"),
+						 typeof (FSpot.Tiff.SceneType))
+
+
+
+			};
+			
+			table = new System.Collections.Hashtable ();
+
+			foreach (Description d in preset) {
+				table [MetadataStore.Namespaces.Resolve (d.predicate)] = d;
+			}
+		}
+		
+		public Description (string predicate, string title) : this (predicate, title, null, null) {}
+
+		public Description (string predicate, string title, string description) : this (predicate, title, description, null) {}
+		
+		public Description (string predicate, string title, System.Type type) : this (predicate, title)
+		{
+			formater = new ValueFormat (type);
+		}
+
+		public Description (string predicate, string title, string description, ValueFormat formater)
+		{
+			this.predicate = predicate;
+			this.title = title;
+			this.formater = formater;
+		}
+		
+		public static void GetDescription (MemoryStore store, Statement stmt, out string label, out string value)
+		{
+			string predicate = stmt.Predicate.Uri;
+
+			Description d = (Description) table [predicate];
+
+			label = System.IO.Path.GetFileName (predicate);
+			value = null;
+
+			if (stmt.Object is SemWeb.Literal)
+			        value = ((SemWeb.Literal)(stmt.Object)).Value;
+
+			if (d != null) {
+				label = d.title;
+				if (d.formater != null && stmt.Object is Literal)
+					value = d.formater.GetValue (store, (SemWeb.Literal)stmt.Object);
+
+			} else {
+				Statement sstmt = new Statement (stmt.Predicate,
+								 (Entity)MetadataStore.Namespaces.Resolve ("rdfs:label"),
+								 null);
+				
+				foreach (Statement tstmt in MetadataStore.Descriptions.Select (sstmt))
+					if (tstmt.Object is SemWeb.Literal)
+						label = ((SemWeb.Literal)(tstmt.Object)).Value;
+			}
+			return;
+		}
+	}
+	
+        internal class ValueFormat 
+	{
+		System.Type type;
+		
+		public ValueFormat (System.Type type)
+		{
+			this.type = type;
+		}
+
+		public virtual string GetValue (MemoryStore store, SemWeb.Literal obj)
+		{
+			string result = obj.Value;
+
+			if (type.IsEnum) {
+				try {
+					object o = System.Enum.Parse (type, obj.Value);
+					result = o.ToString ();
+				} catch (System.Exception e) {
+					System.Console.WriteLine ("Value \"{2}\" not found in {0}{3}{1}", type, e, result, System.Environment.NewLine);
+				}
+			}
+			/*
+			else if (type == typeof (Rational)) {
+				object o = FSpot.Tiff.Rational.Parse (obj.Value);
+			} 
+			*/
+			return result;
+		}
+	}
+	
+	public class MetadataStore : MemoryStore
+	{
+		public static NamespaceManager Namespaces;
+		private static MetadataStore descriptions;
+		
+		public const string PhotoshopNS = "http://ns.adobe.com/photoshop/1.0/";;
+		public const string Iptc4xmpCoreNS = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/";;
+		public const string DcNS = "http://purl.org/dc/elements/1.1/";;
+		public const string XmpNS = "http://ns.adobe.com/xap/1.0/";;
+		public const string XmpidqNS = "http://ns.adobe.com/xmp/Identifier/qual/1.0/";;
+		public const string XmpRightsNS = "http://ns.adobe.com/xap/1.0/rights/";;
+		public const string XmpBJNS = "http://ns.adobe.com/xap/1.0/bj/";;
+		public const string XmpMMNS = "http://ns.adobe.com/xap/1.0/mm/";;
+		public const string ExifNS = "http://ns.adobe.com/exif/1.0/";;
+		public const string TiffNS = "http://ns.adobe.com/tiff/1.0/";;
+		public const string RdfNS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";;
+		public const string RdfsNS = "http://www.w3.org/2000/01/rdf-schema#";;
+		public const string IViewNS = "http://ns.iview-multimedia.com/mediapro/1.0/";;
+		public const string XmlNS = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/";;
+
+		// FIXME this needs to be parsable by System.Uri
+		public const string FSpotXMPBase = "http://fakebase.f-spot.org/internal/";;
+
+		static MetadataStore ()
+		{
+			Namespaces = new NamespaceManager ();
+			
+			Namespaces.AddNamespace (PhotoshopNS, "photoshop");
+			Namespaces.AddNamespace (Iptc4xmpCoreNS, "Iptc4xmpCore");
+			Namespaces.AddNamespace (DcNS, "dc");
+			Namespaces.AddNamespace (XmpNS, "xmp");
+			Namespaces.AddNamespace (XmpidqNS, "xmpidq");
+			Namespaces.AddNamespace (XmpRightsNS, "xmpRights");
+			Namespaces.AddNamespace (XmpBJNS, "xmpBJ");
+			Namespaces.AddNamespace (XmpMMNS, "xmpMM");
+			Namespaces.AddNamespace (ExifNS, "exif");
+			Namespaces.AddNamespace (TiffNS, "tiff");
+			Namespaces.AddNamespace (RdfNS, "rdf");
+			Namespaces.AddNamespace (RdfsNS, "rdfs");
+			Namespaces.AddNamespace (IViewNS, "mediapro");
+		}
+
+		public static MetadataStore Descriptions {
+			get {
+				if (descriptions == null) {
+					descriptions = new MetadataStore ();
+					System.IO.Stream stream = System.Reflection.Assembly.GetCallingAssembly ().GetManifestResourceStream ("dces.rdf");
+					if (stream != null) {
+						descriptions.Import (new RdfXmlReader (stream));
+					} else {
+						System.Console.WriteLine ("Can't find resource");
+					}
+				}
+				
+				return descriptions;
+			}
+		}
+
+		public void Dump ()
+		{
+#if enable_debug
+			XmpFile xmp = new XmpFile ();
+			xmp.Store = this;
+			xmp.Save (System.Console.OpenStandardOutput ());
+#endif
+		}
+
+		public static void AddLiteral (StatementSink sink, string predicate, string type, SemWeb.Literal value)
+		{
+			Entity empty = new BNode ();
+			Statement top = new Statement (FSpotXMPBase, (Entity)MetadataStore.Namespaces.Resolve (predicate), empty);
+			Statement desc = new Statement (empty, 
+							(Entity)MetadataStore.Namespaces.Resolve ("rdf:type"), 
+							(Entity)MetadataStore.Namespaces.Resolve (type));
+			sink.Add (desc);
+			Statement literal = new Statement (empty,
+							   (Entity)MetadataStore.Namespaces.Resolve ("rdf:li"),
+							   value);
+			sink.Add (literal);
+			sink.Add (top);
+		}
+
+		public static void AddLiteral (StatementSink sink, string predicate, string value)
+		{
+			Statement stmt = new Statement (FSpotXMPBase,
+							(Entity)MetadataStore.Namespaces.Resolve (predicate), 
+							new SemWeb.Literal (value));
+			sink.Add (stmt);
+		}
+
+		public static void Add (StatementSink sink, string predicate, string type, string [] values)
+		{
+			Add (sink, FSpotXMPBase, predicate, type, values);
+		}
+
+		public void Delete (string predicate)
+		{
+			System.Collections.ArrayList to_remove = new System.Collections.ArrayList ();
+			foreach (Statement stmt in this) {
+				if (stmt.Predicate == MetadataStore.Namespaces.Resolve (predicate)) {
+					to_remove.Add (stmt);
+					break;
+				}
+			}
+
+			foreach (Statement stmt in to_remove)
+				this.Remove (stmt);
+		}
+
+		public void Update (string predicate, string value1)
+		{
+			// Delete first..
+			Delete (predicate);
+			// Add after...
+			AddLiteral (this, predicate, value1);
+		}
+
+		public void Update (string predicate, string type, string [] values)
+		{
+			Entity anon = null;
+
+			System.Collections.ArrayList to_remove = new System.Collections.ArrayList ();
+			foreach (Statement stmt in this) {
+				if (stmt.Predicate == MetadataStore.Namespaces.Resolve (predicate)) {
+					if (type != null) // only look further if we have a type.
+						anon = (Entity) stmt.Object;
+					to_remove.Add (stmt);
+					break;
+				}
+			}
+
+			if (anon != null) {
+				foreach (Statement stmt in this) {
+					if (stmt.Subject == anon) {
+						to_remove.Add (stmt);
+					}
+				}
+			}
+
+			foreach (Statement stmt in to_remove)
+				this.Remove (stmt);
+
+			if (values.Length > 0) {
+				Add (this, predicate, type, values);
+                        }
+		}
+		
+		public static void Add (StatementSink sink, Entity subject, string predicate, string type, string [] values)
+		{
+			if (values == null) {
+				System.Console.WriteLine ("{0} has no values; skipping", predicate);
+				return;
+			}
+
+                        Entity empty = new SemWeb.BNode();
+			Statement top = new Statement (subject, (Entity)MetadataStore.Namespaces.Resolve (predicate), empty);
+			Statement desc = new Statement (empty, 
+							(Entity)MetadataStore.Namespaces.Resolve ("rdf:type"), 
+							(Entity)MetadataStore.Namespaces.Resolve (type));
+			sink.Add (desc);
+			foreach (string value in values) {
+				Statement literal = new Statement (empty,
+								   (Entity)MetadataStore.Namespaces.Resolve ("rdf:li"),
+								   new SemWeb.Literal (value, null, null));
+				sink.Add (literal);
+			}
+			sink.Add (top);
+		}
+
+		private class StatementWriter : StatementSink 
+		{
+			string name;
+			public StatementWriter (string name)
+			{
+				this.name = name;
+			}
+
+			public bool Add (Statement stmt)
+			{
+				string predicate = stmt.Predicate.ToString ();
+
+				if (predicate.StartsWith (name))
+					System.Console.WriteLine ("----------- {0}", stmt);
+
+				return true;
+			}
+		}
+
+		private class SelectFirst : StatementSink
+		{
+			public Statement Statement;
+
+			public bool Add (Statement stmt)
+			{
+				this.Statement = stmt;
+				return false;
+			}
+		}			
+
+		public void DumpNode (XPathSemWebNavigator navi, int depth)
+		{
+			do { 
+				System.Console.WriteLine ("node [{0}] {1} {2}", depth, navi.Name, navi.Value);
+			} while (navi.MoveToNext ());
+		}
+	       
+	}	       
+}

Added: trunk/beagle/Util/F-Spot/PixbufUtils.cs
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/PixbufUtils.cs	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,944 @@
+using System.Collections;
+using System.Runtime.InteropServices;
+using System;
+using System.IO;
+using FSpot;
+
+/**
+  1        2       3      4         5            6           7          8
+
+888888  888888      88  88      8888888888  88                  88  8888888888
+88          88      88  88      88  88      88  88          88  88      88  88
+8888      8888    8888  8888    88          8888888888  8888888888          88
+88          88      88  88
+88          88  888888  888888
+
+t-l     t-r     b-r     b-l     l-t         r-t         r-b             l-b
+
+**/
+
+public enum PixbufOrientation {
+	TopLeft = 1,
+	TopRight = 2,
+	BottomRight = 3,
+	BottomLeft = 4,
+	LeftTop = 5,
+	RightTop = 6,
+	RightBottom = 7,
+	LeftBottom = 8
+}
+
+public class PixbufUtils {
+		
+	static public PixbufOrientation Rotate270 (PixbufOrientation orientation)
+	{
+		PixbufOrientation [] rot = new PixbufOrientation [] {
+			PixbufOrientation.LeftBottom, 
+			PixbufOrientation.LeftTop,
+			PixbufOrientation.RightTop,
+			PixbufOrientation.RightBottom, 
+			PixbufOrientation.BottomLeft,
+			PixbufOrientation.TopLeft,
+			PixbufOrientation.TopRight,
+			PixbufOrientation.BottomRight
+		};
+
+		orientation = rot [((int)orientation) -1];
+		return orientation;
+	}
+
+	static public PixbufOrientation Rotate90 (PixbufOrientation orientation)
+	{
+		orientation = Rotate270 (orientation);
+		orientation = Rotate270 (orientation);
+		orientation = Rotate270 (orientation);
+		return orientation;
+	}
+
+#if false
+	static Pixbuf error_pixbuf = null;
+	public static Pixbuf ErrorPixbuf {
+		get {
+			if (error_pixbuf == null)
+				error_pixbuf = GtkUtil.TryLoadIcon (FSpot.Global.IconTheme, "f-spot-question-mark", 256, (Gtk.IconLookupFlags)0);
+			return error_pixbuf;
+		}
+	}
+	public static Pixbuf LoadingPixbuf = PixbufUtils.LoadFromAssembly ("f-spot-loading.png");
+
+	public static int GetSize (Pixbuf pixbuf)
+	{
+		return Math.Max (pixbuf.Width, pixbuf.Height);
+	}
+
+	public static double Fit (Pixbuf pixbuf,
+				  int dest_width, int dest_height,
+				  bool upscale_smaller,
+				  out int fit_width, out int fit_height)
+	{
+		return Fit (pixbuf.Width, pixbuf.Height, 
+			    dest_width, dest_height, 
+			    upscale_smaller, 
+			    out fit_width, out fit_height);
+	}
+
+	public static double Fit (int orig_width, int orig_height,
+				  int dest_width, int dest_height,
+				  bool upscale_smaller,
+				  out int fit_width, out int fit_height)
+	{
+		if (orig_width == 0 || orig_height == 0) {
+			fit_width = 0;
+			fit_height = 0;
+			return 0.0;
+		}
+
+		double scale = Math.Min (dest_width / (double)orig_width,
+					 dest_height / (double)orig_height);
+		
+		if (scale > 1.0 && !upscale_smaller)
+			scale = 1.0;
+
+		fit_width = (int)(scale * orig_width);
+		fit_height = (int)(scale * orig_height);
+		
+		return scale;
+	}
+
+
+	// FIXME: These should be in GTK#.  When my patch is committed, these LoadFrom* methods will
+	// go away.
+
+	public class AspectLoader {
+		Gdk.PixbufLoader loader = new Gdk.PixbufLoader ();
+		int max_width;
+		int max_height;
+		PixbufOrientation orientation;
+		int orig_width;
+
+		public AspectLoader (int max_width, int max_height) 
+		{
+			this.max_height = max_height;
+			this.max_width = max_width;
+			loader.SizePrepared += HandleSizePrepared;
+		}
+
+		private void HandleSizePrepared (object obj, SizePreparedArgs args)
+		{
+			switch (orientation) {
+			case PixbufOrientation.LeftTop:
+			case PixbufOrientation.LeftBottom:
+			case PixbufOrientation.RightTop:
+			case PixbufOrientation.RightBottom:	
+				int tmp = max_width;
+				max_width = max_height;
+				max_height = tmp;
+				break;
+			default:
+				break;
+			}
+
+			double scale = Math.Min (max_width / (double)args.Width,
+						 max_height / (double)args.Height);
+
+			
+			int scale_width = (int)(scale * args.Width);
+			int scale_height = (int)(scale * args.Height);
+
+			if (scale < 1.0)
+				loader.SetSize (scale_width, scale_height);
+		}
+
+		public Pixbuf Load (System.IO.Stream stream, PixbufOrientation orientation)
+		{
+			int count;
+			byte [] data = new byte [8192];
+			while (((count = stream.Read (data, 0, data.Length)) > 0) && loader.Write (data, (ulong)count))
+				;
+			
+			loader.Close ();
+			Pixbuf orig = loader.Pixbuf;
+			Gdk.Pixbuf rotated = TransformOrientation (orig, orientation, true);
+			
+			if (orig != rotated) {
+				CopyThumbnailOptions (orig, rotated);
+				orig.Dispose ();
+			}
+			loader.Dispose ();
+			return rotated;
+		}
+		
+		public Pixbuf LoadFromFile (string path)
+		{
+			try {
+				orientation = GetOrientation (path);
+				using (FileStream fs = File.OpenRead (path)) {
+					return Load (fs, orientation);
+				}
+			} catch (Exception) {
+				System.Console.WriteLine ("Error loading photo {0}", path);
+				return null;
+			} 
+		}
+	}
+
+	public static Pixbuf ShallowCopy (Pixbuf pixbuf)
+	{
+		Pixbuf result = new Pixbuf (pixbuf, 0, 0, pixbuf.Width, pixbuf.Height);
+		CopyThumbnailOptions (pixbuf, result);
+		return result;
+	}
+
+	public static Pixbuf ScaleToMaxSize (Pixbuf pixbuf, int width, int height)
+	{
+		return ScaleToMaxSize (pixbuf, width, height, true);
+	}	
+
+	public static Pixbuf ScaleToMaxSize (Pixbuf pixbuf, int width, int height, bool upscale)
+	{
+		double scale = Math.Min  (width / (double)pixbuf.Width, height / (double)pixbuf.Height);
+		int scale_width = (int)(scale * pixbuf.Width);
+		int scale_height = (int)(scale * pixbuf.Height);
+
+		Gdk.Pixbuf result;
+		if (upscale || (scale < 1.0))
+			result = pixbuf.ScaleSimple (scale_width, scale_height, (scale_width > 20) ? Gdk.InterpType.Bilinear : Gdk.InterpType.Nearest);
+		else
+			result = pixbuf.Copy ();
+
+		CopyThumbnailOptions (pixbuf, result);
+
+		return result;
+	}
+		
+	static public void GetSize (string path, out int width, out int height)
+	{
+		Gdk.PixbufLoader loader = new Gdk.PixbufLoader ();
+		int orig_width = 0;
+		int orig_height = 0;
+		bool done = false;
+
+		loader.SizePrepared += delegate (object obj, SizePreparedArgs args) {
+			orig_width = args.Width;
+			orig_height = args.Height;
+			done = true;
+		};
+		
+		using (Stream stream = File.OpenRead (path)) {
+			byte [] data = new byte [4096];
+			int count;
+
+			while (((count = stream.Read (data, 0, data.Length)) > 0) && loader.Write (data, (ulong)count)) {
+				if (done)
+					break;
+			}
+		}
+		
+		width = orig_width;
+		height = orig_height;
+	}
+
+	static public Pixbuf LoadAtMaxSize (string path, int max_width, int max_height)
+	{
+#if true
+		PixbufUtils.AspectLoader loader = new AspectLoader (max_width, max_height);
+		return loader.LoadFromFile (path);
+#else
+		int width, height;
+		JpegUtils.GetSize (path, out width, out height);
+		PixbufUtils.Fit (width, height, max_width, max_height, false, out width, out height);
+		Gdk.Pixbuf image = JpegUtils.LoadScaled (path, width, height);
+		
+		return image;
+#endif
+	}
+
+	static public Pixbuf LoadFromStream (System.IO.Stream input)
+	{
+		Gdk.PixbufLoader loader = new Gdk.PixbufLoader ();
+		byte [] buffer = new byte [8192];
+		int n;
+
+		while ((n = input.Read (buffer, 0, 8192)) != 0)
+			loader.Write (buffer, (ulong) n);
+		
+		loader.Close ();
+		
+		return loader.Pixbuf;
+		
+	}
+	
+
+	// 
+	// FIXME this is actually not public api and we should do a verison check,
+	// but frankly I'm irritated that it isn't public so I don't much care.
+	//
+	[DllImport("libgdk_pixbuf-2.0-0.dll")]
+	static extern bool gdk_pixbuf_set_option(IntPtr raw, string key, string value);
+	
+	public static bool SetOption(Gdk.Pixbuf pixbuf, string key, string value)
+	{
+		
+		if (value != null)
+			return gdk_pixbuf_set_option(pixbuf.Handle, key, value);
+		else
+			return false;
+	}
+	
+	public static void CopyThumbnailOptions (Gdk.Pixbuf src, Gdk.Pixbuf dest)
+	{
+		if (src != null && dest != null) {
+			PixbufUtils.SetOption (dest, "tEXt::Thumb::URI", src.GetOption ("tEXt::Thumb::URI"));
+			PixbufUtils.SetOption (dest, "tEXt::Thumb::MTime", src.GetOption ("tEXt::Thumb::MTime"));
+		}
+	}
+
+	public static void Save (Gdk.Pixbuf pixbuf, System.IO.Stream stream, string type, string [] options, string [] values)
+	{
+		byte [] data;
+
+		data = PixbufUtils.Save (pixbuf, type, options, values);
+		stream.Write (data, 0, data.Length);
+	}
+
+	static string [] NullTerminateArray (string [] options)
+	{
+		string [] terminated_options = options;
+
+		if (options != null && options [ options.Length - 1 ] != null) {
+			terminated_options = new string [options.Length + 1];
+			Array.Copy (options, terminated_options, options.Length);
+		}
+		
+		return terminated_options;
+	}
+
+	[DllImport("libgdk_pixbuf-2.0-0.dll")]
+	static extern bool gdk_pixbuf_save_to_bufferv (IntPtr raw, out IntPtr data, out IntPtr length, 
+						       string type, 
+						       string [] keys, string [] values, out IntPtr error);
+
+					
+	public static byte [] Save (Gdk.Pixbuf pixbuf, string type, string [] options, string [] values)
+	{
+		IntPtr error = IntPtr.Zero;
+		IntPtr data;
+		IntPtr length;
+
+		bool success = gdk_pixbuf_save_to_bufferv (pixbuf.Handle, 
+							   out data, 
+							   out length, 
+							   type,
+							   NullTerminateArray (options),
+							   NullTerminateArray (values),
+							   out error);
+		
+		if (error != IntPtr.Zero) 
+			throw new GLib.GException (error);
+
+		if (!success)
+			throw new ApplicationException ("Unknown error while saving file");
+
+		byte [] content = new byte [(int)length];
+		Marshal.Copy (data, content, 0, (int)length);
+
+		GLib.Marshaller.Free (data);
+
+		return content;
+	}
+	
+	public static Pixbuf TagIconFromPixbuf (Pixbuf source)
+	{
+		return IconFromPixbuf (source, (int) Tag.IconSize.Large);
+	}
+
+	public static Pixbuf IconFromPixbuf (Pixbuf source, int size)
+	{
+		Pixbuf tmp = null;
+		Pixbuf icon = null;
+
+		if (source.Width > source.Height)
+			source = tmp = new Pixbuf (source, (source.Width - source.Height) /2, 0, source.Height, source.Height);
+		else if (source.Width < source.Height) 
+			source = tmp = new Pixbuf (source, 0, (source.Height - source.Width) /2, source.Width, source.Width);
+
+		if (source.Width == source.Height)
+			icon = source.ScaleSimple (size, size, InterpType.Bilinear);
+		else
+			throw new Exception ("Bad logic leads to bad accidents");
+
+		if (tmp != null)
+			tmp.Dispose ();
+		
+		return icon;
+	}
+		
+	static public Pixbuf LoadFromScreen (Gdk.Window win) {
+		Gdk.Screen screen = win.Screen;
+		Drawable d = screen.RootWindow;
+		int monitor = screen.GetMonitorAtWindow (win);
+		Gdk.Rectangle geom = screen.GetMonitorGeometry (monitor);
+		
+		//
+		// We use the screen width and height because that reflects
+		// the current resolution, the RootWindow can actually be different.
+		//
+
+		Pixbuf buf = new Pixbuf (Colorspace.Rgb, false, 8, geom.Width, geom.Height);
+		
+		return buf.GetFromDrawable (d,
+					    d.Colormap, geom.X, geom.Y, 0, 0, 
+					    geom.Width, geom.Height);
+	}
+
+	static public Pixbuf LoadFromScreen () {
+		Screen screen = Display.Default.GetScreen (0);
+		Drawable d = screen.RootWindow;
+		int width = screen.Width;
+		int height = screen.Height;
+		
+		//
+		// We use the screen width and height because that reflects
+		// the current resolution, the RootWindow can actually be different.
+		//
+
+		Pixbuf buf = new Pixbuf (Colorspace.Rgb, false, 8, width, height);
+		
+		return buf.GetFromDrawable (d,
+					    d.Colormap, 0, 0, 0, 0, 
+					    width, height);
+	}
+
+	static public Pixbuf LoadFromAssembly (string resource)
+	{
+		try {
+			return new Pixbuf (System.Reflection.Assembly.GetEntryAssembly (), resource);
+		} catch {
+			return null;
+		}
+	}
+
+	[DllImport ("libc")]
+	static extern int rename (string oldpath, string newpath);
+
+	public static void SaveAtomic (Gdk.Pixbuf src, string filename, string type, string [] keys, string [] values)
+	{
+			string tmpname = filename + ".tmp";
+			src.Savev (tmpname, type, NullTerminateArray (keys), NullTerminateArray (values));
+			if (rename (tmpname, filename) < 0)
+				throw new Exception ("Error renaming file");
+	}
+
+	public static Gdk.Pixbuf ScaleToAspect (Gdk.Pixbuf orig, int width, int height)
+	{
+		Gdk.Rectangle pos;
+		double scale = Fit (orig, width, height, false, out pos.Width, out pos.Height);
+		pos.X = (width - pos.Width) / 2;
+		pos.Y = (height - pos.Height) / 2;
+
+		Pixbuf scaled = new Pixbuf (Colorspace.Rgb, false, 8, width, height);
+		scaled.Fill (0x000000); 
+
+		orig.Composite (scaled, pos.X, pos.Y, 
+				pos.Width, pos.Height,
+				pos.X, pos.Y, scale, scale,
+				Gdk.InterpType.Bilinear,
+				255);
+
+		return scaled;
+	}
+
+	public static string Resize (string orig_path, int size, bool copy_meta)
+	{
+		string version_path = System.IO.Path.GetTempFileName ();
+		Resize (orig_path, version_path, size, copy_meta);
+		return version_path;
+	}
+
+	public static void Resize (string orig_path, string dest_path, int size, bool copy_meta)
+	{
+		Exif.ExifData exif_data;
+		if (copy_meta)
+			exif_data = new Exif.ExifData (orig_path);
+		else 
+			exif_data = new Exif.ExifData ();
+
+		Gdk.Pixbuf image = PixbufUtils.LoadAtMaxSize (orig_path, size, size);
+
+		PixbufUtils.SaveJpeg (image, dest_path, 95, exif_data);
+		image.Dispose ();
+	}
+	
+
+	public static Pixbuf Flatten (Pixbuf pixbuf)
+	{
+		if (!pixbuf.HasAlpha)
+			return null;
+
+		Pixbuf flattened = new Pixbuf (Colorspace.Rgb, false, 8, pixbuf.Width, pixbuf.Height);
+		pixbuf.CompositeColor (flattened, 0, 0, 
+				       pixbuf.Width, pixbuf.Height, 
+				       0, 0, 1, 1, 
+				       InterpType.Bilinear,
+				       255, 0, 0, 2000, 0xffffff, 0xffffff);
+
+		return flattened;
+	}
+
+	[StructLayout(LayoutKind.Sequential)]
+	public unsafe struct FPixbufJpegMarker {
+		public int type;
+		public byte *data;
+		public int length;
+	}
+
+	[DllImport ("libfspot")]
+	static extern bool f_pixbuf_save_jpeg (IntPtr src, string path, int quality, FPixbufJpegMarker [] markers, int num_markers);
+
+	public static void SaveJpeg (Pixbuf pixbuf, string path, int quality, Exif.ExifData exif_data)
+	{
+		Pixbuf temp = null;
+		if (pixbuf.HasAlpha) {
+			temp = Flatten (pixbuf);
+			pixbuf = temp;
+		}
+
+		// The DCF spec says thumbnails should be 160x120 always
+		Pixbuf thumbnail = ScaleToAspect (pixbuf, 160, 120);
+		byte [] thumb_data = Save (thumbnail, "jpeg", null, null);
+		exif_data.Data = thumb_data;
+		thumbnail.Dispose ();
+
+		// Most of the things we will set will be in the 0th ifd
+		Exif.ExifContent content = exif_data.GetContents (Exif.Ifd.Zero);
+
+		// reset the orientation tag the default is top/left
+		content.GetEntry (Exif.Tag.Orientation).Reset ();
+
+		// set the write time in the datetime tag
+		content.GetEntry (Exif.Tag.DateTime).Reset ();
+
+		// set the software tag
+		content.GetEntry (Exif.Tag.Software).SetData (FSpot.Defines.PACKAGE + " version " + FSpot.Defines.VERSION);
+
+		byte [] data = exif_data.Save ();
+		FPixbufJpegMarker [] marker = new FPixbufJpegMarker [0];
+		bool result = false;
+
+		unsafe {
+			if (data.Length > 0) {
+				
+				fixed (byte *p = data) {
+					marker = new FPixbufJpegMarker [1];
+					marker [0].type = 0xe1; // APP1 marker
+					marker [0].data = p;
+					marker [0].length = data.Length;
+					
+					result = f_pixbuf_save_jpeg (pixbuf.Handle, path, quality, marker, marker.Length);
+				}					
+			} else
+				result = f_pixbuf_save_jpeg (pixbuf.Handle, path, quality, marker, marker.Length);
+			
+		}
+
+		if (temp != null)
+			temp.Dispose ();
+		
+		if (result == false)
+			throw new System.Exception ("Error Saving File");
+	}
+
+
+	[DllImport ("libfspot")]
+	static extern IntPtr f_pixbuf_unsharp_mask (IntPtr src, double radius, double amount, double threshold);
+
+	public static Pixbuf UnsharpMask (Pixbuf src, double radius, double amount, double threshold)
+	{
+		IntPtr raw_ret = f_pixbuf_unsharp_mask (src.Handle, radius, amount, threshold);
+ 		Gdk.Pixbuf ret = (Gdk.Pixbuf) GLib.Object.GetObject(raw_ret, true);
+		return ret;
+	}	
+	
+	[DllImport ("libfspot")]
+	static extern IntPtr f_pixbuf_blur (IntPtr src, double radius);
+
+	public static Pixbuf Blur (Pixbuf src, double radius)
+	{
+		IntPtr raw_ret = f_pixbuf_blur (src.Handle, radius);
+ 		Gdk.Pixbuf ret = (Gdk.Pixbuf) GLib.Object.GetObject(raw_ret, true);
+		return ret;
+	}	
+
+#if OLDREDEYE
+	[DllImport ("libfspot")]
+	static extern void f_pixbuf_remove_redeye (IntPtr src);
+
+	public static Gdk.Pixbuf RemoveRedeye (Gdk.Pixbuf src, Gdk.Rectangle area)
+#else
+	public unsafe static Gdk.Pixbuf RemoveRedeye (Gdk.Pixbuf src, Gdk.Rectangle area)
+	{
+		return RemoveRedeye (src, area, -15);
+	}
+
+	public unsafe static Gdk.Pixbuf RemoveRedeye (Gdk.Pixbuf src, Gdk.Rectangle area, int threshold)
+	//threshold, factors and comparisons borrowed from the gimp plugin 'redeye.c' by Robert Merkel
+#endif
+	{
+		Gdk.Pixbuf copy = src.Copy ();
+		Gdk.Pixbuf selection = new Gdk.Pixbuf (copy, area.X, area.Y, area.Width, area.Height);
+#if OLREDEYE
+		f_pixbuf_remove_redeye (selection.Handle);
+		selection.Dispose ();
+#else
+		byte *spix = (byte *)selection.Pixels;
+		int h = selection.Height;
+		int w = selection.Width;
+		int channels = src.NChannels;
+
+		double RED_FACTOR = 0.5133333;
+		double GREEN_FACTOR = 1;
+		double BLUE_FACTOR = 0.1933333;
+
+		for (int j = 0; j < h; j++) {
+			byte *s = spix;
+			for (int i = 0; i < w; i++) {
+				int adjusted_red = (int)(s[0] * RED_FACTOR);
+				int adjusted_green = (int)(s[1] * GREEN_FACTOR);
+				int adjusted_blue = (int)(s[2] * BLUE_FACTOR);
+
+				if (adjusted_red >= adjusted_green - threshold
+				    && adjusted_red >= adjusted_blue - threshold)
+					s[0] = (byte)(((double)(adjusted_green + adjusted_blue)) / (2.0 * RED_FACTOR));
+				s += channels;
+			}
+			spix += selection.Rowstride;
+		}
+
+#endif
+		return copy;
+	}
+
+	public static unsafe Pixbuf ColorAdjust (Pixbuf src, double brightness, double contrast,
+					  double hue, double saturation, int src_color, int dest_color)
+	{
+		Pixbuf adjusted = new Pixbuf (Colorspace.Rgb, src.HasAlpha, 8, src.Width, src.Height);
+		PixbufUtils.ColorAdjust (src, adjusted, brightness, contrast, hue, saturation, src_color, dest_color);
+		return adjusted;
+	}
+
+	public static Cms.Format PixbufCmsFormat (Pixbuf buf)
+	{
+		return buf.HasAlpha ? Cms.Format.Rgba8Planar : Cms.Format.Rgb8;
+	}
+
+	public static unsafe void ColorAdjust (Pixbuf src, Pixbuf dest, 
+					       double brightness, double contrast,
+					       double hue, double saturation, 
+					       int src_color, int dest_color)
+	{
+		if (src.Width != dest.Width || src.Height != dest.Height)
+			throw new Exception ("Invalid Dimensions");
+
+		//Cms.Profile eos10d = new Cms.Profile ("/home/lewing/ICCProfiles/EOS-10D-True-Color-Non-Linear.icm");
+		Cms.Profile srgb = Cms.Profile.CreateStandardRgb ();
+
+		Cms.Profile bchsw = Cms.Profile.CreateAbstract (256, 
+								0.0, 
+								brightness, contrast,
+								hue, saturation, src_color, 
+								dest_color);
+
+		Cms.Profile [] list = new Cms.Profile [] { srgb, bchsw, srgb };
+		Cms.Transform trans = new Cms.Transform (list, 
+							 PixbufCmsFormat (src),
+							 PixbufCmsFormat (dest),
+							 Cms.Intent.Perceptual, 0x0100);
+
+		ColorAdjust (src, dest, trans);
+
+		trans.Dispose ();
+		srgb.Dispose ();
+		bchsw.Dispose ();
+	}
+
+
+	public static unsafe void ColorAdjust (Gdk.Pixbuf src, Gdk.Pixbuf dest, Cms.Transform trans)
+	{
+		int width = src.Width;
+		byte * srcpix  = (byte *) src.Pixels;
+		byte * destpix = (byte *) dest.Pixels;
+
+		for (int row = 0; row < src.Height; row++) {
+			trans.Apply ((IntPtr) (srcpix + row * src.Rowstride),
+				     (IntPtr) (destpix + row * dest.Rowstride), 
+				     (uint)width);
+		}
+		
+	}
+
+	public static unsafe bool IsGray (Gdk.Pixbuf pixbuf, int max_difference)
+	{
+		int chan = pixbuf.NChannels;
+
+		byte *pix = (byte *)pixbuf.Pixels;
+		int h = pixbuf.Height;
+		int w = pixbuf.Width;
+		int stride = pixbuf.Rowstride;
+
+		for (int j = 0; j < h; j++) {
+			byte *p = pix;
+			for (int i = 0; i < w; i++) {
+				if (Math.Abs (p[0] - p[1]) > max_difference || Math.Abs (p[0] - p [2]) > max_difference) {
+					goto Found;
+				}
+				p += chan;
+			}
+			pix += stride;
+		}
+
+		return true;
+
+	Found:
+		return false;
+	}
+
+	public static unsafe void ReplaceColor (Gdk.Pixbuf src, Gdk.Pixbuf dest)
+	{
+		if (src.HasAlpha || !dest.HasAlpha || (src.Width != dest.Width) || (src.Height != dest.Height))
+			throw new ApplicationException ("invalid pixbufs");
+
+		byte *dpix = (byte *)dest.Pixels;
+		byte *spix = (byte *)src.Pixels;
+		int h = src.Height;
+		int w = src.Width;
+		for (int j = 0; j < h; j++) {
+			byte *d = dpix;
+			byte *s = spix;
+			for (int i = 0; i < w; i++) {
+				d[0] = s[0];
+				d[1] = s[1];
+				d[2] = s[2];
+				d += 4;
+				s += 3;
+			}
+			dpix += dest.Rowstride;
+			spix += src.Rowstride;
+		}
+	}
+
+	public static Gdk.Pixbuf GetThumbnail (Exif.ExifData data)
+	{
+		byte [] thumb_data = data.Data;
+		if (thumb_data.Length > 0) {
+			PixbufOrientation orientation = GetOrientation (data);
+			
+			using (MemoryStream mem = new MemoryStream (thumb_data)) {
+				Gdk.Pixbuf thumb = new Gdk.Pixbuf (mem);
+
+				Gdk.Pixbuf rotated = PixbufUtils.TransformOrientation (thumb, orientation);
+				
+				if (rotated != thumb)
+					thumb.Dispose ();
+				return rotated;
+			}			
+		}
+		return null;
+	}
+
+	public static PixbufOrientation GetOrientation (Exif.ExifData data)
+	{
+		PixbufOrientation orientation = PixbufOrientation.TopLeft;
+		
+		Exif.ExifEntry e = data.GetContents (Exif.Ifd.Zero).Lookup (Exif.Tag.Orientation);
+
+		if (e != null) {
+			ushort [] value = e.GetDataUShort ();
+			orientation = (PixbufOrientation) value [0];
+		}
+
+		return orientation;
+	}
+	
+	public static PixbufOrientation GetOrientation (string path)
+	{
+		using (FSpot.ImageFile img = FSpot.ImageFile.Create (path)) {
+			return img.Orientation;
+		}
+	}
+
+	[DllImport("libgnomeui-2-0.dll")]
+	static extern IntPtr gnome_thumbnail_scale_down_pixbuf(IntPtr pixbuf, int dest_width, int dest_height);
+
+	public static Gdk.Pixbuf ScaleDown (Gdk.Pixbuf src, int width, int height)
+	{
+		IntPtr raw_ret = gnome_thumbnail_scale_down_pixbuf(src.Handle, width, height);
+		Gdk.Pixbuf ret;
+		if (raw_ret == IntPtr.Zero)
+			ret = null;
+		else
+			ret = (Gdk.Pixbuf) GLib.Object.GetObject(raw_ret, true);
+		return ret;
+	}
+
+	public static Gdk.Pixbuf TransformOrientation (Gdk.Pixbuf src, PixbufOrientation orientation, bool copy_data)
+	{
+		Gdk.Pixbuf pixbuf;
+		if (src == null)
+			return null;
+		
+		switch (orientation) {
+		case PixbufOrientation.LeftTop:
+		case PixbufOrientation.LeftBottom:
+		case PixbufOrientation.RightTop:
+		case PixbufOrientation.RightBottom:	
+			pixbuf = new Gdk.Pixbuf (src.Colorspace, src.HasAlpha, 
+						 src.BitsPerSample,
+						 src.Height, src.Width);
+			break;
+		case PixbufOrientation.TopRight:
+		case PixbufOrientation.BottomRight:
+		case PixbufOrientation.BottomLeft:
+			pixbuf = new Gdk.Pixbuf (src.Colorspace, src.HasAlpha, 
+						 src.BitsPerSample,
+						 src.Width, src.Height);
+			break;
+		default:
+			pixbuf = src;
+			break;
+		}
+
+		if (copy_data && src != pixbuf) 
+			TransformAndCopy (src, pixbuf, orientation, new Gdk.Rectangle (0, 0, src.Width, src.Height));
+
+		return pixbuf;
+	}
+
+	public static Gdk.Pixbuf TransformOrientation (Gdk.Pixbuf src, PixbufOrientation orientation)
+	{
+		return TransformOrientation (src, orientation, true);
+	}
+
+	public static Gdk.Rectangle TransformOrientation (Gdk.Pixbuf src, Gdk.Rectangle args, PixbufOrientation orientation)
+	{
+		return TransformOrientation (src.Width, src.Height, args, orientation);
+	}
+	
+	public static Gdk.Rectangle TransformOrientation (int total_width, int total_height, Gdk.Rectangle args, PixbufOrientation orientation)
+	{
+		Gdk.Rectangle area = args;
+		
+		switch (orientation) {
+		case PixbufOrientation.BottomRight:
+			area.X = total_width - args.X - args.Width;
+			area.Y = total_height - args.Y - args.Height;
+			break;
+		case PixbufOrientation.TopRight:
+			area.X = total_width - args.X - args.Width;
+			break;
+		case PixbufOrientation.BottomLeft:
+			area.Y = total_height - args.Y - args.Height;
+			break;
+		case PixbufOrientation.LeftTop:
+			area.X = args.Y;
+			area.Y = args.X;
+			area.Width = args.Height;
+			area.Height = args.Width;
+			break;
+		case PixbufOrientation.RightBottom:
+			area.X = total_height - args.Y - args.Height;
+			area.Y = total_width - args.X - args.Width;
+			area.Width = args.Height;
+			area.Height = args.Width;
+			break;
+		case PixbufOrientation.RightTop:
+			area.X = total_height - args.Y - args.Height;
+			area.Y = args.X;
+			area.Width = args.Height;
+			area.Height = args.Width;
+			break;
+		case PixbufOrientation.LeftBottom:
+			area.X = args.Y;
+			area.Y = total_width - args.X - args.Width;
+			area.Width = args.Height;
+			area.Height = args.Width;
+			break;
+		default:
+			break;
+		}
+		
+		return area;
+	}
+	
+	public static Gdk.Rectangle TransformAndCopy (Gdk.Pixbuf src, Gdk.Pixbuf dest, PixbufOrientation orientation, Gdk.Rectangle args)
+	{
+		Gdk.Rectangle area = TransformOrientation (src, args, orientation);
+
+		int step = 256;
+
+		Gdk.Rectangle rect = new Gdk.Rectangle (args.X, args.Y, 
+							Math.Min (step, args.Width),
+							Math.Min (step, args.Height));
+
+		Gdk.Rectangle trect = TransformOrientation (src, rect, orientation);
+		Gdk.Pixbuf tmp = new Gdk.Pixbuf (src.Colorspace, src.HasAlpha, 
+						 src.BitsPerSample,
+						 trect.Width, trect.Height);
+
+		Gdk.Rectangle subarea;
+		BlockProcessor proc = new BlockProcessor (args, 256);
+		while (proc.Step (out subarea)) {
+			Gdk.Rectangle trans = TransformOrientation (src, subarea, orientation);
+			Gdk.Pixbuf ssub = new Gdk.Pixbuf (src, subarea.X, subarea.Y,
+							  subarea.Width, subarea.Height);
+
+			Gdk.Pixbuf tsub = new Gdk.Pixbuf (tmp, 0, 0, trans.Width, trans.Height);
+			CopyWithOrientation (ssub, tsub, orientation);
+
+			tsub.CopyArea (0, 0, trans.Width, trans.Height, dest, trans.X, trans.Y);
+			ssub.Dispose ();
+			tsub.Dispose ();
+		}
+
+		tmp.Dispose ();
+		return area;
+	}
+	// Bindings from libf.
+
+	[DllImport ("libfspot")]
+	static extern IntPtr f_pixbuf_copy_apply_brightness_and_contrast (IntPtr src, float brightness, float contrast);
+
+	public static Pixbuf ApplyBrightnessAndContrast (Pixbuf src, float brightness, float contrast)
+	{
+		return new Pixbuf (f_pixbuf_copy_apply_brightness_and_contrast (src.Handle, brightness, contrast));
+	}
+
+	[DllImport ("libfspot")]
+	static extern bool f_pixbuf_save_jpeg_atomic (IntPtr pixbuf, string filename, int quality, out IntPtr error);
+
+	public static void SaveAsJpegAtomically (Pixbuf pixbuf, string filename, int quality)
+	{
+		IntPtr error = IntPtr.Zero;
+
+		if (! f_pixbuf_save_jpeg_atomic (pixbuf.Handle, filename, quality, out error)) {
+			throw new GLib.GException (error);
+		}
+	}
+
+	[DllImport ("libfspot")]
+	static extern void f_pixbuf_copy_with_orientation (IntPtr src, IntPtr dest, int orientation);
+
+	public static void CopyWithOrientation (Gdk.Pixbuf src, Gdk.Pixbuf dest, PixbufOrientation orientation)
+	{
+		f_pixbuf_copy_with_orientation (src.Handle, dest.Handle, (int)orientation);
+	}
+
+#if false
+	[DllImport("glibsharpglue")]
+	static extern int gtksharp_object_get_ref_count (IntPtr obj);
+	
+	public static int RefCount (GLib.Object obj) {
+		return gtksharp_object_get_ref_count (obj.Handle);
+	}
+#endif
+#endif
+}

Added: trunk/beagle/Util/F-Spot/README
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/README	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,2 @@
+http://svn.gnome.org/viewvc/f-spot/trunk/src/
+Imported from F-Spot svn r3823.

Added: trunk/beagle/Util/F-Spot/upstream-changes/01-initial-diff-r3823.diff
==============================================================================
--- (empty file)
+++ trunk/beagle/Util/F-Spot/upstream-changes/01-initial-diff-r3823.diff	Sun Apr 13 20:18:35 2008
@@ -0,0 +1,464 @@
+Initial diff against F-Spot r3823.
+
+diff -u -r ./Imaging/Ciff.cs ../../../FSpot/Imaging/Ciff.cs
+--- ./Imaging/Ciff.cs	2008-04-13 13:52:26.000000000 -0400
++++ ../../../FSpot/Imaging/Ciff.cs	2008-04-13 13:25:54.000000000 -0400
+@@ -454,7 +454,6 @@
+ 				return DCRawFile.RawPixbufStream (uri);
+ 		}
+ 
+-#if false
+ 		public override Gdk.Pixbuf Load (int width, int height)
+ 		{
+ 			Gdk.Pixbuf full = this.Load ();
+@@ -462,7 +461,7 @@
+ 			full.Dispose ();
+ 			return scaled;
+ 		}
+-#endif
++
+ 		public void Dump ()
+ 		{
+ 			Root.Dump ();
+diff -u -r ./Imaging/DCRawFile.cs ../../../FSpot/Imaging/DCRawFile.cs
+--- ./Imaging/DCRawFile.cs	2008-04-13 14:17:22.000000000 -0400
++++ ../../../FSpot/Imaging/DCRawFile.cs	2008-04-13 13:25:54.000000000 -0400
+@@ -89,18 +89,14 @@
+ 
+ 		internal static System.IO.Stream RawPixbufStream (Uri location)
+ 		{
+-#if false
+ 			string path = location.LocalPath;
+ 			string [] args = new string [] { dcraw_command, "-h", "-w", "-c", "-t", "0", path };
+ 			
+ 			InternalProcess proc = new InternalProcess (System.IO.Path.GetDirectoryName (path), args);
+ 			proc.StandardInput.Close ();
+ 			return proc.StandardOutput;
+-#else
+-			return null;
+-#endif
+ 		}
+-	
++		
+ 		public static System.IO.Stream RawPixbufStreamOld (string path)
+ 		{
+ 			// FIXME this filename quoting is super lame
+@@ -114,7 +110,6 @@
+ 			return new Pipe (process, process.StandardOutput.BaseStream);
+ 		}
+ 		
+-#if false
+ 		public static Gdk.Pixbuf Load (string path, string args)
+ 		{
+ 			// FIXME this filename quoting is super lame
+@@ -130,6 +125,5 @@
+ 				return PixbufUtils.LoadFromStream (process.StandardOutput.BaseStream);
+ 			}
+ 		}
+-#endif
+ 	}
+ }
+diff -u -r ./Imaging/ImageFile.cs ../../../FSpot/Imaging/ImageFile.cs
+--- ./Imaging/ImageFile.cs	2008-04-13 14:11:44.000000000 -0400
++++ ../../../FSpot/Imaging/ImageFile.cs	2008-04-13 13:25:54.000000000 -0400
+@@ -1,6 +1,6 @@
+ using System;
+ using System.IO;
+-using UriUtils = Beagle.Util.UriFu;
++using FSpot.Utils;
+ 
+ namespace FSpot {
+ 	public class ImageFormatException : ApplicationException {
+@@ -30,12 +30,10 @@
+ 			// FIXME, this seems like the sane thing to do, but vfs streams seem to 
+ 			// actually be faster and they need more testing.
+ 			//if (vfs.IsLocal)
+-			return File.OpenRead (uri.LocalPath);
++			//	return File.OpenRead (uri.LocalPath);
+ 
+-#if false
+ 			System.Console.WriteLine ("open uri = {0}", uri.ToString ());
+ 			return new Gnome.Vfs.VfsStream (uri.ToString (), FileMode.Open);
+-#endif
+ 		}
+ 
+ 		public virtual Stream PixbufStream ()
+@@ -83,7 +81,6 @@
+ 			get { return null; }
+ 		}
+ 		
+-#if false
+ 		public virtual void Save (Gdk.Pixbuf pixbuf, System.IO.Stream stream)
+ 		{
+ 			throw new NotImplementedException ();
+@@ -125,23 +122,21 @@
+ 				return aspect.Load (stream, Orientation);
+ 			}	
+ 		}
+-#endif
++	
+ 		public virtual PixbufOrientation GetOrientation () 
+ 		{
+ 			return PixbufOrientation.TopLeft;
+ 		}
+-#if false		
++		
+ 		// FIXME this need to have an intent just like the loading stuff.
+ 		public virtual Cms.Profile GetProfile ()
+ 		{
+ 			return null;
+ 		}
+-#endif	
++		
+ 		public virtual System.DateTime Date 
+ 		{
+ 			get {
+-				return System.IO.File.GetCreationTimeUtc (uri.LocalPath);
+-#if false
+ 				// FIXME mono uses the file change time (ctime) incorrectly
+ 				// as the creation time so we try to work around that slightly
+ 				Gnome.Vfs.FileInfo info = new Gnome.Vfs.FileInfo (uri.ToString ());
+@@ -153,7 +148,6 @@
+ 					return create;
+ 				else 
+ 					return write;
+-#endif
+ 			}
+ 		}
+ 
+diff -u -r ./Imaging/JpegFile.cs ../../../FSpot/Imaging/JpegFile.cs
+--- ./Imaging/JpegFile.cs	2008-04-13 14:20:17.000000000 -0400
++++ ../../../FSpot/Imaging/JpegFile.cs	2008-04-13 13:25:54.000000000 -0400
+@@ -10,9 +10,7 @@
+ 
+ namespace FSpot {
+ 	public interface IThumbnailContainer {
+-#if false
+ 		Gdk.Pixbuf GetEmbeddedThumbnail ();
+-#endif
+ 	}
+ 
+ 	public class JpegFile : ImageFile, IThumbnailContainer, SemWeb.StatementSource {
+@@ -64,12 +62,11 @@
+ 			Header.Select (sink);
+ 		}
+ 
+-#if false
+ 		public override Cms.Profile GetProfile ()
+ 		{
+ 			return Header.GetProfile ();
+ 		}
+-#endif
++
+ 		public override string Description {
+ 			get {
+ #if USE_TIFF
+@@ -120,7 +117,7 @@
+ 		private void UpdateMeta ()
+ 		{
+ 			Exif.ExifContent image_content = this.ExifData.GetContents (Exif.Ifd.Zero);
+-			image_content.GetEntry (Exif.Tag.Software).SetData ("F-Spot" + " version " + "r3823");
++			image_content.GetEntry (Exif.Tag.Software).SetData (FSpot.Defines.PACKAGE + " version " + FSpot.Defines.VERSION);
+ 
+ 			// set the write time in the datetime tag
+ 			image_content.GetEntry (Exif.Tag.DateTime).Reset ();
+@@ -141,7 +138,6 @@
+ 			// Console.WriteLine ("saved");
+ 		}
+ 		
+-#if false
+ 		public void SaveMetaData (string path)
+ 		{
+ 			UpdateMeta ();
+@@ -187,7 +183,6 @@
+ 			// now update the exif data
+ 			ExifData.Data = thumb_data;
+ 		}
+-#endif
+ 
+ 		public void SetDimensions (int width, int height)
+ 		{
+@@ -213,7 +208,6 @@
+ 			image_content.GetEntry (Exif.Tag.PixelYDimension).SetData ((uint)height);
+ 		}
+ 
+-#if false
+ 		public override void Save (Gdk.Pixbuf pixbuf, System.IO.Stream stream)
+ 		{
+ 
+@@ -252,7 +246,7 @@
+ 			}
+ 			return null;
+ 		}
+-#endif	
++		
+ 		public Exif.ExifData ExifData {
+ 			get {
+ 				if (exif_data == null) {
+diff -u -r ./Imaging/JpegHeader.cs ../../../FSpot/Imaging/JpegHeader.cs
+--- ./Imaging/JpegHeader.cs	2008-04-13 13:55:40.000000000 -0400
++++ ../../../FSpot/Imaging/JpegHeader.cs	2008-04-13 13:25:54.000000000 -0400
+@@ -294,7 +294,6 @@
+ 		return FindMarker (new Signature (id, name));
+ 	}
+ 
+-#if false
+ 	public Cms.Profile GetProfile ()
+ 	{
+ 		Marker m = FindMarker (IccProfileSignature);
+@@ -312,7 +311,7 @@
+ 		
+ 		return null;
+ 	}
+-#endif
++	
+ 	public FSpot.Tiff.Header GetExifHeader ()
+ 	{
+ 		string name = ExifSignature.Name;
+diff -u -r ./Imaging/MrwFile.cs ../../../FSpot/Imaging/MrwFile.cs
+--- ./Imaging/MrwFile.cs	2008-04-13 13:53:37.000000000 -0400
++++ ../../../FSpot/Imaging/MrwFile.cs	2008-04-13 13:25:54.000000000 -0400
+@@ -217,7 +217,6 @@
+ 			return DCRawFile.RawPixbufStream (uri);
+ 		}
+ 		
+-#if false
+ 		public override Gdk.Pixbuf Load ()
+ 		{
+ 			using (System.IO.Stream stream = Open ()) {
+@@ -229,7 +228,7 @@
+ 		{
+ 			return PixbufUtils.ScaleToMaxSize (this.Load (), width, height);
+ 		}
+-#endif
++
+ 		protected void LoadBlocks () 
+ 		{
+ 			using (System.IO.Stream file = Open ()) {
+diff -u -r ./Imaging/PngFile.cs ../../../FSpot/Imaging/PngFile.cs
+--- ./Imaging/PngFile.cs	2008-04-13 14:21:09.000000000 -0400
++++ ../../../FSpot/Imaging/PngFile.cs	2008-04-13 13:25:54.000000000 -0400
+@@ -1,5 +1,6 @@
+ using ICSharpCode.SharpZipLib.Zip.Compression;
+ using SemWeb;
++using Cms;
+ using System.IO;
+ using FSpot.Xmp;
+ using System.Collections;
+@@ -427,7 +428,7 @@
+ 				this.Time = System.DateTime.Now;
+ 			}
+ 		}
+-#if false
++
+ 		public class StandardRgbChunk : Chunk {
+ 			public StandardRgbChunk (string name, byte [] data) : base (name, data) {}
+ 			
+@@ -437,7 +438,7 @@
+ 				}
+ 			}
+ 		}
+-#endif
++
+ 		public class GammaChunk : Chunk {
+ 			public GammaChunk (string name, byte [] data) : base (name, data) {}
+ 			private const int divisor = 100000;
+@@ -687,9 +688,7 @@
+ 				name_table ["cHRM"] = typeof (ColorChunk);
+ 				name_table ["pHYs"] = typeof (PhysChunk);
+ 				name_table ["gAMA"] = typeof (GammaChunk);
+-#if false
+ 				name_table ["sRGB"] = typeof (StandardRgbChunk);
+-#endif
+ 			}
+ 			
+ 			protected Chunk ()
+@@ -851,7 +850,6 @@
+ 			}
+ 		}
+ 
+-#if false
+ 		public class ScanlineDecoder {
+ 			int width;
+ 			int height;
+@@ -1168,7 +1166,7 @@
+ 			}
+ 			return pixbuf;
+ 		}
+-#endif
++
+ 		private static byte [] magic = new byte [] { 137, 80, 78, 71, 13, 10, 26, 10 };
+ 
+ 				
+@@ -1273,7 +1271,6 @@
+ 			Header.Save (stream);
+ 		}
+ 
+-#if false
+ 		public void Save (string path)
+ 		{
+ 			string  temp_path = path + ".tmp.png";
+@@ -1360,7 +1357,7 @@
+ 			
+ 			return null;
+ 		}
+-#endif
++
+ 		public override string Description {
+ 			get {
+ 				string description = Header.LookupText ("Description");
+diff -u -r ./Imaging/PnmFile.cs ../../../FSpot/Imaging/PnmFile.cs
+--- ./Imaging/PnmFile.cs	2008-04-13 13:34:17.000000000 -0400
++++ ../../../FSpot/Imaging/PnmFile.cs	2008-04-13 13:25:54.000000000 -0400
+@@ -1,3 +1,4 @@
++using FSpot.Imaging;
+ using SemWeb;
+ using System;
+ using System.IO;
+@@ -116,7 +117,6 @@
+ 			return data;
+ 		}
+ 
+-#if false
+ 		static Gdk.Pixbuf LoadRGB16 (Stream stream, int width, int height)
+ 		{
+ 			Gdk.Pixbuf pixbuf = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, false, 8, width, height);
+@@ -280,7 +280,6 @@
+ 				throw new System.Exception (System.String.Format ("unknown pnm type {0}", header.Magic));
+ 			}			
+ 		}
+-#endif
+ 	}
+ 
+ #if ENABLE_NUNIT
+diff -u -r ./Imaging/RafFile.cs ../../../FSpot/Imaging/RafFile.cs
+--- ./Imaging/RafFile.cs	2008-04-13 13:53:16.000000000 -0400
++++ ../../../FSpot/Imaging/RafFile.cs	2008-04-13 13:25:54.000000000 -0400
+@@ -69,7 +69,6 @@
+ 				return DCRawFile.RawPixbufStream (uri);
+ 		}
+  
+-#if false
+ 		public override Gdk.Pixbuf Load ()
+ 		{
+ 			return new Gdk.Pixbuf (PixbufStream ());
+@@ -83,7 +82,7 @@
+ 			full.Dispose ();
+ 			return scaled;
+ 		}
+-#endif
++
+ 		public void Select (SemWeb.StatementSink sink)
+ 		{
+ 			byte [] data = GetEmbeddedJpeg ();
+diff -u -r ./Imaging/Tiff.cs ../../../FSpot/Imaging/Tiff.cs
+--- ./Imaging/Tiff.cs	2008-04-13 13:55:18.000000000 -0400
++++ ../../../FSpot/Imaging/Tiff.cs	2008-04-13 13:25:54.000000000 -0400
+@@ -1318,7 +1318,6 @@
+ 			return new DirectoryEntry (input, start, header_endian);
+ 		}
+ 		
+-#if false
+ 		public Cms.Profile GetProfile ()
+ 		{
+ 			Cms.ColorCIExyY whitepoint = new Cms.ColorCIExyY (0, 0, 0);
+@@ -1405,7 +1404,6 @@
+ 				
+ 			return new Cms.Profile (whitepoint, primaries, transfer);
+ 		}
+-#endif
+ 
+ 		public void Dump (string name) 
+ 		{
+@@ -1583,7 +1581,6 @@
+ 		}
+ 
+ 
+-#if false
+ 		public Gdk.Pixbuf LoadPixbuf (System.IO.Stream stream) 
+ 		{
+ 			Gdk.Pixbuf dest = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, false, width, height);
+@@ -1598,7 +1595,6 @@
+ 				}
+ 			}
+ 		}
+-#endif
+ 	}
+ #endif
+ 
+@@ -2108,7 +2104,6 @@
+ 			return file;
+ 		}
+ 
+-#if false
+ 		public Gdk.Pixbuf LoadJpegInterchangeFormat (ImageDirectory directory)
+ 		{
+ 			uint offset = directory.Lookup (TagId.JPEGInterchangeFormat).ValueAsLong [0];
+@@ -2135,7 +2130,6 @@
+ 				return result; 
+ 			}
+ 		}
+-#endif
+ 	}
+ 
+ 	public class DngFile : TiffFile {
+@@ -2266,7 +2260,6 @@
+ 			} while (i < sub.Directory.Length);
+ 		}
+ 
+-#if false
+ 		public Gdk.Pixbuf GetEmbeddedThumbnail ()
+ 		{
+ 			using (System.IO.Stream stream = Open ()) {
+@@ -2284,7 +2277,6 @@
+ 				return DCRawFile.RawPixbufStream (uri);
+ 			}
+ 		}
+-#endif
+ 	}
+ 		
+ 
+@@ -2304,14 +2296,13 @@
+ 		}
+ 		*/
+ 
+-#if false
+ 		public Gdk.Pixbuf GetEmbeddedThumbnail ()
+ 		{
+ 			ImageDirectory directory;
+ 			directory = Header.Directory.NextDirectory;
+ 			return TransformAndDispose (LoadJpegInterchangeFormat (directory));
+ 		}
+-#endif
++
+ 
+ 		public override System.IO.Stream PixbufStream ()
+ 		{
+diff -u -r ./PixbufUtils.cs ../../../FSpot/PixbufUtils.cs
+--- ./PixbufUtils.cs	2008-04-13 13:50:20.000000000 -0400
++++ ../../../FSpot/PixbufUtils.cs	2008-04-13 13:45:27.000000000 -0400
+@@ -1,8 +1,10 @@
++using Gdk;
+ using System.Collections;
+ using System.Runtime.InteropServices;
+ using System;
+ using System.IO;
+ using FSpot;
++using FSpot.Utils;
+ 
+ /**
+   1        2       3      4         5            6           7          8
+@@ -55,7 +57,6 @@
+ 		return orientation;
+ 	}
+ 
+-#if false
+ 	static Pixbuf error_pixbuf = null;
+ 	public static Pixbuf ErrorPixbuf {
+ 		get {
+@@ -940,5 +941,4 @@
+ 		return gtksharp_object_get_ref_count (obj.Handle);
+ 	}
+ #endif
+-#endif
+ }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]