/// -----------------------------------------------------------------------------
/// <summary>
/// Utility class for reading EXIF data from images. Provides abstraction
/// for most common data and generic utilities for work with all other.
/// </summary>
/// <remarks>
/// Copyright (c) Michal A. Valášek - Altair Communications, 2003
/// Copmany: http://software.altaircom.net * support@altaircom.net
/// Private: http://www.rider.cz * developer@rider.cz
/// This is free software licensed under GNU Lesser General Public License
/// </remarks>
/// <history>
/// [altair] 	10.9.2003	Created
/// </history>
/// -----------------------------------------------------------------------------
using System;
using System.Drawing;
using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;

namespace ImageCatalog
{
    public class ExifReader : IDisposable
    {
        private Bitmap Image;

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Contains possible values of EXIF tag names (ID)
    /// </summary>
    /// <remarks>See GdiPlusImaging.h</remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public enum TagNames
        {
            ExifIFD = 0x8769,
            GpsIFD = 0x8825,
            NewSubfileType = 0xFE,
            SubfileType = 0xFF,
            ImageWidth = 0x100,
            ImageHeight = 0x101,
            BitsPerSample = 0x102,
            Compression = 0x103,
            PhotometricInterp = 0x106,
            ThreshHolding = 0x107,
            CellWidth = 0x108,
            CellHeight = 0x109,
            FillOrder = 0x10A,
            DocumentName = 0x10D,
            ImageDescription = 0x10E,
            EquipMake = 0x10F,
            EquipModel = 0x110,
            StripOffsets = 0x111,
            Orientation = 0x112,
            SamplesPerPixel = 0x115,
            RowsPerStrip = 0x116,
            StripBytesCount = 0x117,
            MinSampleValue = 0x118,
            MaxSampleValue = 0x119,
            XResolution = 0x11A,
            YResolution = 0x11B,
            PlanarConfig = 0x11C,
            PageName = 0x11D,
            XPosition = 0x11E,
            YPosition = 0x11F,
            FreeOffset = 0x120,
            FreeByteCounts = 0x121,
            GrayResponseUnit = 0x122,
            GrayResponseCurve = 0x123,
            T4Option = 0x124,
            T6Option = 0x125,
            ResolutionUnit = 0x128,
            PageNumber = 0x129,
            TransferFuncition = 0x12D,
            SoftwareUsed = 0x131,
            DateTime = 0x132,
            Artist = 0x13B,
            HostComputer = 0x13C,
            Predictor = 0x13D,
            WhitePoint = 0x13E,
            PrimaryChromaticities = 0x13F,
            ColorMap = 0x140,
            HalftoneHints = 0x141,
            TileWidth = 0x142,
            TileLength = 0x143,
            TileOffset = 0x144,
            TileByteCounts = 0x145,
            InkSet = 0x14C,
            InkNames = 0x14D,
            NumberOfInks = 0x14E,
            DotRange = 0x150,
            TargetPrinter = 0x151,
            ExtraSamples = 0x152,
            SampleFormat = 0x153,
            SMinSampleValue = 0x154,
            SMaxSampleValue = 0x155,
            TransferRange = 0x156,
            JPEGProc = 0x200,
            JPEGInterFormat = 0x201,
            JPEGInterLength = 0x202,
            JPEGRestartInterval = 0x203,
            JPEGLosslessPredictors = 0x205,
            JPEGPointTransforms = 0x206,
            JPEGQTables = 0x207,
            JPEGDCTables = 0x208,
            JPEGACTables = 0x209,
            YCbCrCoefficients = 0x211,
            YCbCrSubsampling = 0x212,
            YCbCrPositioning = 0x213,
            REFBlackWhite = 0x214,
            ICCProfile = 0x8773,
            Gamma = 0x301,
            ICCProfileDescriptor = 0x302,
            SRGBRenderingIntent = 0x303,
            ImageTitle = 0x320,
            Copyright = 0x8298,
            ResolutionXUnit = 0x5001,
            ResolutionYUnit = 0x5002,
            ResolutionXLengthUnit = 0x5003,
            ResolutionYLengthUnit = 0x5004,
            PrintFlags = 0x5005,
            PrintFlagsVersion = 0x5006,
            PrintFlagsCrop = 0x5007,
            PrintFlagsBleedWidth = 0x5008,
            PrintFlagsBleedWidthScale = 0x5009,
            HalftoneLPI = 0x500A,
            HalftoneLPIUnit = 0x500B,
            HalftoneDegree = 0x500C,
            HalftoneShape = 0x500D,
            HalftoneMisc = 0x500E,
            HalftoneScreen = 0x500F,
            JPEGQuality = 0x5010,
            GridSize = 0x5011,
            ThumbnailFormat = 0x5012,
            ThumbnailWidth = 0x5013,
            ThumbnailHeight = 0x5014,
            ThumbnailColorDepth = 0x5015,
            ThumbnailPlanes = 0x5016,
            ThumbnailRawBytes = 0x5017,
            ThumbnailSize = 0x5018,
            ThumbnailCompressedSize = 0x5019,
            ColorTransferFunction = 0x501A,
            ThumbnailData = 0x501B,
            ThumbnailImageWidth = 0x5020,
            ThumbnailImageHeight = 0x502,
            ThumbnailBitsPerSample = 0x5022,
            ThumbnailCompression = 0x5023,
            ThumbnailPhotometricInterp = 0x5024,
            ThumbnailImageDescription = 0x5025,
            ThumbnailEquipMake = 0x5026,
            ThumbnailEquipModel = 0x5027,
            ThumbnailStripOffsets = 0x5028,
            ThumbnailOrientation = 0x5029,
            ThumbnailSamplesPerPixel = 0x502A,
            ThumbnailRowsPerStrip = 0x502B,
            ThumbnailStripBytesCount = 0x502C,
            ThumbnailResolutionX = 0x502D,
            ThumbnailResolutionY = 0x502E,
            ThumbnailPlanarConfig = 0x502F,
            ThumbnailResolutionUnit = 0x5030,
            ThumbnailTransferFunction = 0x5031,
            ThumbnailSoftwareUsed = 0x5032,
            ThumbnailDateTime = 0x5033,
            ThumbnailArtist = 0x5034,
            ThumbnailWhitePoint = 0x5035,
            ThumbnailPrimaryChromaticities = 0x5036,
            ThumbnailYCbCrCoefficients = 0x5037,
            ThumbnailYCbCrSubsampling = 0x5038,
            ThumbnailYCbCrPositioning = 0x5039,
            ThumbnailRefBlackWhite = 0x503A,
            ThumbnailCopyRight = 0x503B,
            LuminanceTable = 0x5090,
            ChrominanceTable = 0x5091,
            FrameDelay = 0x5100,
            LoopCount = 0x5101,
            PixelUnit = 0x5110,
            PixelPerUnitX = 0x5111,
            PixelPerUnitY = 0x5112,
            PaletteHistogram = 0x5113,
            ExifExposureTime = 0x829A,
            ExifFNumber = 0x829D,
            ExifExposureProg = 0x8822,
            ExifSpectralSense = 0x8824,
            ExifISOSpeed = 0x8827,
            ExifOECF = 0x8828,
            ExifVer = 0x9000,
            ExifDTOrig = 0x9003,
            ExifDTDigitized = 0x9004,
            ExifCompConfig = 0x9101,
            ExifCompBPP = 0x9102,
            ExifShutterSpeed = 0x9201,
            ExifAperture = 0x9202,
            ExifBrightness = 0x9203,
            ExifExposureBias = 0x9204,
            ExifMaxAperture = 0x9205,
            ExifSubjectDist = 0x9206,
            ExifMeteringMode = 0x9207,
            ExifLightSource = 0x9208,
            ExifFlash = 0x9209,
            ExifFocalLength = 0x920A,
            ExifMakerNote = 0x927C,
            ExifUserComment = 0x9286,
            ExifDTSubsec = 0x9290,
            ExifDTOrigSS = 0x9291,
            ExifDTDigSS = 0x9292,
            ExifFPXVer = 0xA000,
            ExifColorSpace = 0xA001,
            ExifPixXDim = 0xA002,
            ExifPixYDim = 0xA003,
            ExifRelatedWav = 0xA004,
            ExifInterop = 0xA005,
            ExifFlashEnergy = 0xA20B,
            ExifSpatialFR = 0xA20C,
            ExifFocalXRes = 0xA20E,
            ExifFocalYRes = 0xA20F,
            ExifFocalResUnit = 0xA210,
            ExifSubjectLoc = 0xA214,
            ExifExposureIndex = 0xA215,
            ExifSensingMethod = 0xA217,
            ExifFileSource = 0xA300,
            ExifSceneType = 0xA301,
            ExifCfaPattern = 0xA302,
            GpsVer = 0x0,
            GpsLatitudeRef = 0x1,
            GpsLatitude = 0x2,
            GpsLongitudeRef = 0x3,
            GpsLongitude = 0x4,
            GpsAltitudeRef = 0x5,
            GpsAltitude = 0x6,
            GpsGpsTime = 0x7,
            GpsGpsSatellites = 0x8,
            GpsGpsStatus = 0x9,
            GpsGpsMeasureMode = 0xA,
            GpsGpsDop = 0xB,
            GpsSpeedRef = 0xC,
            GpsSpeed = 0xD,
            GpsTrackRef = 0xE,
            GpsTrack = 0xF,
            GpsImgDirRef = 0x10,
            GpsImgDir = 0x11,
            GpsMapDatum = 0x12,
            GpsDestLatRef = 0x13,
            GpsDestLat = 0x14,
            GpsDestLongRef = 0x15,
            GpsDestLong = 0x16,
            GpsDestBearRef = 0x17,
            GpsDestBear = 0x18,
            GpsDestDistRef = 0x19,
            GpsDestDist = 0x1A
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Real position of 0th row and column of picture
    /// </summary>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public enum Orientations
        {
            TopLeft = 1,
            TopRight = 2,
            BottomRight = 3,
            BottomLeft = 4,
            LeftTop = 5,
            RightTop = 6,
            RightBottom = 7,
            LftBottom = 8
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Exposure programs
    /// </summary>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public enum ExposurePrograms
        {
            Manual = 1,
            Normal = 2,
            AperturePriority = 3,
            ShutterPriority = 4,
            Creative = 5,
            Action = 6,
            Portrait = 7,
            Landscape = 8
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Exposure metering modes
    /// </summary>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public enum ExposureMeteringModes
        {
            Unknown = 0,
            Average = 1,
            CenterWeightedAverage = 2,
            Spot = 3,
            MultiSpot = 4,
            MultiSegment = 5,
            Partial = 6,
            Other = 255
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Flash activity modes
    /// </summary>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public enum FlashModes
        {
            NotFired = 0,
            Fired = 1,
            FiredButNoStrobeReturned = 5,
            FiredAndStrobeReturned = 7
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Possible light sources (white balance)
    /// </summary>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public enum LightSources
        {
            Unknown = 0,
            Daylight = 1,
            Fluorescent = 2,
            Tungsten = 3,
            Flash = 10,
            StandardLightA = 17,
            StandardLightB = 18,
            StandardLightC = 19,
            D55 = 20,
            D65 = 21,
            D75 = 22,
            Other = 255
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Represents rational which is type of some Exif properties
    /// </summary>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public struct Rational
        {
            public int Numerator;
            public int Denominator;

            /// -----------------------------------------------------------------------------
        /// <summary>
        /// Converts rational to string representation
        /// </summary>
        /// <param name="Delimiter">Optional, default "/". String to be used as delimiter of components.</param>
        /// <returns>String representation of the rational.</returns>
        /// <remarks></remarks>
        /// <history>
        /// [altair] 	10.9.2003	Created
        /// </history>
        /// -----------------------------------------------------------------------------
            public new string ToString(string Delimiter = "/")
            {
                return Numerator + Delimiter + Denominator;
            }

            /// -----------------------------------------------------------------------------
        /// <summary>
        /// Converts rational to double precision real number
        /// </summary>
        /// <returns>The rational as double precision real number.</returns>
        /// <remarks></remarks>
        /// <history>
        /// [altair] 	10.9.2003	Created
        /// </history>
        /// -----------------------------------------------------------------------------
            public double ToDouble()
            {
                return Numerator / (double)Denominator;
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Initializes new instance of this class.
    /// </summary>
    /// <param name="Bitmap">Bitmap to read exif information from</param>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public ExifReader(Bitmap Bitmap)
        {
            if (Bitmap is null)
                throw new ArgumentNullException("Bitmap");
            Image = Bitmap;
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Returns all available data in formatted string form
    /// </summary>
    /// <returns></returns>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public override string ToString()
        {
            var SB = new System.Text.StringBuilder();
            SB.Append("Image:");
            SB.Append(@"\n\tDimensions:        " + Width + " x " + Height + " px");
            SB.Append(@"\n\tResolution:        " + ResolutionX + " x " + ResolutionY + " dpi");
            SB.Append(@"\n\tOrientation:       " + Enum.GetName(typeof(Orientations), Orientation));
            SB.Append(@"\n\tTitle:             " + Title);
            SB.Append(@"\n\tDescription:       " + Description);
            SB.Append(@"\n\tCopyright:         " + Copyright);
            SB.Append(@"\nEquipment:");
            SB.Append(@"\n\tMaker:             " + EquipmentMaker);
            SB.Append(@"\n\tModel:             " + EquipmentModel);
            SB.Append(@"\n\tSoftware:          " + Software);
            SB.Append(@"\nDate and time:");
            SB.Append(@"\n\tGeneral:           " + DateTimeLastModified.ToString());
            SB.Append(@"\n\tOriginal:          " + DateTimeOriginal.ToString());
            SB.Append(@"\n\tDigitized:         " + DateTimeDigitized.ToString());
            SB.Append(@"\nShooting conditions:");
            SB.Append(@"\n\tExposure time:     " + ExposureTime.ToString("N4") + " s");
            SB.Append(@"\n\tExposure program:  " + Enum.GetName(typeof(ExposurePrograms), ExposureProgram));
            SB.Append(@"\n\tExposure mode:     " + Enum.GetName(typeof(ExposureMeteringModes), ExposureMeteringMode));
            SB.Append(@"\n\tAperture:          F" + Aperture.ToString("N2"));
            SB.Append(@"\n\tISO sensitivity:   " + ISO);
            SB.Append(@"\n\tSubject distance:  " + SubjectDistance.ToString("N2") + " m");
            SB.Append(@"\n\tFocal length:      " + FocalLength);
            SB.Append(@"\n\tFlash:             " + Enum.GetName(typeof(FlashModes), FlashMode));
            SB.Append(@"\n\tLight source (WB): " + Enum.GetName(typeof(LightSources), LightSource));
            SB.Append(@"\n\nCopyright (c) Michal A. Valasek - Altair Communications, 2003");
            SB.Append(@"\nhttp://software.altaircom.net * support@altaircom.net");
            SB.Replace(@"\n", Constants.vbCrLf);
            SB.Replace(@"\t", Constants.vbTab);
            return SB.ToString();
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Brand of equipment (EXIF EquipMake)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public string EquipmentMaker
        {
            get
            {
                return GetPropertyString((int)TagNames.EquipMake);
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Model of equipment (EXIF EquipModel)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public string EquipmentModel
        {
            get
            {
                return GetPropertyString((int)TagNames.EquipModel);
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Software used for processing (EXIF Software)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public string Software
        {
            get
            {
                return GetPropertyString((int)TagNames.SoftwareUsed);
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Orientation of image (position of row 0, column 0) (EXIF Orientation)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public Orientations Orientation
        {
            get
            {
                int X = GetPropertyInt16((int)TagNames.Orientation);
                if (!Enum.IsDefined(typeof(Orientations), X))
                {
                    return Orientations.TopLeft;
                }
                else
                {
                    return (Orientations)Conversions.ToInteger(Enum.Parse(typeof(Orientations), Enum.GetName(typeof(Orientations), X)));
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Time when image was last modified (EXIF DateTime).
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public DateTime DateTimeLastModified
        {
            get
            {
                try
                {
                    return DateTime.ParseExact(GetPropertyString((int)TagNames.DateTime), @"yyyy\:MM\:dd HH\:mm\:ss", null);
                }
                catch (Exception ex)
                {
                    return DateTime.MinValue;
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Time when image was taken (EXIF DateTimeOriginal).
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public DateTime DateTimeOriginal
        {
            get
            {
                try
                {
                    return DateTime.ParseExact(GetPropertyString((int)TagNames.ExifDTOrig), @"yyyy\:MM\:dd HH\:mm\:ss", null);
                }
                catch (Exception ex)
                {
                    return DateTime.MinValue;
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Time when image was digitized (EXIF DateTimeDigitized).
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public DateTime DateTimeDigitized
        {
            get
            {
                try
                {
                    return DateTime.ParseExact(GetPropertyString((int)TagNames.ExifDTDigitized), @"yyyy\:MM\:dd HH\:mm\:ss", null);
                }
                catch (Exception ex)
                {
                    return DateTime.MinValue;
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Image width
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public short Width
        {
            get
            {
                return GetPropertyInt16((int)TagNames.ImageWidth);
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Image height
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public short Height
        {
            get
            {
                return GetPropertyInt16((int)TagNames.ImageHeight);
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// X resolution in dpi (EXIF XResolution/ResolutionUnit)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public double ResolutionX
        {
            get
            {
                double R = GetPropertyRational((int)TagNames.XResolution).ToDouble();
                if (GetPropertyInt16((int)TagNames.ResolutionUnit) == 3)
                {
                    // -- resolution is in points/cm
                    return R * 2.54d;
                }
                else
                {
                    // -- resolution is in points/inch
                    return R;
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Y resolution in dpi (EXIF YResolution/ResolutionUnit)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public double ResolutionY
        {
            get
            {
                double R = GetPropertyRational((int)TagNames.YResolution).ToDouble();
                if (GetPropertyInt16((int)TagNames.ResolutionUnit) == 3)
                {
                    // -- resolution is in points/cm
                    return R * 2.54d;
                }
                else
                {
                    // -- resolution is in points/inch
                    return R;
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Image title (EXIF ImageTitle)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public string Title
        {
            get
            {
                return GetPropertyString((int)TagNames.ImageTitle);
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Image description (EXIF ImageDescription)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public string Description
        {
            get
            {
                return GetPropertyString((int)TagNames.ImageDescription);
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Image copyright (EXIF Copyright)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public string Copyright
        {
            get
            {
                return GetPropertyString((int)TagNames.Copyright);
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Exposure time in seconds (EXIF ExifExposureTime/ExifShutterSpeed)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public double ExposureTime
        {
            get
            {
                if (IsPropertyDefined((int)TagNames.ExifExposureTime))
                {
                    // -- Exposure time is explicitly specified
                    return GetPropertyRational((int)TagNames.ExifExposureTime).ToDouble();
                }
                else if (IsPropertyDefined((int)TagNames.ExifShutterSpeed))
                {
                    // -- Compute exposure time from shutter speed
                    return 1d / Math.Pow(2d, GetPropertyRational((int)TagNames.ExifShutterSpeed).ToDouble());
                }
                else
                {
                    // -- Can't figure out 
                    return 0d;
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Aperture value as F number (EXIF ExifFNumber/ExifApertureValue)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public double Aperture
        {
            get
            {
                if (IsPropertyDefined((int)TagNames.ExifFNumber))
                {
                    return GetPropertyRational((int)TagNames.ExifFNumber).ToDouble();
                }
                else if (IsPropertyDefined((int)TagNames.ExifAperture))
                {
                    return Math.Pow(Math.Sqrt(2d), GetPropertyRational((int)TagNames.ExifAperture).ToDouble());
                }
                else
                {
                    return 0d;
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Exposure program used (EXIF ExifExposureProg)
    /// </summary>
    /// <value></value>
    /// <remarks>If not specified, returns Normal (2)</remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public ExposurePrograms ExposureProgram
        {
            get
            {
                int X = GetPropertyInt16((int)TagNames.ExifExposureProg);
                if (Enum.IsDefined(typeof(ExposurePrograms), X))
                {
                    return (ExposurePrograms)Conversions.ToInteger(Enum.Parse(typeof(ExposurePrograms), Enum.GetName(typeof(ExposurePrograms), X)));
                }
                else
                {
                    return ExposurePrograms.Normal;
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// ISO sensitivity
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public short ISO
        {
            get
            {
                return GetPropertyInt16((int)TagNames.ExifISOSpeed);
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Subject distance in meters (EXIF SubjectDistance)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public double SubjectDistance
        {
            get
            {
                return GetPropertyRational((int)TagNames.ExifSubjectDist).ToDouble();
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Exposure method metering mode used (EXIF MeteringMode)
    /// </summary>
    /// <value></value>
    /// <remarks>If not specified, returns Unknown (0)</remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public ExposureMeteringModes ExposureMeteringMode
        {
            get
            {
                int X = GetPropertyInt16((int)TagNames.ExifMeteringMode);
                if (Enum.IsDefined(typeof(ExposureMeteringModes), X))
                {
                    return (ExposureMeteringModes)Conversions.ToInteger(Enum.Parse(typeof(ExposureMeteringModes), Enum.GetName(typeof(ExposureMeteringModes), X)));
                }
                else
                {
                    return ExposureMeteringModes.Unknown;
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Focal length of lenses in mm (EXIF FocalLength)
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public double FocalLength
        {
            get
            {
                return GetPropertyRational((int)TagNames.ExifFocalLength).ToDouble();
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Flash mode (EXIF Flash)
    /// </summary>
    /// <value></value>
    /// <remarks>If not present, value NotFired (0) is returned</remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public FlashModes FlashMode
        {
            get
            {
                int X = GetPropertyInt16((int)TagNames.ExifFlash);
                if (Enum.IsDefined(typeof(FlashModes), X))
                {
                    return (FlashModes)Conversions.ToInteger(Enum.Parse(typeof(FlashModes), Enum.GetName(typeof(FlashModes), X)));
                }
                else
                {
                    return FlashModes.NotFired;
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Light source / white balance (EXIF LightSource)
    /// </summary>
    /// <value></value>
    /// <remarks>If not specified, returns Unknown (0).</remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public LightSources LightSource
        {
            get
            {
                int X = GetPropertyInt16((int)TagNames.ExifLightSource);
                if (Enum.IsDefined(typeof(LightSources), X))
                {
                    return (LightSources)Conversions.ToInteger(Enum.Parse(typeof(LightSources), Enum.GetName(typeof(LightSources), X)));
                }
                else
                {
                    return LightSources.Unknown;
                }
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Checks if current image has specified certain property
    /// </summary>
    /// <param name="PID"></param>
    /// <returns>True if image has specified property, False otherwise.</returns>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public bool IsPropertyDefined(int PID)
        {
            return Array.IndexOf(Image.PropertyIdList, PID) > -1;
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Gets specified Int32 property
    /// </summary>
    /// <param name="PID">Property ID</param>
    /// <param name="DefaultValue">Optional, default 0. Default value returned if property is not present.</param>
    /// <remarks>Value of property or DefaultValue if property is not present.</remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public int GetPropertyInt32(int PID, int DefaultValue = 0)
        {
            if (IsPropertyDefined(PID))
            {
                return GetInt32(Image.GetPropertyItem(PID).Value);
            }
            else
            {
                return DefaultValue;
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Gets specified Int16 property
    /// </summary>
    /// <param name="PID">Property ID</param>
    /// <param name="DefaultValue">Optional, default 0. Default value returned if property is not present.</param>
    /// <remarks>Value of property or DefaultValue if property is not present.</remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public short GetPropertyInt16(int PID, short DefaultValue = 0)
        {
            if (IsPropertyDefined(PID))
            {
                return GetInt16(Image.GetPropertyItem(PID).Value);
            }
            else
            {
                return DefaultValue;
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Gets specified string property
    /// </summary>
    /// <param name="PID">Property ID</param>
    /// <param name="DefaultValue">Optional, default String.Empty. Default value returned if property is not present.</param>
    /// <returns></returns>
    /// <remarks>Value of property or DefaultValue if property is not present.</remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public string GetPropertyString(int PID, string DefaultValue = "")
        {
            if (IsPropertyDefined(PID))
            {
                return GetString(Image.GetPropertyItem(PID).Value);
            }
            else
            {
                return DefaultValue;
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Gets specified rational property
    /// </summary>
    /// <param name="PID">Property ID</param>
    /// <returns></returns>
    /// <remarks>Value of property or 0/1 if not present.</remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public Rational GetPropertyRational(int PID)
        {
            if (IsPropertyDefined(PID))
            {
                return GetRational(Image.GetPropertyItem(PID).Value);
            }
            else
            {
                Rational R;
                R.Numerator = 0;
                R.Denominator = 1;
                return R;
            }
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Reads Int32 from EXIF bytearray.
    /// </summary>
    /// <param name="B">EXIF bytearray to process</param>
    /// <returns></returns>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public static int GetInt32(byte[] B)
        {
            if (B.Length < 4)
                throw new ArgumentException("Data too short (4 bytes expected)", "B");
            return B[3] << 24 | B[2] << 16 | B[1] << 8 | B[0];
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Reads Int16 from EXIF bytearray.
    /// </summary>
    /// <param name="B">EXIF bytearray to process</param>
    /// <returns></returns>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public static short GetInt16(byte[] B)
        {
            if (B.Length < 2)
                throw new ArgumentException("Data too short (2 bytes expected)", "B");
            return (short)(B[1] << 8 | B[0]);
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Reads string from EXIF bytearray.
    /// </summary>
    /// <param name="B">EXIF bytearray to process</param>
    /// <returns></returns>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public static string GetString(byte[] B)
        {
            string R = System.Text.Encoding.ASCII.GetString(B);
            if (R.EndsWith(Constants.vbNullChar))
                R = R.Substring(0, R.Length - 1);
            return R;
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Reads rational from EXIF bytearray.
    /// </summary>
    /// <param name="B">EXIF bytearray to process</param>
    /// <returns></returns>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public static Rational GetRational(byte[] B)
        {
            var R = new Rational();
            byte[] N = new byte[4], D = new byte[4];
            Array.Copy(B, 0, N, 0, 4);
            Array.Copy(B, 4, D, 0, 4);
            R.Denominator = GetInt32(D);
            R.Numerator = GetInt32(N);
            return R;
        }

        /// -----------------------------------------------------------------------------
    /// <summary>
    /// Disposes unmanaged resources of this class
    /// </summary>
    /// <remarks></remarks>
    /// <history>
    /// [altair] 	10.9.2003	Created
    /// </history>
    /// -----------------------------------------------------------------------------
        public void Dispose()
        {
            Image.Dispose();
        }
    }
}