65.9K
CodeProject 正在变化。 阅读更多。
Home

无损旋转、EXIF 和其他功能的图像查看器

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.16/5 (15投票s)

2003年8月28日

CPOL

6分钟阅读

viewsIcon

147998

downloadIcon

1380

本文演示了一个简单的 JPEG 图像查看器。

概述

本文演示了一个简单的 JPEG 图像查看器。其中演示了几个有趣的编码技巧,包括线程、双向链表和图像处理。该程序将显示文件夹中的所有图像,包括子文件夹,并动态调整图片大小以填满整个屏幕。此外,还支持将图像旋转 90 度,以及访问图像的扩展信息,允许用户显示或更改存储在图片中的注释和其他信息。

引言

我对数码摄影产生了兴趣,从西澳大利亚的一次假期回来后,我带回了大约 500 张照片,其中大部分是用 200 万像素的相机拍摄的。许多照片是竖拍的,因此需要旋转 90 度。我希望在不重新压缩的情况下旋转它们,即使是 Photoshop apparently 也无法做到这一点。但一旦我发现 .NET Framework 类可以轻松完成这项工作,我就决定制作一个应用程序来完成这项任务。同时,它还可以让我通过按空格键来简单地一张一张地查看照片。我知道市面上有很多程序可以做这类事情,包括创建虚拟相册的功能,但管它呢,我认为我的程序简单易用。我查看过的一些查看器实际上相当糟糕。我不想说出名字,但一位朋友的相机附带的一个商业产品拒绝显示我的一些照片,所以我觉得 .NET 真是救星!

我想要做的有趣的事情之一是让我的查看器显示初始文件夹下的所有内容,而不管它们位于哪个子文件夹。现在,导航层次结构有多种方法。最简单的方法当然是使用递归。在我的情况下,我想查看文件夹内的所有文件(包括该文件夹的所有子文件夹)。

例如

void showFiles(string path)
{
    foreach (string fn in Directory.GetFiles(path) )
    {
        Console.WriteLine(fn);
    }
    foreach (string folder in Directory.GetFolders(path) )
    {
        showFiles(folder);
    }
}

事实证明,有些类的问题不太适合这种方法——这完全取决于谁是“驾驶员”。我也不想用文件导航代码来污染我的查看器,所以我决定安排所有文件将它们自己呈现为简单的列表。当然,我可以用递归创建一个这样的列表,但我想玩玩线程,所以我编写了一个“生产者”线程,它一次提供一个文件。这个线程中的代码作为单独的提交提交——查找文章“Flattening a Hierarchy – a producer thread to get all files in a folder and subfolders”。(当然,如果你的层次结构非常大,将项目放入列表可能由于内存不足而不可行,这也是开发该技术的一个动机。)

将图像调整到全屏大小

看照片时,我想看到所有内容。我就是不明白为什么 JPEG 在 Internet Explorer 中打开时只显示图像的一部分。所以,我首先创建了一个窗体,并在上面放置了一个 PictureBox,其 SizeMode 属性设置为 StretchImage

然后,我需要确保纵横比不会被搞乱,所以我写了这段代码。

public static void ShowPicture(PictureBox picBox, 
                                Size intendedSize, string fileName) 
{ 
    bool tooSmallToResize = true;  
        etc...
 
    Size newSize = new Size(intendedSize.Width, intendedSize.Height);
    if ( (double)intendedSize.Width/intendedSize.Height > aspectRatio) 
    { 
        newSize.Width = (int)(intendedSize.Height * aspectRatio); 
    } 
    else 
    { 
        newSize.Height = (int)(intendedSize.Width / aspectRatio); 
    } 
 
    ...
}

我还调整了位置,以便在必要时,图片能居中显示在屏幕上。如果图片太小,我根本不调整它的大小。

旋转图像

我想让旋转图像变得非常容易,所以我编写了一个键盘事件处理程序。当用户按下“R”(Rotate 的缩写)时,我将其旋转 90 度。如果方向不正确,只需再按几次!

为了测试没有发生重新压缩,我写了一些临时代码将图像旋转 2,000 次,并将结果与原始图像进行了比较。它们的大小相同,即使放大得很近也看不出区别。

