⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 cfcimporter.java

📁 lumaQQ的源文件
💻 JAVA
字号:
/*
* LumaQQ - Java QQ Client
*
* Copyright (C) 2004 notXX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.tsinghua.lumaqq.customface;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;

import edu.tsinghua.lumaqq.ecore.face.FaceConstant;
import edu.tsinghua.lumaqq.ecore.face.FaceGroup;
import edu.tsinghua.lumaqq.qq.Util;
import edu.tsinghua.lumaqq.ui.helper.FileTool;

/**
 * QQ自定义表情CFC格式文件的导入器,CFC文件是一个比较简单的文件格式,它比EIP要简单很多,
 * 缺点是CFC文件不能支持表情分组。
 * <p>
 * CFC文件的格式是一系列连续的块,每个块是一个GIF和BMP文件,BMP文件是GIF的微缩图,我们
 * 知道QQ在选择自定义表情的时候都会把它们的微缩图显示出来给我们选。LumaQQ只关心GIF,BMP
 * 暂且用不上。
 * </p>
 * <p>
 * CFC文件没有文件头,也没有文件尾,它就是一系列连续的块组成的,所以下面介绍块的格式,我们
 * 要注意的是,这个文件里面的整数都是little-endian格式.
 * </p>
 * <p>
 * 1. md5的字符串形式长度,4个字节,这个一般都是0x00000020,因为MD5是16字节
 * 2. 快捷键长度,4字节
 * 3. 表情名称长度,4字节
 * 4. 表情文件名长度,4字节,加上".GIF",所以这个字段一般都是0x00000024
 * 5. 表情文件长度,4字节
 * 6. 微缩图文件名长度,4字节,因为微缩图一般都是MD5 + "fixed.bmp",所以这个字段一般都是0x00000029
 * 7. 微缩文件长度,4字节
 * 8. 表情文件帧数,4字节,因为表情文件可能是动画,动画自然是多帧的
 * 9. 图片md5的字符串形式
 * 10. 快捷键
 * 11. 表情名称
 * 12. 表情文件名
 * 13. 微缩图文件名
 * 14. 表情文件内容
 * 15. 微缩图内容
 * </p>
 * 
 * @author luma
 */
public class CFCImporter {
    private FaceEntry entry;
    private String destDir;
    private FaceGroup group;
    
    private RandomAccessFile cfcFile;
    
    private long nextEntryOffset;
    private long cfcFileLength;
    
    private byte[] buffer;
    
    private int md5Length;
    private int shortcutLength;
    private int nameLength;
    private int fileNameLength;
    private int fileLength;
    private int thumbFileNameLength;
    private int thumbFileLength;
    
    /**
     * 创建一个CFC文件导入器
     * 
     * @param file
     * 		CFC文件路径
     * @param destDir
     * 		保存图片的目的路径
     * @param g
     * 		保存到的组
     */
    public CFCImporter(String file, String destDir, FaceGroup g) {
        this.destDir = destDir;        
        nextEntryOffset = 0;
        buffer = new byte[10000];
        entry = new FaceEntry();
        group = g;
        
        try {
            cfcFile = new RandomAccessFile(file, "r");
            cfcFileLength = cfcFile.length();
        } catch (IOException e) {
            cfcFileLength = -1;
        }
    }
    
    /**
     * 释放资源
     */
    public void dispose() {
        buffer = null;
        try {
            cfcFile.close();
        } catch (IOException e) {
        }
    }
    
    /**
     * @return
     * 		下一个表情项内容,如果为null,表示没有更多项了
     */
    public FaceEntry getNextEntry() {
        if(nextEntryOffset >= cfcFileLength)
            return null;
        
        boolean success = readEntry();
        while(nextEntryOffset < cfcFileLength && !success)
            success = readEntry();
        
        return success ? entry : null;
    }
    
	/**
	 * 从offset位置读取4个字节为一个long,因为java为big-endian格式,所以没办法
	 * 用了这么一个函数来做转换
	 * @param offset
	 * @return 读取的long值,返回-1表示读取文件失败
	 */
	private int readInt4(long offset) {
	    int ret = 0;
		try {
		    cfcFile.seek(offset);
			ret |= (cfcFile.readByte() & 0xFF);
			ret |= ((cfcFile.readByte() << 8) & 0xFF00);
			ret |= ((cfcFile.readByte() << 16) & 0xFF0000);
			ret |= ((cfcFile.readByte() << 24) & 0xFF000000);
			return ret;
		} catch (IOException e) {
			return -1;
		}
	}
	
