Logo Search packages:      
Sourcecode: f-spot version File versions  Download package

Tiff.cs

//#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);
            }
            
            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);
            }

            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 != 
            }


            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

      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;
            }

            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; 
                  }
            }
      }

      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);
            }

            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);
                  }
            }
      }
            

      public class Cr2File : TiffFile, IThumbnailContainer {
            public Cr2File (string path) : base (path) 
            {
            }

            public Cr2File (Uri uri) : base (uri)
            {
            }

            /*
            public override PixbufOrientation GetOrientation ()
            {
                  return PixbufOrientation.TopLeft;
            }
            */

            public Gdk.Pixbuf GetEmbeddedThumbnail ()
            {
                  ImageDirectory directory;
                  directory = Header.Directory.NextDirectory;
                  return TransformAndDispose (LoadJpegInterchangeFormat (directory));
            }


            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
}


Generated by  Doxygen 1.6.0   Back to index