数码设备在生成JPG照片的时候会保存Exif信息,这些信息包含照片拍摄的一些参数,包括从拍摄时间到拍摄设备、曝光参数等等很多信息。
C#中从文件打开图像文件的时候,可以取得PropertyItem数组,里面记录了Exif参数的Dictionary。

[code=’c#’]
Image img = Image.FromFile(filePath);
PropertyItem[] pt = img.PropertyItems;
[/code]

PropertyItem类包含四个属性:

Type的定义:

  • 1 指定 Value 为字节数组。
  • 2 指定 Value 为空终止 ASCII 字符串。如果将类型数据成员设置为 ASCII 类型,则应该将 Len 属性设置为包括空终止的字符串长度。例如,字符串“Hello”的长度为 6。
  • 3 指定 Value 为无符号的短(16 位)整型数组。
  • 4 指定 Value 为无符号的长(32 位)整型数组。
  • 5 指定 Value 数据成员为无符号的长整型对数组。每一对都表示一个分数;第一个整数是分子,第二个整数是分母。
  • 6 指定 Value 为可以包含任何数据类型的值的字节数组。
  • 7 指定 Value 为有符号的长(32 位)整型数组。
  • 10 指定 Value 为有符号的长整型对数组。每一对都表示一个分数;第一个整数是分子,第二个整数是分母。

不同的类型都需要不同的解析方式。后面我给出两个附件,包含这些解析的函数的一个类。
其中cs文件是我整理过的,直接可以用,zip文件是参考代码。

[code=’c#’]
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace JPGCompact
{
class TestExif
{
public void DoTest(string filePath)
{
Image img = Image.FromFile(filePath);
PropertyItem[] pt = img.PropertyItems;

for (int i = 0; i < pt.Length; i++) { PropertyItem p = pt[i]; // 设备制造商 if (pt[i].Id == 0x010F) { //tbExifCamera.Text = ascii.GetString(pt[i].Value); } // 设备型号 if (pt[i].Id == 0x0110) { tbExifCamera.Text = ExifMeta.FormatValue(p); } // 拍照时间 if (pt[i].Id == 0x0132) { tbExifTime.Text = ExifMeta.FormatValue(p); } // 光圈 if (pt[i].Id == 0x9202) { tbExifAperture.Text = GetAperture(ExifMeta.FormatValue(p)); } // ISO if (pt[i].Id == 0x8827) { tbExifISO.Text = ExifMeta.FormatValue(p); } // 模式 if (pt[i].Id == 0x8822) { tbExifMode.Text = GetProgram(ExifMeta.FormatValue(p)); } } } } } [/code]

程序员懒吧?呵呵。
只要能找到一个好用的,我是绝对不会自己写一个出来的。因为WordPress的图库插件NextGEN Gallery破算法不能压缩太大的文件,PHP执行内存不足,同样的照片跑在同一环境下的Gallery2处理起来很轻松,还能一下执行N张呢…
没办法只好先把照片压缩下在传上去了。找个好几个批量压缩软件都不如意,没办法只好自己写一个了。

界面是这样子的:

[singlepic=15854,700,510]

核心算法其实非常简单:

[code=’c#’]
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.IO;

namespace JPGCompact
{
public partial class MainForm : Form
{
///

/// 保存JPG时用
///

/// 文件类型 /// 得到指定mimeType的ImageCodecInfo
private static ImageCodecInfo GetCodecInfo(string mimeType)
{
ImageCodecInfo[] CodecInfo = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo ici in CodecInfo)
{
if (ici.MimeType == mimeType) return ici;
}
return null;
}

///

/// 保存为JPEG格式,支持压缩质量选项
///

/// 原始位图 /// 新文件地址 /// 压缩质量,越大越好,文件也越大(0-100) /// 成功标志
public static bool SaveAsJPEG(Bitmap bmp, string FileName, int Qty)
{
try
{
EncoderParameter p;
EncoderParameters ps;
ps = new EncoderParameters(1);
p = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, Qty);
ps.Param[0] = p;
bmp.Save(FileName, GetCodecInfo(“image/jpeg”), ps);
return true;
}
catch
{
return false;
}
}

private bool CompressPicture(string sourcePath, string targetPath)
{
try
{
// 大小比率
double sizeRate = double.Parse(cbSizeRate.Text) / 100;
// 品质比率
int qualityRate = int.Parse(cbQualityRate.Text);

Image sourceImage = Image.FromFile(sourcePath);
// 调整图片大小
Bitmap bmp = new Bitmap(
sourceImage,
new Size(
(int)(sourceImage.Width * sizeRate),
(int)(sourceImage.Height * sizeRate)));
// 压缩图片
SaveAsJPEG(bmp, targetPath, qualityRate);

GC.Collect();

return true;
}
catch
{
return false;
}
}
}
}
[/code]

比较有技术含量的是那个Exif信息的读取,一会儿我写一篇说说那个怎么做。

下载地址:http://download.nocoo.us/Download/Archive/JPGCompact.rar

在C#中,经常用到这样一个场景,Windows Form程序启动一个工作者线程执行一部分工作,这样做是为了避免速度慢的工作如果直接调用会使得主Form停止响应一段时间。
既然启动了线程,就避免不了线程之间数据传递的事情,相信你有很多种办法能解决,总之注意同步和互斥操作就好。我想说的是,工作线程处理中可能想操作某个主线程的Windows Form的Control,比如按钮,ListView等等更新工作状态之类,直接控制是不行的,不能够跨线程操作另一个线程创建的Windows Form控件。要使用委托去调用。
[code=’c#’]
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace JPGCompact
{
public partial class MainForm : Form
{
// 定义委托
private delegate void DelegateWriteResult(string file, bool result);

// 与定义的委托签名相同的函数,操作主线程控件
private void WriteResult(string fileName, bool result)
{
if (result)
{
ListViewItem thisListItem = new ListViewItem();
thisListItem.ForeColor = Color.White;
thisListItem.BackColor = Color.DarkGreen;
thisListItem.SubItems[0].Text = fileName;
thisListItem.SubItems.Add(“成功”);
lvResultList.Items.Add(thisListItem);
}
else
{
ListViewItem thisListItem = new ListViewItem();
thisListItem.ForeColor = Color.White;
thisListItem.BackColor = Color.Red;
thisListItem.SubItems[0].Text = fileName;
thisListItem.SubItems.Add(“失败”);
lvResultList.Items.Add(thisListItem);
}
}

// 启动线程
private void btnStart_Click(object sender, EventArgs e)
{
Thread workThread = new Thread(new ThreadStart(CompressAll));
// 设置为背景线程,主线程一旦推出,该线程也不等待而立即结束
workThread.IsBackground = true;
workThread.Start();
}

// 线程执行函数
private void CompressAll()
{
// 判断是否需要Invoke,多线程时需要
if (this.InvokeRequired)
{
// 通过委托调用写主线程控件的程序,传递参数放在object数组中
this.Invoke(new DelegateWriteResult(WriteResult),
new object[] { item, true });
}
else
{
// 如果不需要委托调用,则直接调用
this.WriteResult(item, true);
}
}
}
}
[/code]

不同的操作系统,桌面的路径不尽相同,而且随着用户安装位置的不同也不同。
C#可以从Windows注册表读取得到用户的特殊文件夹(桌面、收藏夹等等)的位置。
代码如下:
[code=’c#’]
using Microsoft.Win32;
namespace JPGCompact
{
public partial class MainForm : Form
{
private void Test()
{
RegistryKey folders;
folders = OpenRegistryPath(Registry.CurrentUser, @”\software\microsoft\windows\currentversion\explorer\shell folders”);
// Windows用户桌面路径
string desktopPath = folders.GetValue(“Desktop”).ToString();
// Windows用户字体目录路径
string fontsPath = folders.GetValue(“Fonts”).ToString();
// Windows用户网络邻居路径
string nethoodPath = folders.GetValue(“Nethood”).ToString();
// Windows用户我的文档路径
string personalPath = folders.GetValue(“Personal”).ToString();
// Windows用户开始菜单程序路径
string programsPath = folders.GetValue(“Programs”).ToString();
// Windows用户存放用户最近访问文档快捷方式的目录路径
string recentPath = folders.GetValue(“Recent”).ToString();
// Windows用户发送到目录路径
string sendtoPath = folders.GetValue(“Sendto”).ToString();
// Windows用户开始菜单目录路径
string startmenuPath = folders.GetValue(“Startmenu”).ToString();
// Windows用户开始菜单启动项目录路径
string startupPath = folders.GetValue(“Startup”).ToString();
// Windows用户收藏夹目录路径
string favoritesPath = folders.GetValue(“Favorites”).ToString();
// Windows用户网页历史目录路径
string historyPath = folders.GetValue(“History”).ToString();
// Windows用户Cookies目录路径
string cookiesPath = folders.GetValue(“Cookies”).ToString();
// Windows用户Cache目录路径
string cachePath = folders.GetValue(“Cache”).ToString();
// Windows用户应用程式数据目录路径
string appdataPath = folders.GetValue(“Appdata”).ToString();
// Windows用户打印目录路径
string printhoodPath = folders.GetValue(“Printhood”).ToString();
}

private RegistryKey OpenRegistryPath(RegistryKey root, string s)
{
s = s.Remove(0, 1) + @”\”;
while (s.IndexOf(@”\”) != -1)
{
root = root.OpenSubKey(s.Substring(0, s.IndexOf(@”\”)));
s = s.Remove(0, s.IndexOf(@”\”) + 1);
}
return root;
}
}
}
[/code]

SMS.cs,一个订阅者
[sourcecode language=’c#’]
using System;
using System.Collections.Generic;
using System.Text;

namespace EventExample
{
class SMS
{
public void OnNewMail(object sender, NewMailEventArgs e)
{
Console.WriteLine(
“SMS Arrives: [NEW MAIL] {0} to {1}, {2}”,
e.From, e.To, e.Subject);
}
}
}
[/sourcecode]

Phone.cs,另一个订阅者
[sourcecode language=’c#’]
using System;
using System.Collections.Generic;
using System.Text;

namespace EventExample
{
class Phone
{
public void OnNewMail(object sender, NewMailEventArgs e)
{
Console.WriteLine(
“Phone Rings: [NEW MAIL] {0} to {1}, {2}”,
e.From, e.To, e.Subject);
}
}
}
[/sourcecode]

NewMailEventArgs.cs,自定义的事件附加信息类
[sourcecode language=’c#’]
using System;
using System.Collections.Generic;
using System.Text;

namespace EventExample
{
class NewMailEventArgs : EventArgs
{
private readonly string m_from, m_to, m_subject;

public NewMailEventArgs(string from, string to, string subject)
{
this.m_from = from;
this.m_to = to;
this.m_subject = subject;
}

public string From { get { return m_from; } }
public string To { get { return m_to; } }
public string Subject { get { return m_subject; } }
}
}
[/sourcecode]

MailManager.cs,引发事件的类
[sourcecode language=’c#’]
using System;
using System.Collections.Generic;
using System.Text;

namespace EventExample
{
class MailManager
{
public event EventHandler NewMail;

protected virtual void OnNewMail(NewMailEventArgs e)
{
// For thread safe
EventHandler temp = NewMail;

if (temp != null)
{
temp(this, e);
}
}

public void SimulateNewMail(string from, string to, string subject)
{
NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
OnNewMail(e);
}
}
}
[/sourcecode]

这里的类使用了一个线程安全的做法,使用了一个OnNewMail虚方法调用事件,在调用之前首先将事件保存到一个temp中,这是因为确保事件不是null,如果直接判断NewMail是不是null,可能会在判断完之后,当时不是null,而紧接着被另一个线程修改成null,从而导致NullReferenceException的情况。

Program.cs
[sourcecode language=’c#’]
using System;
using System.Collections.Generic;
using System.Text;

namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Phone phone = new Phone();
SMS sms = new SMS();
MailManager mailManager = new MailManager();

mailManager.NewMail += phone.OnNewMail;
mailManager.NewMail += sms.OnNewMail;

for (int i = 0; i < 100; i++) { mailManager.SimulateNewMail( "Someone", "Someone else", "Test Mail" + i.ToString()); } } } } [/sourcecode]

其实,手机的摄像头挺重要的,最最关键的时刻,还是随身携带的东西最方便。
跨越半年,每个月都回家。
回家其实很少出去,只会陪着父母还有Aya。但还是被细心的我发现这两辆车,很牛哦。

[singlepic=15811,700,525]

2008年08月11日,济南历山路

[singlepic=15810,700,525]

2008年02月16日,济南经十路

RedMine是领先的软件项目管理软件,基于MySQL和Ruby on Rails。
我在实验室项目管理中架设了RedMine开发版。
基于个人信息化策略的需求,希望当某个issue更新的时候能获得一个邮件通知。还好,RedMine提供了这项功能。
在管理->配置->邮件通知中可以管理发信信息。

[singlepic=15537]

1. 配置SMTP服务器
不打算用外面免费邮箱的SMTP服务器。在Windows Server 2003上使用Manage Your Server为服务器添加SMTP和POP的邮件服务器角色。没什么好说的,Server 2003这一点很方便,装好之后也不需要重启。

2. 配置RedMine Email配置脚本
在RedMine的config目录中,有一个email.yml.example文件,重命名为email.yml后用文本编辑器打开,更改production段的内容。因为我是用本机做SMTP,我是这样改的:

[code=’css’]
# Outgoing email settings
production:
delivery_method: :smtp
smtp_settings:
address: 127.0.0.1
port: 25
domain: hpcc.tongji.edu.cn
[/code]

根据你的情况修改即可。需要登录的话可能要这样:

[code=’css’]
# Outgoing email settings
production:
delivery_method: :smtp
smtp_settings:
address: 127.0.0.1
port: 25
domain: hpcc.tongji.edu.cn
authentication: :login
user_name: [email protected]
password: redmine
[/code]

保存后需要重启RedMine的Ruby服务器。

3. 管理->配置->邮件通知
到这里去发测试邮件,一般就没问题了。我是新架,出现一个错误:550 5.7.1 Uable to relay [email protected]
这是SMTP服务器的典型错误,一般情况如果你新配置的SMTP服务器默认配置下出现,只说明一个问题,MX记录未正确设置。我想看这篇文章的人一定懂得什么是MX记录了。去你的域控制器DNS中添加一条MX记录,指向你的SMTP服务器就可以了。
再次发送测试邮件,我的Gmail瞬间收到了邮件通知:

[singlepic=15538]

Have fun!