带加密功能的照片和视频查看器
一个使用 C# 和 Visual Studio 2008 查看照片和视频的个人媒体查看器。

引言
我尝试过一些图像查看器实用程序,但没有找到真正符合我偏好的一个,所以我决定自己写一个。我已经具备了我需要的所有功能,但想从专家那里获得关于一些问题的建议。
常见图像实用程序的问题
- 以缩略图列表加载照片,没有切换到简单文件名列表的选项。当文件夹包含数百张照片时,这将花费很长时间。当卸载从具有 1GB+ SD 卡的数码相机拍摄的照片时,这非常常见。
- 缩略图列表将驻留在宽视图窗格中,该窗格占用了主图像的宝贵视图空间,此外双击在主视图中打开照片,然后关闭并双击另一张照片也很烦人。
实用程序功能
功能太多无法一一列出,但总体思路是使照片列表尽可能窄,主视图尽可能大。选择照片将在主视图中使用默认的“适应屏幕”显示,以便用户无需向右/向下滚动即可看到整张图片。从 6 百万像素数码相机拍摄的照片通常具有 2576 x 1932 的分辨率。选择照片后,listview 将获得焦点,后续照片可以通过简单地按向上/向下键来选择下一个/上一个文件来查看。
有用的图像处理代码
//
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 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;
}
//
部分缩略图创建
对于缩略图渲染,我使用类似于 Stack's Pop 的方法。
Listview
在设计视图中映射到ImageList
。- 对于缩略图显示模式,将
ImageList
中的所有图像设置为默认的“正在处理”图像,并将待处理(要创建)的缩略图列表存储在自定义对象列表中。 - 缩略图创建过程在单独的线程中运行。它循环遍历待处理的缩略图列表,每次处理 10 个(或者如果剩余少于 10 个则处理更少),然后退出并调用一个在父线程中执行的函数,以使用新创建的缩略图更新
ImageList
并刷新ListView
。然后,父线程函数从列表中弹出(删除)前 10 个项目,检查是否仍有待处理项目,并再次调用缩略图创建线程,直到 Pending 列表中没有更多项目。
//
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; }
}
//
使用的框架组件
TreeView
,ListView
,PictureBox
,TabControl
, 等等。
编译器要求/使用说明
- 此程序使用 Windows Media Player 播放大多数视频。请遵循 Microsoft 指南 将 Windows Media Player 控件与 Microsoft Visual Studio 一起使用,使其可被项目引用。
- 加密/解密:我不是这方面的专家,代码是从另一位程序员那里修改复制的。使用风险自负。
改进需求
- 当以缩略图列表查看照片时,
ListView
最初仅加载文件名。调用一个单独的线程来生成所有缩略图,完成后,调用另一个在父线程中运行的函数,以使用生成的缩略图更新listview
的LargeIconList
。这可以防止在后台创建数百个缩略图时listview
卡死,并允许照片选择加载到主视图中。我正在寻找一种一次生成(例如)10 或 20 个缩略图并在Listview
中显示它们,并重复相同的过程直到所有缩略图都显示出来的方法。我尝试了几种方法,例如线程回调,但显示效果太糟糕了。
2009 年 2 月 18 日更新 - 成功修改为一次加载 10 个缩略图。 - 缩略图创建使用 .NET 库提供的最佳设置,但某些照片的显示效果不如内置资源管理器的缩略图视图。
- 效率 - 防止内存泄漏,更好地处理对象/初始化组件等。
历史
- 2009 年 2 月 12 日:初始版本
- 2009 年 2 月 18 日:修改版本