【转】编程实现QQ表情文件CFC格式

做了一个小站,需提供QQ表情包下载,虽说cfc表情包腾讯已经不在用了,网上找不到eiq格式的分析,cfc也就凑合的用了。文章作者分析的比较清楚了。

编程实现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的字符串形式
快捷键
表情名称
表情文件名
微缩图文件名
表情文件内容
微缩图内容
知道了格式就好办了,我们按步就班定义一个结构(struct)
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 = new List<byte>();
29             bytes.AddRange(bt);
30             if (bytes.Count < 4)
31             {
32                 int l = 4 – bytes.Count;
33                 for (int i = 0; i < l; i++)
34                     bytes.Add((byte)0);
35             }
36             return bytes.ToArray();
37         }
38
39         public byte[] ToBytes()
40         {
41             List<byte> bytes = new List<byte>();
42             Encoding e = 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其中含有两方法,一个是从文件得到一个此结构的静态方法,另一个是将此结构转化为byte数组。

我们再建一个类,命名为:FaceHelper
代码如下:
public class FaceHelper
{
internal static FaceBlock GetFaceBlockFromImage(string file)
{
FaceBlock fb = new FaceBlock();
//打开文件流
FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read);
//获取图像
Image img = Image.FromStream(fs);
//获得一个20*20的缩略图
&nb
sp;   Image thumbnail = img.GetThumbnailImage(20, 20, null, IntPtr.Zero);
MemoryStream ms = new MemoryStream();
//将缩图图转化数byte数组
thumbnail.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
byte[] thumbnailData = ms.ToArray();
ms.Close();
ms.Dispose();
thumbnail.Dispose();

//得到一个唯一的MD5字符串
string md5 = GetMD5(fs);
//文件名,格式为:md5 + 扩展名
string fileName = string.Format(“{0}{1}”, md5, Path.GetExtension(file));
//缩略图文件名,格式为:md5 + fixed.bmp
string thumbnailName = string.Format(“{0}fixed.bmp”, md5);
//随机设置一个快捷键
string uintcuts = “qq.5inet.net_” + RandomNum(6);
fs.Close();
fs.Dispose();

//取得总的帧数
System.Drawing.Imaging.FrameDimension fd = System.Drawing.Imaging.FrameDimension.Resolution;
int frameCount = img.FrameDimensionsList.Length;
img.Dispose();

fb.MD5 = md5;
fb.MD5Length = (uint)md5.Length;
fb.uintcuts = uintcuts;
fb.uintcutLength = (uint)uintcuts.Length;
fb.FaceName = uintcuts;
fb.FaceNameLength = (uint)uintcuts.Length;
fb.FaceFileName = fileName;
fb.FaceFileNameLength = (uint)fileName.Length;
fb.ThumbnailFileName = thumbnailName;
fb.ThumbnailFileNameLength = (uint)thumbnailName.Length;
fb.FaceData = File.ReadAllBytes(file);
fb.FileLength = (uint)fb.FaceData.Length;
fb.ThumbnailData = thumbnailData;
fb.ThumbnailFileLength = (uint)thumbnailData.Length;
fb.FrameLength = (uint)frameCount;

return fb;
}

Helper#region Helper
//随机方法
internal static string RandomNum(int n) //
{
string strchar = “0,1,2,3,4,5,6,7,8,9″;
string[] VcArray = strchar.Split(‘,’);
string VNum = “”;//由于字符串很短,F77pclw,c络G|?,业,e’b就不用StringBuilder了
int temp = -1;     //记录上次随机数值,尽量避免产生几个一样的随机数
//采用一个简单的算法以保证生成随机数的不同
Random rand = new Random();
for (int i = 1; i < n + 1; i++)
{
if (temp != -1)
{
rand = new Random(i * temp * unchecked((int)

DateTime.Now.Ticks));
}
//int t =   rand.Next(35) ;
int t = rand.Next(10);
if (temp != -1 && temp == t)
{
return RandomNum(n);
}
temp = t;
VNum += VcArray[t];
}
return VNum;//返回生成的随机数
}

//从文件名获得MD5
internal static string GetMD5(FileStream fs)
{
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] md5byte = md5.ComputeHash(fs);
string str = string.Empty;
&nbsp
;        int i, j;
foreach (byte b in md5byte)
{
i = Convert.ToInt32(b);
j = i >> 4;
str += (Convert.ToString(j, 16));
j = ((i << 4) & 0×00ff) >> 4;
str += (Convert.ToString(j, 16));
}

return str.ToUpper();
}
#endregion

//从一个目录生成一个CFC文件集合
public static void
BuildCFCFileFromDirectory(string directory)
{
List<byte> bytes = new List<byte>();
foreach (string file in Directory.GetFiles(directory))
{
if (!IsImageFile(file))
continue;

bytes.AddRange(FaceBlock.FromImage(file).ToBytes());
}

string fName = Path.Combine(directory, Path.GetDirectoryName(directory) + “.cfc”);
FileStream fs = File.Create(fName);
fs.Write(bytes.ToArray(), 0, bytes.Count);
fs.Close();
}

//判断是否为图像文件,方法比较简陋。
private static bool IsImageFile(string file)
{
List<string> validExt = new List<string>(new string[]{
“.bmp”,
“.jpg”,
“.jpeg”,
“.gif”,
“.png”,
});

return validExt.Contains(Path.GetExtension(file).ToLower());
}
}
好,有了上面的方法,我们就可以调用了。
调用方法实在是有些简单。

FaceHelper.BuildCFCFileFromDirectory(Server.MapPath(“~/img/”));
这样就OK了,现在去你的网站根目录下看看,有没有一个img.cfc的文件呢?再双击一下,是不是将img目录下的文件全部导入到QQ表情里了呢? enjoy coding!

随机日志

发表评论

Your email is never shared. 标记为 * 的为必填项目

*
*