vbAccelerator - Contents of code file: EXIFListBox.cs

using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;

using vbAccelerator.Components.EXIF;

namespace vbAccelerator.Components.ListBox
{
   /// <summary>
   /// Provides progress information during
   /// asynchronous loading of items
   /// </summary>
   public class ProgressEventArgs
   {
      private int item = 0;
      private int count = 0;
      private bool complete = false;
      private string file = "";
      private bool cancel = false;

      /// <summary>
      /// Gets the item being processed
      /// </summary>
      public int Item
      {
         get
         {
            return this.item;
         }
      }
      /// <summary>
      /// Gets the total count of items to be processed
      /// </summary>
      public int Count
      {
         get
         {
            return this.count;
         }
      }
      /// <summary>
      /// Gets the file being processed
      /// </summary>
      public string File
      {
         get
         {
            return this.file;
         }
      }
      /// <summary>
      /// Gets/sets whether the operation should be cancelled
      /// </summary>
      public bool Cancel
      {
         get
         {
            return this.cancel;
         }
         set
         {
            this.cancel = value;
         }
      }
      /// <summary>
      /// Gets/sets whether this operation has been completed
      /// </summary>
      public bool Complete
      {
         get
         {
            return this.complete;
         }
      }
      public ProgressEventArgs(
         int item,
         int count,
         string file,
         bool complete
         )
      {
         this.item = item;
         this.count = count;
         this.file = file;
         this.complete = complete;
      }
   }

   #region Event Delegates
   public delegate void ProgressEventHandler(object sender, ProgressEventArgs
    e);
   #endregion

   /// <summary>
   /// Summary description for EXIFListBox.
   /// </summary>
   public class EXIFListBox : System.Windows.Forms.ListBox
   {
      #region Member Variables
      private Size thumbNailSize = new Size(100,100);
      private Stack fileStack = new Stack();
      private bool cancel = false;
      private int loadRun = 0;
      private int progressItem = 0;
      private int progressCount = 0;
      #endregion

      #region Events      
      public event ProgressEventHandler Progress;
      #endregion

      #region Properties
      /// <summary>
      /// Gets/sets the size of the thumbnail to create
      /// </summary>
      public Size ThumbNailSize
      {
         get
         {
            return this.thumbNailSize;
         }
         set
         {
            this.thumbNailSize = value;                  
            setItemHeight();
         }
      }
      /// <summary>
      /// Determines whether an asynchronous item loading operation
      /// is in progress
      /// </summary>
      public bool IsLoading
      {
         get
         {
            return (this.fileStack.Count > 0);
         }
      }
      #endregion

      #region Methods
      /// <summary>
      /// Cancels an asynchronous item load operation (if one is in
      /// progress)
      /// </summary>
      public void Cancel()
      {
         this.loadRun++;
         if (fileStack.Count > 0)
         {
            cancel = true;
         }
      }
      /// <summary>
      /// Adds all of the image files in the specified folder
      /// asynchronously
      /// </summary>
      /// <param name="folder">Folder to add files from</param>
      public void AddFolder(string folder)
      {
         cancel = false;
         loadRun++;
         progressItem = 0;
         fileStack.Clear();

         string[] files = Directory.GetFiles(folder, "*.*");
         foreach (string file in files)
         {
            fileStack.Push(file);
         }
         progressCount = fileStack.Count;
         processFile();
      }

      /// <summary>
      /// Adds the specified image file asynchronously
      /// </summary>
      /// <param name="file">Image file to add</param>
      public void AddFile(string file)
      {
         cancel = false;
         loadRun++;
         progressItem = 0;
         fileStack.Clear();

         fileStack.Push(file);
         progressCount = fileStack.Count;
         processFile();

      }
      #endregion

      #region Asynchronous Loading
      private void processFile()
      {
         if (cancel)
         {
            fileStack.Clear();
            cancel = false;
         }
         else
         {
            if (fileStack.Count == 0)
            {
               // complete
               cancel = false;
               if (Progress != null)
               {
                  ProgressEventArgs p = new ProgressEventArgs(
                     progressItem,
                     progressCount,
                     "", 
                     true);
                  Progress(this, p);
               }
            }
            else
            {
               string file = (string)fileStack.Pop();
               progressItem++;
               if (Progress != null)
               {
                  ProgressEventArgs p = new ProgressEventArgs(
                     progressItem,
                     progressCount,
                     file, 
                     false);
                  Progress(this, p);
                  if (p.Cancel)
                  {
                     fileStack.Clear();
                     cancel = false;
                  }
               }
               // This method runs on another thread:
               ProcessFileHandler pfh = new
                ProcessFileHandler(processFileBackground);
               pfh.BeginInvoke(loadRun, file, this.thumbNailSize, null, null);
            }
         }
      }

