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

本文介绍如何使用.NET实现QQ表情包CFC格式的打包功能。解析CFC文件格式,并提供详细代码示例,包括创建FaceBlock结构体及FaceHelper辅助类。

背景:最近闲来无事,也应论坛某会员要求,想做个QQ表情下载的站点。本来事情是很简单的,写个小小的CRUD也就可以了,但嘻哈呵嘿既然是个.Net程序员,当然要使用.Net来实现了。今天我们就用.Net来实现CFC ( custom face cab? ) 的表情格式的打包功能。

要做到这个功能,我们必须先了解这个格式,首先Google一下。我们找到了这一篇来自清华大学的文章:FC文件格式详解

从这篇文章里我们得知了CFC的文件格式大概如下:

一个块有15个字段,如下

  1. md5的字符串形式长度,4个字节
  2. 快捷键长度,4字节
  3. 表情名称长度,4字节
  4. 表情文件名长度,4字节
  5. 表情文件长度,4字节
  6. 微缩图文件名长度,4字节
  7. 微缩文件长度,4字节
  8. 表情文件帧数,4字节
  9. 图片md5的字符串形式
  10. 快捷键
  11. 表情名称
  12. 表情文件名
  13. 微缩图文件名
  14. 表情文件内容
  15. 微缩图内容
知道了格式就好办了,我们按步就班定义一个结构(struct)
 1 ContractedBlock.gif ExpandedBlockStart.gif      Struct #region Struct
 2InBlock.gif    public struct FaceBlock
 3ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 4InBlock.gif        public uint MD5Length; //32
 5InBlock.gif        public uint uintcutLength; //4
 6InBlock.gif        public uint FaceNameLength; //4
 7InBlock.gif        public uint FaceFileNameLength; //36 md5 + extension
 8InBlock.gif        public uint FileLength;
 9InBlock.gif        public uint ThumbnailFileNameLength; //41 md5 + fixed.bmp
10InBlock.gif        public uint ThumbnailFileLength;
11InBlock.gif        public uint FrameLength;
12InBlock.gif        public string MD5;
13InBlock.gif        public string uintcuts;
14InBlock.gif        public string FaceName;
15InBlock.gif        public string FaceFileName;
16InBlock.gif        public string ThumbnailFileName;
17InBlock.gif        public byte[] FaceData;
18InBlock.gif        public byte[] ThumbnailData;
19InBlock.gif
20InBlock.gif        public static FaceBlock FromImage(string file)
21ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
22InBlock.gif            return FaceHelper.GetFaceBlockFromImage(file);
23ExpandedSubBlockEnd.gif        }

24InBlock.gif
25InBlock.gif        byte[] GetBytes(uint value)
26ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
27InBlock.gif            byte[] bt = BitConverter.GetBytes(value);
28InBlock.gif            List<byte> bytes = new List<byte>();
29InBlock.gif            bytes.AddRange(bt);
30InBlock.gif            if (bytes.Count < 4)
31ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
32InBlock.gif                int l = 4 - bytes.Count;
33InBlock.gif                for (int i = 0; i < l; i++)
34InBlock.gif                    bytes.Add((byte)0);
35ExpandedSubBlockEnd.gif            }

36InBlock.gif            return bytes.ToArray();
37ExpandedSubBlockEnd.gif        }

38InBlock.gif
39InBlock.gif        public byte[] ToBytes()
40ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
41InBlock.gif            List<byte> bytes = new List<byte>();
42InBlock.gif            Encoding e = Encoding.ASCII;
43InBlock.gif            bytes.AddRange(GetBytes(MD5Length));
44InBlock.gif            bytes.AddRange(GetBytes(uintcutLength));
45InBlock.gif            bytes.AddRange(GetBytes(FaceNameLength));
46InBlock.gif            bytes.AddRange(GetBytes(FaceFileNameLength));
47InBlock.gif            bytes.AddRange(GetBytes(FileLength));
48InBlock.gif            bytes.AddRange(GetBytes(ThumbnailFileNameLength));
49InBlock.gif            bytes.AddRange(GetBytes(ThumbnailFileLength));
50InBlock.gif            bytes.AddRange(GetBytes(FrameLength));
51InBlock.gif
52InBlock.gif            bytes.AddRange(e.GetBytes(MD5));
53InBlock.gif            bytes.AddRange(e.GetBytes(uintcuts));
54InBlock.gif            bytes.AddRange(e.GetBytes(FaceName));
55InBlock.gif            bytes.AddRange(e.GetBytes(FaceFileName));
56InBlock.gif            bytes.AddRange(e.GetBytes(ThumbnailFileName));
57InBlock.gif
58InBlock.gif            bytes.AddRange(FaceData);
59InBlock.gif            bytes.AddRange(ThumbnailData);
60InBlock.gif
61InBlock.gif            return bytes.ToArray();
62ExpandedSubBlockEnd.gif        }

63ExpandedSubBlockEnd.gif    }

64ExpandedBlockEnd.gif    #endregion
其中含有两方法,一个是从文件得到一个此结构的静态方法,另一个是将此结构转化为byte数组。

我们再建一个类,命名为:FaceHelper
代码如下:
None.gif      public   class  FaceHelper
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        
internal static FaceBlock GetFaceBlockFromImage(string file)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            FaceBlock fb 
= new FaceBlock();
InBlock.gif            
//打开文件流   
InBlock.gif
            FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read);
