Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Photo & Video Viewer with Encryption Capability

4.33/5 (14 votes)
18 Feb 2009CPOL3 min read 64.6K   2.9K  
A personal media viewer to view photos and videos using C# and Visual Studio 2008
Image 1

Introduction

I've tried a few image viewer utilities out there but couldn't find one that really fits my preference, so I've decided to write up one for my own use. I've got all the functionality I need but would like to get input from experts out there on a few issues.

The Problems with Common Image Utilities

  • Load photos as a thumbnail list with no option to switch to simple file name list. This would take forever when the folder contains several hundreds of photos. This is very common when unloading photos taken from a digital camera that has 1GB+ SD card.
  • The thumbnail list would reside in a wide view pane that takes up valuable view space for the main image plus the annoying double click to open a photo in the main view, then close and double click on another.

The Utility Features

There are too many features to list but the general idea is to make the photo list as narrow as possible and the main view as large as possible. Selecting a photo would display it in the main view using the default "Fit to screen" so user can see the whole picture without having to scroll right/down. A photo taken from a 6 mega pixels digital camera is typically 2576 x 1932 resolution. Once a photo is selected, the listview has focus and subsequent photo can be viewed by simply pressing the up/down key to select next/previous file.

Useful Imaging Code

C#
//
       static Image ScaleByPercent(Image imgPhoto, int Percent)
        {
            float nPercent = ((float)Percent / 100);

            int sourceWidth = imgPhoto.Width;
            int sourceHeight = imgPhoto.Height;
            int destWidth = (int)(sourceWidth * nPercent);
            int destHeight = (int)(sourceHeight * nPercent);

            Bitmap bmPhoto = new Bitmap(destWidth, destHeight, 
					PixelFormat.Format24bppRgb);
            bmPhoto.SetResolution(imgPhoto.HorizontalResolution, 
					imgPhoto.VerticalResolution);

            Graphics grPhoto = Graphics.FromImage(bmPhoto);
            grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;

            grPhoto.DrawImage(imgPhoto,
                new Rectangle(0, 0, destWidth, destHeight),
                new Rectangle(0, 0, sourceWidth, sourceHeight),
                GraphicsUnit.Pixel);

            grPhoto.Dispose();
            return bmPhoto;
        }

        static Image CreateThumbnail(Image imgPhoto)
        {
            Bitmap thumbBmp = new Bitmap(100, 100);
            Graphics g = Graphics.FromImage(thumbBmp);
            g.FillRectangle(Brushes.White, 0, 0, 100, 100);

            //Adjust settings to make this as high-quality as possible
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            g.InterpolationMode = 
		System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
            g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;

            int thumbWidth, thumbHeight;
            if (imgPhoto.Width >= imgPhoto.Height) 	// reduce width to 100 
						// then height proportionally
            {
                thumbWidth = 100;
                thumbHeight = (int)((double)imgPhoto.Height * 
					(100 / (double)imgPhoto.Width));
            }
            else // reduce height to 100 then height proportionally
            {
                thumbHeight = 100;
                thumbWidth = (int)((double)imgPhoto.Width * 
				(100 / (double)imgPhoto.Height));
            }

            // draw the original Image onto the empty Bitmap, 
	   // don't use GetThumbnailImage() from original
            // because it returns poor quality thumb
            int top = (100 - thumbHeight) / 2;
            int left = (100 - thumbWidth) / 2;

            g.DrawImage(imgPhoto, new Rectangle(left, top, thumbWidth, thumbHeight),
                new Rectangle(0, 0, imgPhoto.Width, imgPhoto.Height),
                GraphicsUnit.Pixel);

            g.Dispose();

            return thumbBmp;
        }

		  private static Image AlterBrightness(Image bmp, int level)
        {
            if (level == 50)
            {
                // do nothing
                return bmp;
            }

            Graphics graphics = Graphics.FromImage(bmp);
            if (level < 50)
            {
                // make it darker
                // Work out how much darker
                int conversion = 250 - (5 * level);
                Pen pDark = new Pen(Color.FromArgb(conversion, 0, 0, 0), bmp.Width * 2);
                graphics.DrawLine(pDark, 0, 0, bmp.Width, bmp.Height);
            }
            else if (level > 50)
            {
                // make it lighter
                // Work out how much lighter.
                int conversion = (5 * (level - 50));
                Pen pLight = new Pen(Color.FromArgb(conversion, 255, 255, 255), 
								bmp.Width * 2);
                graphics.DrawLine(pLight, 0, 0, bmp.Width, bmp.Height);
            }
            graphics.Save();
            graphics.Dispose();
            return bmp;
        }