      private delegate void ProcessFileHandler(int loadRun, string file, Size
       thumbNailSize);

      private void processFileBackground(int loadRun, string file, Size
       thumbNailSize)
      {
         try
         {
            // Do the long operation of loading image, getting thumbnail, 
            // reading EXIF properties:
            EXIFListBoxItem item = new EXIFListBoxItem(file, thumbNailSize);
   
            // Now call back to the control.  Must use BeginInvoke to do this
            // in order to call back on the correct thread.
            object[] pList = { loadRun, item };
            this.BeginInvoke(new
             ProcessFileCompleteHandler(processFileComplete), pList);
         }
         catch 
         {
            // The image couldn't be loaded, so proceed to the next one
            object[] pList = { loadRun, null };
            this.BeginInvoke(new
             ProcessFileCompleteHandler(processFileComplete), pList);
         }

      }

      private delegate void ProcessFileCompleteHandler(int loadRun,
       EXIFListBoxItem item);

      private void processFileComplete(int loadRun, EXIFListBoxItem ef)
      {
         // add this item to the list box if valid:
         if ((ef != null) && (loadRun == this.loadRun))
         {
            this.Items.Add(ef);
         }
         // do the next file
         processFile();
      }
      #endregion

      #region Drawing Code
      protected override void OnDrawItem (
       System.Windows.Forms.DrawItemEventArgs e )
      {
         if ((e.Index > -1) && (e.Index < this.Items.Count))
         {
            // Owner draw ListBoxes tend to flicker unless you
            // draw to an off-screen bitmap
            Bitmap bmOffscreen = new Bitmap(e.Bounds.Width, e.Bounds.Height);
            Graphics gfx = Graphics.FromImage(bmOffscreen);
            Rectangle bounds = new Rectangle(0, 0, e.Bounds.Width,
             e.Bounds.Height);

            // Fill the background of the item
            Brush brBackground = SystemBrushes.Window;
            gfx.FillRectangle(brBackground, bounds);

            bool selected = ((e.State & DrawItemState.Selected) ==
             DrawItemState.Selected);
            if (selected)
            {
               Color brushColor = Color.FromArgb(128,
                Color.FromKnownColor(KnownColor.Highlight));
               brBackground = new SolidBrush(brushColor);
               gfx.FillRectangle(brBackground, bounds);
               brBackground.Dispose();
            }

            // Get the EXIF item we're working on
            EXIFListBoxItem item = (EXIFListBoxItem )this.Items[e.Index];
            SizeF size = e.Graphics.MeasureString("Xg", this.Font, 128);

            // Draw the file name:
            RectangleF textRect = new RectangleF(
               bounds.X + 2,
               bounds.Y + 2,
               bounds.Width - 4,
               size.Height);
            Color titleColor = Color.FromKnownColor(KnownColor.Highlight);
            if (!selected)
            {
               titleColor = Color.FromArgb(128,
                Color.FromKnownColor(KnownColor.Highlight));
            }
            Brush titleBrush = new SolidBrush(titleColor);
            gfx.FillRectangle(titleBrush, textRect);
            if (selected)
            {
               gfx.DrawRectangle(SystemPens.ControlDarkDark, (int)textRect.X,
                (int)textRect.Y, (int)textRect.Width, (int)textRect.Height);
            }
            else
            {
               gfx.DrawRectangle(SystemPens.Highlight, (int)textRect.X,
                (int)textRect.Y, (int)textRect.Width, (int)textRect.Height);
            }
            titleBrush.Dispose();
                                    
            Brush textBrush;
            if (selected)
            {
               textBrush = SystemBrushes.HighlightText;
            }
            else
            {
               textBrush = SystemBrushes.WindowText;
            }            
            StringFormat textFormat = new StringFormat();
            textFormat.Alignment = StringAlignment.Near;
            textFormat.Trimming = StringTrimming.EllipsisPath;
            textFormat.FormatFlags = StringFormatFlags.LineLimit;
            gfx.DrawString(
               Path.GetFileName(item.FileName),
               this.Font,
               textBrush,
               textRect,
               textFormat);
            textFormat.Dispose();

            // Draw the thumbnail:
            gfx.DrawImageUnscaled(
               item.ThumbNail, 
               bounds.X + 2, 
               bounds.Y + 2 + (int)size.Height + 2);

            // Draw the image size & colour depth:
            textRect.Y = bounds.Y + 2 + (int)size.Height + 2 +
             item.ThumbNail.Height + 2;
            textRect.Height = (int)size.Height * 2;
            gfx.DrawString(
               String.Format("Size: {0} x {1}\r\nBit Depth {2}",
                  item.Size.Width, item.Size.Height,
                  bitDepthForDisplay(item.PixelFormat)),
               this.Font,
               textBrush,
               textRect);               

            // Draw the EXIF Properties. Here we try for
            // HorzRes, VertRes, DatePictureTaken, FrameCount, 
            // EquipmentMake, CameraModel,
            // CreationSoftware, ColourRepresentation, FlashMode, FocalLength
            // F-Number and ExposureTime:
            textRect.X = bounds.X + item.ThumbNail.Width + 4;
            textRect.Y = bounds.Y + 2 + (int)size.Height + 2;
            textRect.Width = bounds.Width - 4 - textRect.X;
            textRect.Height = bounds.Height - 4;

            addProperty(gfx, "Horizontal Resolution",
             item.HorizontalResolution, ref textRect, (int)size.Height);
            addProperty(gfx, "Vertical Resolution", item.VerticalResolution,
             ref textRect, (int)size.Height);
            addProperty(gfx, "Date Picture Taken", item.DatePictureTaken, ref
             textRect, (int)size.Height);

            addProperty(gfx, "Equipment Make", item.Manufacturer, ref textRect,
             (int)size.Height);
            addProperty(gfx, "Model", item.Model, ref textRect,
             (int)size.Height);
            addProperty(gfx, "Creation Software", item.SoftwareVersion, ref
             textRect, (int)size.Height);
            
            addProperty(gfx, "Flash Mode", item.FlashMode, ref textRect,
             (int)size.Height);
            addProperty(gfx, "Focal Length", item.FocalLength, ref textRect,
             (int)size.Height);            
            addProperty(gfx, "F-Number", item.Aperture, ref textRect,
             (int)size.Height);
            addProperty(gfx, "Exposure Time", item.ExposureTime, ref textRect,
             (int)size.Height);

            // Swap off screen graphics to display:
            e.Graphics.DrawImageUnscaled(bmOffscreen, e.Bounds.X, e.Bounds.Y);
            bmOffscreen.Dispose();
            gfx.Dispose();
         }
         else
         {
            base.OnDrawItem(e);
         }
      }

