编程实现QQ表情文件CFC格式
背景:最近闲来无事,也应论坛某会员要求,想做个QQ表情下载的站点。本来事情是很简单的,写个小小的CRUD也就可以了,但嘻哈呵嘿既然是个.Net程序员,当然要使用.Net来实现了。今天我们就用.Net来实现CFC ( custom face cab? ) 的表情格式的打包功能。
要做到这个功能,我们必须先了解这个格式,首先Google一下。我们找到了这一篇来自清华大学的文章:FC文件格式详解
从这篇文章里我们得知了CFC的文件格式大概如下:
一个块有15个字段,如下
- md5的字符串形式长度,4个字节
- 快捷键长度,4字节
- 表情名称长度,4字节
- 表情文件名长度,4字节
- 表情文件长度,4字节
- 微缩图文件名长度,4字节
- 微缩文件长度,4字节
- 表情文件帧数,4字节
- 图片md5的字符串形式
- 快捷键
- 表情名称
- 表情文件名
- 微缩图文件名
- 表情文件内容
- 微缩图内容
1 Struct#region Struct
2 public struct FaceBlock
3 {
4 public uint MD5Length; //32
5 public uint uintcutLength; //4
6 public uint FaceNameLength; //4
7 public uint FaceFileNameLength; //36 md5 + extension
8 public uint FileLength;
9 public uint ThumbnailFileNameLength; //41 md5 + fixed.bmp
10 public uint ThumbnailFileLength;
11 public uint FrameLength;
12 public string MD5;
13 public string uintcuts;
14 public string FaceName;
15 public string FaceFileName;
16 public string ThumbnailFileName;
17 public byte[] FaceData;
18 public byte[] ThumbnailData;
19
20 public static FaceBlock FromImage(string file)
21 {
22 return FaceHelper.GetFaceBlockFromImage(file);
23 }
24
25 byte[] GetBytes(uint value)
26 {
27 byte[] bt = BitConverter.GetBytes(value);
28 List<byte> bytes &#61; new List<byte>();
29 bytes.AddRange(bt);
30 if (bytes.Count < 4)
31 {
32 int l &#61; 4 - bytes.Count;
33 for (int i &#61; 0; i < l; i&#43;&#43;)
34 bytes.Add((byte)0);
35 }
36 return bytes.ToArray();
37 }
38
39 public byte[] ToBytes()
40 {
41 List<byte> bytes &#61; new List<byte>();
42 Encoding e &#61; Encoding.ASCII;
43 bytes.AddRange(GetBytes(MD5Length));
44 bytes.AddRange(GetBytes(uintcutLength));
45 bytes.AddRange(GetBytes(FaceNameLength));
46 bytes.AddRange(GetBytes(FaceFileNameLength));
47 bytes.AddRange(GetBytes(FileLength));
48 bytes.AddRange(GetBytes(ThumbnailFileNameLength));
49 bytes.AddRange(GetBytes(ThumbnailFileLength));
50 bytes.AddRange(GetBytes(FrameLength));
51
52 bytes.AddRange(e.GetBytes(MD5));
53 bytes.AddRange(e.GetBytes(uintcuts));
54 bytes.AddRange(e.GetBytes(FaceName));
55 bytes.AddRange(e.GetBytes(FaceFileName));
56 bytes.AddRange(e.GetBytes(ThumbnailFileName));
57
58 bytes.AddRange(FaceData);
59 bytes.AddRange(ThumbnailData);
60
61 return bytes.ToArray();
62 }
63 }
64 #endregion
其中含有两方法&#xff0c;一个是从文件得到一个此结构的静态方法&#xff0c;另一个是将此结构转化为byte数组。2 public struct FaceBlock
3 {
4 public uint MD5Length; //32
5 public uint uintcutLength; //4
6 public uint FaceNameLength; //4
7 public uint FaceFileNameLength; //36 md5 &#43; extension
8 public uint FileLength;
9 public uint ThumbnailFileNameLength; //41 md5 &#43; fixed.bmp
10 public uint ThumbnailFileLength;
11 public uint FrameLength;
12 public string MD5;
13 public string uintcuts;
14 public string FaceName;
15 public string FaceFileName;
16 public string ThumbnailFileName;
17 public byte[] FaceData;
18 public byte[] ThumbnailData;
19
20 public static FaceBlock FromImage(string file)
21 {
22 return FaceHelper.GetFaceBlockFromImage(file);
23 }
24
25 byte[] GetBytes(uint value)
26 {
27 byte[] bt &#61; BitConverter.GetBytes(value);
28 List<byte> bytes &#61; new List<byte>();
29 bytes.AddRange(bt);
30 if (bytes.Count < 4)
31 {
32 int l &#61; 4 - bytes.Count;
33 for (int i &#61; 0; i < l; i&#43;&#43;)
34 bytes.Add((byte)0);
35 }
36 return bytes.ToArray();
37 }
38
39 public byte[] ToBytes()
40 {
41 List<byte> bytes &#61; new List<byte>();
42 Encoding e &#61; Encoding.ASCII;
43 bytes.AddRange(GetBytes(MD5Length));
44 bytes.AddRange(GetBytes(uintcutLength));
45 bytes.AddRange(GetBytes(FaceNameLength));
46 bytes.AddRange(GetBytes(FaceFileNameLength));
47 bytes.AddRange(GetBytes(FileLength));
48 bytes.AddRange(GetBytes(ThumbnailFileNameLength));
49 bytes.AddRange(GetBytes(ThumbnailFileLength));
50 bytes.AddRange(GetBytes(FrameLength));
51
52 bytes.AddRange(e.GetBytes(MD5));
53 bytes.AddRange(e.GetBytes(uintcuts));
54 bytes.AddRange(e.GetBytes(FaceName));
55 bytes.AddRange(e.GetBytes(FaceFileName));
56 bytes.AddRange(e.GetBytes(ThumbnailFileName));
57
58 bytes.AddRange(FaceData);
59 bytes.AddRange(ThumbnailData);
60
61 return bytes.ToArray();
62 }
63 }
64 #endregion
我们再建一个类&#xff0c;命名为&#xff1a;FaceHelper
代码如下&#xff1a;
public class FaceHelper
{
internal static FaceBlock GetFaceBlockFromImage(string file)
{
FaceBlock fb &#61; new FaceBlock();
//打开文件流
FileStream fs &#61; new FileStream(file, FileMode.Open, FileAccess.Read);
//获取图像
Image img &#61; Image.FromStream(fs);
//获得一个20*20的缩略图
Image thumbnail &#61; img.GetThumbnailImage(20, 20, null, IntPtr.Zero);
MemoryStream ms &#61; new MemoryStream();
//将缩图图转化数byte数组
thumbnail.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
byte[] thumbnailData &#61; ms.ToArray();
ms.Close();
ms.Dispose();
thumbnail.Dispose();
//得到一个唯一的MD5字符串
string md5 &#61; GetMD5(fs);
//文件名&#xff0c;格式为:md5 &#43; 扩展名
string fileName &#61; string.Format("{0}{1}", md5, Path.GetExtension(file));
//缩略图文件名&#xff0c;格式为&#xff1a;md5 &#43; fixed.bmp
string thumbnailName &#61; string.Format("{0}fixed.bmp", md5);
//随机设置一个快捷键
string uintcuts &#61; "qq.5inet.net_" &#43; RandomNum(6);
fs.Close();
fs.Dispose();
//取得总的帧数
System.Drawing.Imaging.FrameDimension fd &#61; System.Drawing.Imaging.FrameDimension.Resolution;
int frameCount &#61; img.FrameDimensionsList.Length;
img.Dispose();
fb.MD5 &#61; md5;
fb.MD5Length &#61; (uint)md5.Length;
fb.uintcuts &#61; uintcuts;
fb.uintcutLength &#61; (uint)uintcuts.Length;
fb.FaceName &#61; uintcuts;
fb.FaceNameLength &#61; (uint)uintcuts.Length;
fb.FaceFileName &#61; fileName;
fb.FaceFileNameLength &#61; (uint)fileName.Length;
fb.ThumbnailFileName &#61; thumbnailName;
fb.ThumbnailFileNameLength &#61; (uint)thumbnailName.Length;
fb.FaceData &#61; File.ReadAllBytes(file);
fb.FileLength &#61; (uint)fb.FaceData.Length;
fb.ThumbnailData &#61; thumbnailData;
fb.ThumbnailFileLength &#61; (uint)thumbnailData.Length;
fb.FrameLength &#61; (uint)frameCount;
return fb;
}
Helper#region Helper
//随机方法
internal static string RandomNum(int n) //
{
string strchar &#61; "0,1,2,3,4,5,6,7,8,9";
string[] VcArray &#61; strchar.Split(&#39;,&#39;);
string VNum &#61; "";//由于字符串很短&#xff0c;F77pclw,c络G|?,业,e&#39;b就不用StringBuilder了
int temp &#61; -1; //记录上次随机数值&#xff0c;尽量避免产生几个一样的随机数
//采用一个简单的算法以保证生成随机数的不同
Random rand &#61; new Random();
for (int i &#61; 1; i < n &#43; 1; i&#43;&#43;)
{
if (temp !&#61; -1)
{
rand &#61; new Random(i * temp * unchecked((int)
DateTime.Now.Ticks));
}
//int t &#61; rand.Next(35) ;
int t &#61; rand.Next(10);
if (temp !&#61; -1 && temp &#61;&#61; t)
{
return RandomNum(n);
}
temp &#61; t;
VNum &#43;&#61; VcArray[t];
}
return VNum;//返回生成的随机数
}
//从文件名获得MD5
internal static string GetMD5(FileStream fs)
{
MD5CryptoServiceProvider md5 &#61; new MD5CryptoServiceProvider();
byte[] md5byte &#61; md5.ComputeHash(fs);
string str &#61; string.Empty;
int i, j;
foreach (byte b in md5byte)
{
i &#61; Convert.ToInt32(b);
j &#61; i >> 4;
str &#43;&#61; (Convert.ToString(j, 16));
j &#61; ((i << 4) & 0x00ff) >> 4;
str &#43;&#61; (Convert.ToString(j, 16));
}
return str.ToUpper();
}
#endregion
//从一个目录生成一个CFC文件集合
public static void
BuildCFCFileFromDirectory(string directory)
{
List<byte> bytes &#61; new List<byte>();
foreach (string file in Directory.GetFiles(directory))
{
if (!IsImageFile(file))
continue;
bytes.AddRange(FaceBlock.FromImage(file).ToBytes());
}
string fName &#61; Path.Combine(directory, Path.GetDirectoryName(directory) &#43; ".cfc");
FileStream fs &#61; File.Create(fName);
fs.Write(bytes.ToArray(), 0, bytes.Count);
fs.Close();
}
//判断是否为图像文件&#xff0c;方法比较简陋。
private static bool IsImageFile(string file)
{
List<string> validExt &#61; new List<string>(new string[]{
".bmp",
".jpg",
".jpeg",
".gif",
".png",
});
return validExt.Contains(Path.GetExtension(file).ToLower());
}
}
{
internal static FaceBlock GetFaceBlockFromImage(string file)
{
FaceBlock fb &#61; new FaceBlock();
//打开文件流
FileStream fs &#61; new FileStream(file, FileMode.Open, FileAccess.Read);
//获取图像
Image img &#61; Image.FromStream(fs);
//获得一个20*20的缩略图
Image thumbnail &#61; img.GetThumbnailImage(20, 20, null, IntPtr.Zero);
MemoryStream ms &#61; new MemoryStream();
//将缩图图转化数byte数组
thumbnail.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
byte[] thumbnailData &#61; ms.ToArray();
ms.Close();
ms.Dispose();
thumbnail.Dispose();
//得到一个唯一的MD5字符串
string md5 &#61; GetMD5(fs);
//文件名&#xff0c;格式为:md5 &#43; 扩展名
string fileName &#61; string.Format("{0}{1}", md5, Path.GetExtension(file));
//缩略图文件名&#xff0c;格式为&#xff1a;md5 &#43; fixed.bmp
string thumbnailName &#61; string.Format("{0}fixed.bmp", md5);
//随机设置一个快捷键
string uintcuts &#61; "qq.5inet.net_" &#43; RandomNum(6);
fs.Close();
fs.Dispose();
//取得总的帧数
System.Drawing.Imaging.FrameDimension fd &#61; System.Drawing.Imaging.FrameDimension.Resolution;
int frameCount &#61; img.FrameDimensionsList.Length;
img.Dispose();
fb.MD5 &#61; md5;
fb.MD5Length &#61; (uint)md5.Length;
fb.uintcuts &#61; uintcuts;
fb.uintcutLength &#61; (uint)uintcuts.Length;
fb.FaceName &#61; uintcuts;
fb.FaceNameLength &#61; (uint)uintcuts.Length;
fb.FaceFileName &#61; fileName;
fb.FaceFileNameLength &#61; (uint)fileName.Length;
fb.ThumbnailFileName &#61; thumbnailName;
fb.ThumbnailFileNameLength &#61; (uint)thumbnailName.Length;
fb.FaceData &#61; File.ReadAllBytes(file);
fb.FileLength &#61; (uint)fb.FaceData.Length;
fb.ThumbnailData &#61; thumbnailData;
fb.ThumbnailFileLength &#61; (uint)thumbnailData.Length;
fb.FrameLength &#61; (uint)frameCount;
return fb;
}
Helper#region Helper
//随机方法
internal static string RandomNum(int n) //
{
string strchar &#61; "0,1,2,3,4,5,6,7,8,9";
string[] VcArray &#61; strchar.Split(&#39;,&#39;);
string VNum &#61; "";//由于字符串很短&#xff0c;F77pclw,c络G|?,业,e&#39;b就不用StringBuilder了
int temp &#61; -1; //记录上次随机数值&#xff0c;尽量避免产生几个一样的随机数
//采用一个简单的算法以保证生成随机数的不同
Random rand &#61; new Random();
for (int i &#61; 1; i < n &#43; 1; i&#43;&#43;)
{
if (temp !&#61; -1)
{
rand &#61; new Random(i * temp * unchecked((int)
DateTime.Now.Ticks));
}
//int t &#61; rand.Next(35) ;
int t &#61; rand.Next(10);
if (temp !&#61; -1 && temp &#61;&#61; t)
{
return RandomNum(n);
}
temp &#61; t;
VNum &#43;&#61; VcArray[t];
}
return VNum;//返回生成的随机数
}
//从文件名获得MD5
internal static string GetMD5(FileStream fs)
{
MD5CryptoServiceProvider md5 &#61; new MD5CryptoServiceProvider();
byte[] md5byte &#61; md5.ComputeHash(fs);
string str &#61; string.Empty;
int i, j;
foreach (byte b in md5byte)
{
i &#61; Convert.ToInt32(b);
j &#61; i >> 4;
str &#43;&#61; (Convert.ToString(j, 16));
j &#61; ((i << 4) & 0x00ff) >> 4;
str &#43;&#61; (Convert.ToString(j, 16));
}
return str.ToUpper();
}
#endregion
//从一个目录生成一个CFC文件集合
public static void
BuildCFCFileFromDirectory(string directory)
{
List<byte> bytes &#61; new List<byte>();
foreach (string file in Directory.GetFiles(directory))
{
if (!IsImageFile(file))
continue;
bytes.AddRange(FaceBlock.FromImage(file).ToBytes());
}
string fName &#61; Path.Combine(directory, Path.GetDirectoryName(directory) &#43; ".cfc");
FileStream fs &#61; File.Create(fName);
fs.Write(bytes.ToArray(), 0, bytes.Count);
fs.Close();
}
//判断是否为图像文件&#xff0c;方法比较简陋。
private static bool IsImageFile(string file)
{
List<string> validExt &#61; new List<string>(new string[]{
".bmp",
".jpg",
".jpeg",
".gif",
".png",
});
return validExt.Contains(Path.GetExtension(file).ToLower());
}
}
好&#xff0c;有了上面的方法&#xff0c;我们就可以调用了。
调用方法实在是有些简单。
FaceHelper.BuildCFCFileFromDirectory(Server.MapPath("~/img/"));
这样就OK了&#xff0c;现在去你的网站根目录下看看&#xff0c;有没有一个img.cfc的文件呢&#xff1f;再双击一下&#xff0c;是不是将img目录下的文件全部导入到QQ表情里了呢&#xff1f; enjoy coding!
本文原发&#xff1a;无垠IT教学网
如有不妥&#xff0c;请各位光临论坛指教。
posted on 2006-10-03 15:15 嘻哈呵嘿 阅读(...) 评论(...) 编辑 收藏