public static void Rotate90(string fileName)
{ 
    Image Pic; 
    string FileNameTemp; 
    Encoder Enc = Encoder.Transformation; 
    EncoderParameters EncParms = new EncoderParameters(1); 
    EncoderParameter EncParm; 
    ImageCodecInfo CodecInfo = GetEncoderInfo("image/jpeg"); 
 
    // load the image to change 
    Pic = Image.FromFile(fileName); 
 
    // we cannot store in the same image, so use a temporary image instead 
    FileNameTemp = fileName + ".temp"; 

    // for rewriting without recompression we must rotate the image 90 degrees
    EncParm = new EncoderParameter(Enc,(long)EncoderValue.TransformRotate90); 
    EncParms.Param[0] = EncParm; 

    // now write the rotated image with new description 
    Pic.Save(FileNameTemp,CodecInfo,EncParms); 
    Pic.Dispose(); 
    Pic = null; 

    // delete the original file, will be replaced later 
    System.IO.File.Delete(fileName); 
    System.IO.File.Move(FileNameTemp, fileName); 
}

JPEG 图像的扩展信息

EXIF 信息很有用,可以让你记录关于照片的评论,例如照片中的人物姓名,或照片的拍摄地点。按 I 键或使用鼠标右键调出图像的扩展属性。

一个小的棘手代码,源于拍摄照片的日期和时间是以 yyyy:mm:dd hh:mm:ss 的格式存储的,小时采用 24 小时制。以下代码片段展示了我如何解决这种格式的日期解析问题。

string d = "2003:01:03 23:30:02";
DateTimeFormatInfo info = new DateTimeFormatInfo();
info.ShortDatePattern = "yyyy:MM:dd HH:mm:ss";
DateTime dt = DateTime.ParseExact(d, "d", info);

浏览文件夹

在 .NET Framework 的 1.0 版本中,你需要从 FolderNameEditor 派生一个类。但在 1.1 版本中,有一个新类 FolderBrowser,它更易于使用。请查看 *MainPropertiesForm.css* 中的 #if FRAMEWORK11。

具有容量限制的双向链表

为了保存你浏览过的图片的记录(以便你可以向后跳转而不是只向前),我保留了一个列表——但有一个转折,我给双向链表设置了容量限制——请查看 *DoublyLinkedList.cs* 中的代码。

移至下一张图片

你可能会注意到窗体使用了枚举器接口的 MoveNext 函数来移至下一张图片。通过使用此接口,代码无需关心我们正在遍历的列表类型。通过添加对相册的支持,我们无需更改代码。我们只需确保我们有一个可以遍历相册中图片的枚举器。

相册支持

仍在开发中!支持创建相册,但不支持选择和使用相册。我通过使窗体使用 IEnumerator 来简化了实现,这意味着你只需要更改主窗体中的一些基础设施。如果有人想写那部分代码,请发给我!

制作缩略图和网络尺寸图片

有一些制作缩略图的支持——按“T”键即可静默完成。网络尺寸缩减?——仍在开发中,但不应与制作缩略图有太大差异。

FxCop

使用它!要摆脱所有消息非常困难,但这是一个值得追求的目标!如果你还没有副本,请到这里 http://www.gotdotnet.com/team/fxcop

垃圾回收器和 Dispose

我在将 dispose 调用放到正确的位置上遇到了很多麻烦,以避免随机的“文件正在使用中”错误。我甚至一度不得不诉诸使用垃圾回收器。但我后来发现,即使这样也不可靠,因为垃圾回收器运行在单独的线程上!不过,现在代码中找不到对 Dispose()GC.Collect() 的调用了,因为我找到了更好的方法。

using (Image Pic = Image.FromFile(fileName)) 
{ 
    // Compiler will call Dispose on 'pic' for us on exit from this block
}

用于图像处理的命令行工具

对于那些对命令行图像处理工具感兴趣的人,我推荐我的有才华且有时令人讨厌的同事 Michael Still 的文章——http://www-106.ibm.com/developerworks/linux/library/l-graf/

可能的增强功能

如果选择图片制作相册后,还能自动生成网站,那就太酷了。

致谢

感谢 Vernon Zhou 协助编写此程序,感谢所有其他优秀的 CodeProject 贡献者为我打下的基础,尤其感谢 Doug Hanhart http://www.dotnet247.com/247reference/msgs/28/144569.aspx

© . All rights reserved.