      private void addProperty(
         Graphics gfx,
         string propName,
         string propValue,
         ref RectangleF textRect,
         int height
         )
      {
         if (propValue.Length > 0)
         {
            gfx.DrawString(
               String.Format("{0} : {1}", propName, propValue),
               this.Font,
               SystemBrushes.WindowText,
               textRect);            
            textRect.Y += height;
         }
      }

      private string bitDepthForDisplay(PixelFormat pixelFormat)
      {
         string ans = "";
         switch (pixelFormat)
         {
            case PixelFormat.Format16bppArgb1555:
            case PixelFormat.Format16bppGrayScale:
            case PixelFormat.Format16bppRgb555:
            case PixelFormat.Format16bppRgb565:
               ans = "16 bit";
               break;
            case PixelFormat.Format1bppIndexed:
               ans = "Black and White";
               break;
            case PixelFormat.Format24bppRgb:
               ans = "24 bit";
               break;
            case PixelFormat.Format32bppArgb:
            case PixelFormat.Format32bppPArgb:
            case PixelFormat.Format32bppRgb:
               ans = "32 bit";
               break;
            case PixelFormat.Format48bppRgb:
               ans = "48 bit";
               break;
            case PixelFormat.Format4bppIndexed:
               ans = "16 colour";
               break;
            case PixelFormat.Format64bppArgb:
            case PixelFormat.Format64bppPArgb:
               ans = "64 bit";
               break;
            case PixelFormat.Format8bppIndexed:
               ans = "256 bit";
               break;            
         }
         return ans;
      }
      #endregion