InBlock.gif            
//获取图像
InBlock.gif
            Image img = Image.FromStream(fs);
InBlock.gif            
//获得一个20*20的缩略图
InBlock.gif
            Image thumbnail = img.GetThumbnailImage(2020null, IntPtr.Zero);
InBlock.gif            MemoryStream ms 
= new MemoryStream();
InBlock.gif            
//将缩图图转化数byte数组
InBlock.gif
            thumbnail.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
InBlock.gif            
byte[] thumbnailData = ms.ToArray();
InBlock.gif            ms.Close();
InBlock.gif            ms.Dispose();
InBlock.gif            thumbnail.Dispose();
InBlock.gif
InBlock.gif            
//得到一个唯一的MD5字符串
InBlock.gif
            string md5 = GetMD5(fs);
InBlock.gif            
//文件名,格式为:md5 + 扩展名
InBlock.gif
            string fileName = string.Format("{0}{1}", md5, Path.GetExtension(file));
InBlock.gif            
//缩略图文件名,格式为:md5 + fixed.bmp
InBlock.gif
            string thumbnailName = string.Format("{0}fixed.bmp", md5);
InBlock.gif            
//随机设置一个快捷键
InBlock.gif
            string uintcuts = "qq.5inet.net_" + RandomNum(6);
InBlock.gif            fs.Close();
InBlock.gif            fs.Dispose();
InBlock.gif
InBlock.gif            
//取得总的帧数
InBlock.gif
System.Drawing.Imaging.FrameDimension fd = System.Drawing.Imaging.FrameDimension.Resolution;
InBlock.gif            
int frameCount = img.FrameDimensionsList.Length;
InBlock.gif            img.Dispose();
InBlock.gif
InBlock.gif            fb.MD5 
= md5;
InBlock.gif            fb.MD5Length 
= (uint)md5.Length;
InBlock.gif            fb.uintcuts 
= uintcuts;
InBlock.gif            fb.uintcutLength 
= (uint)uintcuts.Length;
InBlock.gif            fb.FaceName 
= uintcuts;
InBlock.gif            fb.FaceNameLength 
= (uint)uintcuts.Length;
InBlock.gif            fb.FaceFileName 
= fileName;
InBlock.gif            fb.FaceFileNameLength 
= (uint)fileName.Length;
InBlock.gif            fb.ThumbnailFileName 
= thumbnailName;
InBlock.gif            fb.ThumbnailFileNameLength 
= (uint)thumbnailName.Length;
InBlock.gif            fb.FaceData 
= File.ReadAllBytes(file);
InBlock.gif            fb.FileLength 
= (uint)fb.FaceData.Length;
InBlock.gif            fb.ThumbnailData 
= thumbnailData;
InBlock.gif            fb.ThumbnailFileLength 
= (uint)thumbnailData.Length;
InBlock.gif            fb.FrameLength 
= (uint)frameCount;
InBlock.gif
InBlock.gif            
return fb;
ExpandedSubBlockEnd.gif        }

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

InBlock.gif                
//int t =  rand.Next(35) ;
InBlock.gif
                int t = rand.Next(10);
InBlock.gif                
if (temp != -1 && temp == t)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    
return RandomNum(n);
ExpandedSubBlockEnd.gif                }

InBlock.gif                temp 
= t;
InBlock.gif                VNum 
+= VcArray[t];
ExpandedSubBlockEnd.gif            }

InBlock.gif            
return VNum;//返回生成的随机数
ExpandedSubBlockEnd.gif
        }

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

InBlock.gif
InBlock.gif            
return str.ToUpper();
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif        
#endregion

InBlock.gif
InBlock.gif        
//从一个目录生成一个CFC文件集合
InBlock.gif
        public static void 
InBlock.gifBuildCFCFileFromDirectory(
string directory)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            List
<byte> bytes = new List<byte>();
InBlock.gif            
foreach (string file in Directory.GetFiles(directory))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
if (!IsImageFile(file))
InBlock.gif                    
continue;
InBlock.gif
InBlock.gif                bytes.AddRange(FaceBlock.FromImage(file).ToBytes());
ExpandedSubBlockEnd.gif            }

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

InBlock.gif
InBlock.gif        
//判断是否为图像文件,方法比较简陋。
InBlock.gif
        private static bool IsImageFile(string file)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif            List
<string> validExt = new List<string>(new string[]dot.gif{
InBlock.gif                
".bmp",
InBlock.gif                
".jpg",
InBlock.gif                
".jpeg",
InBlock.gif                
".gif",
InBlock.gif                
".png",
ExpandedSubBlockEnd.gif            }
);
InBlock.gif
InBlock.gif            
return validExt.Contains(Path.GetExtension(file).ToLower());
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

好,有了上面的方法,我们就可以调用了。
调用方法实在是有些简单。

None.gif FaceHelper.BuildCFCFileFromDirectory(Server.MapPath( " ~/img/ " ));

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

本文原发: 无垠IT教学网
如有不妥,请各位光临 论坛指教。
posted on 2006-10-03 15:15 嘻哈呵嘿 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/skyover/archive/2006/10/03/520581.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值