	/**
	 * 从指定位置读取一个指定长度的字符串
	 * 
	 * @param offset
	 * 		起始偏移
	 * @param length
	 * 		字符串长度
	 * @return
	 * 		字符串,失败返回null
	 */
	private String readString(long offset, int length) {
	    try {
            byte[] buf = new byte[length];
            cfcFile.seek(offset);
            cfcFile.readFully(buf);
            return Util.getString(buf);
        } catch (IOException e) {
            return null;
        }
	}
	
	/**
	 * 从指定位置读取一段内容
	 * 
	 * @param offset
	 * 		起始偏移
	 * @param length
	 * 		读取长度
	 * @return
	 * 		true表示成功
	 */
	private boolean readBytes(long offset, int length) {
	    if(length > buffer.length)
	        expandBuffer(length);
	    
	    try {
            cfcFile.seek(offset);
            cfcFile.read(buffer, 0, length);
            return true;
        } catch (IOException e) {
            return false;
        }
	}
	
	/**
	 * 读取下一个face entry。读取完毕后,nextEntryOffset将被置为下一个entry的偏移
	 * 
	 * @return
	 * 		true表示读取成功
	 */
	private boolean readEntry() {
	    long offset = nextEntryOffset;
	    
	    md5Length = readInt4(offset);
	    offset += 4;
	    
	    shortcutLength = readInt4(offset);
	    offset += 4;
	    
	    nameLength = readInt4(offset);
	    offset += 4;
	    
	    fileNameLength = readInt4(offset);
	    offset += 4;
	    
	    fileLength = readInt4(offset);
	    offset += 4;
	    
	    thumbFileNameLength = readInt4(offset);
	    offset += 4;
	    
	    thumbFileLength = readInt4(offset);
	    offset += 4;
	    
	    // 读取md5
	    offset += 4;	    
	    entry.md5 = readString(offset, md5Length);
	    offset += md5Length;
	    // shortcut
	    entry.shortcut = readString(offset, shortcutLength);
	    offset += shortcutLength;
	    // 名称
	    entry.name = readString(offset, nameLength);
	    offset += nameLength;
	    // 文件名
	    entry.filename = readString(offset, fileNameLength);
	    offset += fileNameLength;
	    
	    // 读取文件内容
	    offset += thumbFileNameLength;
	    if(!readBytes(offset, fileLength)) {
	        nextEntryOffset = offset + fileLength + thumbFileLength;
	        return false;
	    }
	    
	    nextEntryOffset = offset + fileLength + thumbFileLength;
	    return true;
	}

    /**
     * 保存这个块中的图片文件到目标目录
     * 
     * @return
     * 		true表示保存成功
     */
    public boolean saveEntry() {
    	if(group.getId() == FaceConstant.CUSTOM_HEAD_GROUP_ID) 
    		return saveCustomHead();
    	else {
    		// 保存表情文件
    		String filename = destDir + group.getId() + '/' + entry.filename;
    		if(!FileTool.saveFile(buffer, 0, fileLength, filename))
    			return false;
    		
    		// 保存缩略图
    		try {
    			ImageLoader loader = new ImageLoader();
    			loader.load(filename);
    			ImageData data = loader.data[0].scaledTo(20, 20);
    			loader = new ImageLoader();
    			loader.data = new ImageData[] { data };
    			loader.save(destDir + group.getId() + '/' + entry.md5 + "fixed.bmp", SWT.IMAGE_BMP);
    		} catch (SWTException e) {
    			return false;
    		}    
    		return true;    		
    	}
    }

	/**
	 * 保存文件为自定义头像
	 * 
     * @return
     * 		true表示保存成功
	 */
	private boolean saveCustomHead() {
		try {
			// 生成ImageData
			ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, fileLength);
			ImageData origin = new ImageData(bais);
			ImageData data = origin.scaledTo(40, 40);
			
			// save 40x40 bmp
			ImageLoader saveLoader = new ImageLoader();
			saveLoader.data = new ImageData[] { data };
			saveLoader.save(destDir + group.getId() + '/' + entry.md5 + ".bmp", SWT.IMAGE_BMP);
			
			// save 20x20 bmp
			data = origin.scaledTo(20, 20);
			saveLoader = new ImageLoader();
			saveLoader.data = new ImageData[] { data };
			saveLoader.save(destDir + group.getId() + '/' + entry.md5 + "fixed.bmp", SWT.IMAGE_BMP);
			
			return true;
		} catch(SWTException e) {
			return false;
		}
	}

	/**
     * 扩展缓冲区
     */
    private void expandBuffer(int length) {
        buffer = new byte[length];
    }
}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -