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

分类: asp.net技巧   出处:iocblog整理  更新时间:2008-10-05   添加到收藏  

背景:最近闲来无事,也应论坛某会员要求,想做个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的缩略图
            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
        //随机方法[www.iocblog.net 来源]
        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;
            int i, j;
            foreach (byte b in md5byte)
            {
                i = convert.toint32(b);
                j = i >> 4;
                str += (convert.tostring(j, 16));
                j = ((i << 4) & 0x00ff) >> 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());
        }
    }
好,有了上面的方法,我们就可以调用了。
调用方法实在是有些简单。[www.iocblog.net 来源]

facehelper.buildcfcfilefromdirectory(server.mappath("~/img/"));
这样就ok了,现在去你的网站根目录下看看,有没有一个img.cfc的文件呢?再双击一下,是不是将img目录下的文件全部导入到qq表情里了呢? enjoy coding!

本文原发:http://www.cnblogs.com/skyover/archive/2006/10/03/520581.html