//

Encryption/Decryption Code

C#
//
	// encryption key (stored in global variable _CurrentPassword) must be 
	// an 8 character string and is case sensitive, ex: myKey123
         private bool EncryptFile(string inputFile, string outputFile)
        {
            bool bSuccess = false;

            if (_CurrentPassword == "") return bSuccess;

            try
            {
                UnicodeEncoding UE = new UnicodeEncoding();
                byte[] key = UE.GetBytes(@_CurrentPassword);

                string cryptFile = outputFile;
                FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create);

                RijndaelManaged RMCrypto = new RijndaelManaged();

                CryptoStream cs = new CryptoStream(fsCrypt,
                    RMCrypto.CreateEncryptor(key, key),
                    CryptoStreamMode.Write);

                FileStream fsIn = new FileStream(inputFile, FileMode.Open);

                int data;
                while ((data = fsIn.ReadByte()) != -1)
                    cs.WriteByte((byte)data);


                fsIn.Close();
                cs.Close();
                fsCrypt.Close();

                bSuccess = true;
            }
            catch(Exception)
            {
                // nothing    
            }
            
            return bSuccess;
        }

	// decrypt file on the fly into MemoryStream without 
	// creating a physical file
	// useful for viewing an encrypted photo file directly 
	// ex: Bitmap orgImage = (Bitmap)Bitmap.FromStream
	// (DecryptFile(SomeFileName));
        private MemoryStream DecryptFile(string inputFile)
        {
            if (_CurrentPassword == "") return _MSErrorImage;

            FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);
            try
            {
                MemoryStream msOut = new MemoryStream();
                UnicodeEncoding UE = new UnicodeEncoding();
                byte[] key = UE.GetBytes(@_CurrentPassword);

                RijndaelManaged RMCrypto = new RijndaelManaged();

                CryptoStream cs = new CryptoStream
		(fsCrypt, RMCrypto.CreateDecryptor(key, key), CryptoStreamMode.Read);

                int data;
                while ((data = cs.ReadByte()) != -1)
                {
                    msOut.WriteByte((byte)data);
                }
                cs.Close();
                fsCrypt.Close();

                return msOut;
            }
            catch 
            {
                fsCrypt.Close(); // release file lock
                return _MSErrorImage;
            }
        }

		// decrypt into another physical file
        private bool DecryptFile(string inputFile, string outputFile)
        {
            bool bSuccess = false;
            if (_CurrentPassword == "") return bSuccess;

            FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);
            try
            {
                FileStream fsOut = new FileStream(outputFile, FileMode.Create);
                UnicodeEncoding UE = new UnicodeEncoding();
                byte[] key = UE.GetBytes(@_CurrentPassword);

                RijndaelManaged RMCrypto = new RijndaelManaged();

                CryptoStream cs = new CryptoStream
		(fsCrypt, RMCrypto.CreateDecryptor(key, key), CryptoStreamMode.Read);

                int data;
                while ((data = cs.ReadByte()) != -1)
                {
                    fsOut.WriteByte((byte)data);
                }
                cs.Close();
                fsCrypt.Close();
                fsOut.Close();
                bSuccess = true;
            }
            catch
            {
                fsCrypt.Close(); // release file lock
            }
            
            return bSuccess;
        }
//

Partial Thumbnails Creation

For thumbnail rendering, I use an approach similar to Stack's Pop.

  • The Listview is mapped to an ImageList in design view.
  • For thumbnail display mode, set all images in the ImageList to a default "Processing" image and store the list of pending (to be created) thumbnails in a custom object list.
  • The thumbnails creation process is run in a separate Thread. It loops through the list of pending thumbnails and processes 10 at a time (or less if less than 10 remaining), then exits and calls a function that executes in the parent Thread to update the ImageList with the newly created thumbnails and refresh the ListView. The parent Thread function then pops (remove) the first 10 items from the list, checks if there're still pending items and calls the thumbnails creation thread again until there're no more in the Pending list.