      #region General Implementation Details
      protected override void OnFontChanged ( System.EventArgs e )
      {
         setItemHeight();
         base.OnFontChanged(e);
      }

      private void setItemHeight()
      {
         Bitmap bm = new Bitmap(48,48);
         Graphics gfx = Graphics.FromImage(bm);
         SizeF size = gfx.MeasureString("Xg", this.Font, 128);
         this.ItemHeight = (int)Math.Max(
            9 * size.Height, 
            this.thumbNailSize.Height + size.Height * 3 + 6
            );
         gfx.Dispose();
         bm.Dispose();
      }
      #endregion

      #region Constructor, Dispose
      public EXIFListBox() : base()
      {
         this.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
      }
      #endregion
   }


   public class EXIFListBoxItem : ReadOnlyCollectionBase
   {
      private string fileName = "";
      private Bitmap thumbNail = null;
      private System.Drawing.Imaging.PixelFormat pixelFormat =
       PixelFormat.DontCare;
      private System.Drawing.Size size;

      public Bitmap ThumbNail
      {
         get
         {
            return this.thumbNail;
         }
      }

      public string FileName
      {
         get
         {
            return this.fileName;
         }
      }

      public string FlashMode
      {
         get
         {
            string ret = "";
            EXIFPropertyItem flash = ItemForEXIFCode(KnownEXIFIDCodes.Flash);
            if (flash != null)
            {
               try
               {
                  switch (flash.Value[0])
                  {
                     case 0:
                        ret = "No Flash";
                        break;
                     case 1:
                        ret = "Flash";
                        break;
                     case 5:
                        ret = "Flash Fired, No Strobe Return";
                        break;
                     case 7:
                        ret = "Flash With Strobe Return";
                        break;
                  }
               }
               catch {}
            }
            return ret;
         }
      }

      public string FocalLength
      {
         get
         {
            string ret = "";
            EXIFPropertyItem focalLength =
             ItemForEXIFCode(KnownEXIFIDCodes.FocalLength);
            if (focalLength != null)
            {
               try
               {
                  EXIFRational focalLengthFraction = focalLength.ParsedRational;
                  ret = string.Format("{0:N0} mm", 
                     focalLengthFraction.Numerator * 1.0 /
                      focalLengthFraction.Denominator);
               }
               catch {}
            }
            return ret;
         }
      }

      public string ExposureTime
      {
         get
         {
            string ret = "";
            EXIFPropertyItem exposure =
             ItemForEXIFCode(KnownEXIFIDCodes.ExposureTime);
            if (exposure != null)
            {
               try
               {
                  EXIFRational exposureTime = exposure.ParsedRational;
                  ret = string.Format("{0}/{1} s",
                     exposureTime.Numerator, exposureTime.Denominator);
               }
               catch {}
            }
            return ret;
         }
      }

      public string Aperture
      {
         get
         {
            string ret = "";
            EXIFPropertyItem aperture =
             ItemForEXIFCode(KnownEXIFIDCodes.ApertureValue);
            if (aperture != null)
            {
               try
               {
                  EXIFRational apertureFraction = aperture.ParsedRational;
                  double apertureValue = Math.Pow(
                     Math.Sqrt(2.0), 
                     (apertureFraction.Numerator * 1.0 /
                      apertureFraction.Denominator)
                     );
                  ret = string.Format("{0:F1}", apertureValue);
               }
               catch 
               {
               }
            }
            return ret;
         }
      }

      public string Model
      {
         get
         {
            return parseEXIFString(KnownEXIFIDCodes.Model);
         }
      }

      public string Manufacturer
      {
         get
         {
            return parseEXIFString(KnownEXIFIDCodes.Manufacturer);
         }
      }

      public string SoftwareVersion
      {
         get
         {
            return parseEXIFString(KnownEXIFIDCodes.Software);
         }
      }

      public string HorizontalResolution
      {
         get
         {
            string ret = parseResolution(KnownEXIFIDCodes.XResolution);
            return ret;
         }
      }

      public string VerticalResolution
      {
         get
         {
            string ret = parseResolution(KnownEXIFIDCodes.YResolution);
            return ret;
         }
      }
      public string DatePictureTaken
      {
         get
         {
            return parseEXIFDateTime(KnownEXIFIDCodes.DateTimeOriginal);   
         }
      }