C#
//
        Thread _CreateThumbListThread = null;
        List<ThumbnailItem> _ThumbList = new List<ThumbnailItem>();
        int _MaxThumbPerThreadRun = 10;

        private void RunThumbGeneratorThread()
        {
            if (_ThumbList.Count > 0)
            {
                _CreateThumbListThread = new Thread(new ThreadStart(CreateThumbList));
                _CreateThumbListThread.Start();
            }
        }
        
        private void CreateThumbList()
        {
            
            int NumItemsToProcess = (_MaxThumbPerThreadRun 
		< _ThumbList.Count ? _MaxThumbPerThreadRun : _ThumbList.Count);
            for (int i = 0; i < NumItemsToProcess; i++)
            {
                if (IsPhoto(_ThumbList[i].FileExtension)) 
                {
                    Bitmap orgImage;

                    if (_ThumbList[i].FileFullName.IndexOf("_Enc@@") != -1) // encrypted 
							// file -> decrypt first
                    {
                        GetCurrentConfigEncryptionKey(false);
                        orgImage = (Bitmap)Bitmap.FromStream
				(DecryptFile(_ThumbList[i].FileFullName));
                    }
                    else
                    {
                        orgImage = (Bitmap)Bitmap.FromFile(_ThumbList[i].FileFullName);
                        
                    }
                    _ThumbList[i].ThumbnailImage = CreateThumbnail(orgImage);
                }
            }

            UpdateLargeIcons(NumItemsToProcess);
        }

        private delegate void UpdateLargeIconsDelegate(int ItemsProcessed);

        private void UpdateLargeIcons(int ItemsProcessed)
        {
            if (listView1.InvokeRequired)
            {
                UpdateLargeIconsDelegate d = new UpdateLargeIconsDelegate
							(UpdateLargeIcons);
                listView1.BeginInvoke(d, new object[] { ItemsProcessed });
            }
            else
            {
                for (int i = 0; i < ItemsProcessed; i++)
                {
                    thumbImageList.Images[_ThumbList[i].ThumbListIndex] = 
						_ThumbList[i].ThumbnailImage;
                }
                
                listView1.Refresh();
                
                // remove processed thumbs
                for (int j = 0; j < ItemsProcessed; j++)
                {
                    _ThumbList.RemoveAt(0);
                }

                // still has unprocessed thumbs => run generator again
                if (_ThumbList.Count > 0)
                {
                    RunThumbGeneratorThread();
                }
            }
        }

    public class ThumbnailItem
    {
        Image _ThumbImage = null;

        public ThumbnailItem(int ThumbListIndex, 
		string FileFullName, string FileExtension)
        {
            this.ThumbListIndex = ThumbListIndex;
            this.FileFullName = FileFullName;
            this.FileExtension = FileExtension;
        }
        public int ThumbListIndex
        {
            get;
            set;
        }
        public string FileFullName
        {
            get;
            set;
        }
        public string FileExtension
        {
            get;
            set;
        }
        public Image ThumbnailImage
        {
            get { return _ThumbImage; }
            set { _ThumbImage = value;  }
        }
//

Framework Components Used

  • TreeView, ListView, PictureBox, TabControl, etc.

Compiler Requirements/Usage Notes

Improvement Desires

  • When viewing photos as thumbnail list, the ListView is initially loaded with just the file names. A separate thread is called to generate all the thumbnails, and once complete, invoke another function running in the parent thread to update the listview's LargeIconList with the generated thumbnails. This prevents the listview from freezing up while hundreds of thumbnails are being created in the background and allows photo selection to load in main view. I'm looking for a way to generate, say, 10 or 20 thumbnails at a time and make them show in the Listview and repeat the same process until all are shown. I've tried a few approaches such as thread callback but the display is just too screwy.
    2/18/2009 update - Successfully modified to load 10 thumbnails at a time.
  • The thumbnail creation uses the best setting provided by the .NET library but some photos just won't display as good as the built-in explorer's thumbnail view.
  • Efficiency - prevent memory leak, better way to dispose objects/initializing components, etc.

History

  • 12th February, 2009: Initial version
  • 18th February, 2009: Modified version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)