      private string parseEXIFDateTime(KnownEXIFIDCodes dateItem)
      {
         string ret = "";
         EXIFPropertyItem item = ItemForEXIFCode(dateItem);
         if (item != null)
         {
            try
            {
               DateTime dt = item.ParsedDate;
               ret = String.Format("{0:d} {1:T}", dt, dt);
            }
            catch {}
         }
         return ret;
      }
   
      private string parseEXIFString(KnownEXIFIDCodes stringItem)
      {
         string ret = "";
         EXIFPropertyItem item = ItemForEXIFCode(stringItem);
         if (item != null)
         {
            try
            {
               ret = item.ParsedString;
            }
            catch {}
         }
         return ret;
      }

      private string parseResolution(KnownEXIFIDCodes resolutionAxis)
      {
         string ret = "";
         EXIFPropertyItem resUnit =
          ItemForEXIFCode(KnownEXIFIDCodes.ResolutionUnit);
         EXIFPropertyItem res = ItemForEXIFCode(resolutionAxis);
         if ((resUnit != null) && (res != null))
         {
            try
            {
               EXIFRational resFraction = res.ParsedRational;
               double resValue =  resFraction.Numerator * 1.0 /
                resFraction.Denominator;
               ret = String.Format("{0:N0}", resValue);
               switch (res.Value[0])
               {
                  case 2:
                     ret += " dpi";
                     break;
                  case 3:
                     ret += " dpcm";
                     break;
                  default:
                     ret += " dpi";
                     break;
               }
            }
            catch {}
         }
         return ret;
      }

      public EXIFPropertyItem ItemForEXIFCode(KnownEXIFIDCodes code)
      {
         EXIFPropertyItem ret = null;
         foreach (EXIFPropertyItem p in this.InnerList)
         {
            if (p.EXIFCode == code)
            {
               ret = p;
               break;
            }
         }
         return ret;
      }

      public EXIFPropertyItem this[int index]
      {
         get
         {
            return (EXIFPropertyItem)this.InnerList[index];
         }
      }

      public System.Drawing.Imaging.PixelFormat PixelFormat
      {
         get
         {
            return this.pixelFormat;
         }
      }

      public System.Drawing.Size Size
      {
         get
         {
            return this.size;
         }
      }

      public EXIFListBoxItem(string file, Size thumbNailSize)
      {
         Image img = Image.FromFile(file);
         this.pixelFormat = img.PixelFormat;
         this.size = img.Size;

         this.fileName = file;
         Console.WriteLine("{0}", file);
         
         int[] propIds = img.PropertyIdList;
         foreach( int id in propIds )
         {
            try 
            {
               PropertyItem prop = img.GetPropertyItem(id);
               EXIFPropertyItem exifProp = new EXIFPropertyItem(prop);
               this.InnerList.Add(exifProp);
            }
            catch
            {
               // can't read this property
            }
         }

         // Create the thumbnail
         this.thumbNail = new Bitmap(thumbNailSize.Width, thumbNailSize.Height);
         Graphics gfx = Graphics.FromImage(this.thumbNail);
         gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
         
         // Determine scaling for new rectangle:
         double xScale = (1.0 * img.Width) / thumbNail.Width;
         double yScale = (1.0 * img.Height) / thumbNail.Height;
         Rectangle destRect;
         if (xScale > yScale)
         {
            if (img.Height / xScale < thumbNail.Height)
            {
               destRect = new Rectangle(0, 0, (int)(img.Width / xScale),
                (int)(img.Height / xScale));
            }
            else
            {
               destRect = new Rectangle(0, 0, (int)(img.Width / yScale),
                (int)(img.Height / yScale));
            }
         }
         else
         {
            if (img.Width / yScale < thumbNail.Width)
            {
               destRect = new Rectangle(0, 0, (int)(img.Width / yScale),
                (int)(img.Height / yScale));
            }
            else
            {
               destRect = new Rectangle(0, 0, (int)(img.Width / xScale),
                (int)(img.Height / xScale));
            }
         }
         gfx.DrawImage(img, destRect, 0, 0, img.Width, img.Height,
          GraphicsUnit.Pixel);
         
         gfx.Dispose();
         img.Dispose();
      }
   }
}