FLIC动画格式包含FLI和FLC两种常见子类型,其核心是将多帧动画数据按顺序存储,每帧包含像素更新、调色板调整等不同操作信息,c++解析时需要先读取文件头确定整体参数,再逐帧解析对应数据。

FLIC文件基础结构
FLIC文件开头是固定的文件头,之后跟随多个帧数据块,每个帧数据块开头也有对应的帧头,解析时需要先处理文件头获取动画的基础信息。
文件头结构定义
FLIC文件头的长度固定为80字节,不同字段存储了动画的帧数、宽度、高度等核心参数,我们可以用结构体来映射这部分数据。
#include <fstream>
#include <cstdint>
#include <vector>
#include <iostream>
// FLIC文件头结构体,按小端字节序存储
#pragma pack(push, 1)
struct FlicFileHeader {
uint32_t file_size; // 整个文件的大小
uint16_t magic; // 魔术数字,FLI为0xAF11,FLC为0xAF12
uint16_t frames; // 总帧数
uint16_t width; // 动画宽度
uint16_t height; // 动画高度
uint16_t depth; // 颜色深度,通常为8
uint16_t flags; // 标志位
uint32_t speed; // 播放速度,FLI单位为毫秒,FLC单位为1/70秒
uint16_t reserved1; // 保留字段
uint32_t created_time; // 创建时间
uint32_t creator; // 创建者
uint32_t updated_time; // 更新时间
uint32_t updater; // 更新者
uint16_t aspect_x; // 宽高比X
uint16_t aspect_y; // 宽高比Y
uint8_t reserved2[38]; // 剩余保留字段
};
#pragma pack(pop)
帧头结构定义
每个帧数据块的开头是帧头,存储了当前帧的大小、类型等信息,帧类型决定了后续数据的解析方式。
// FLIC帧头结构体
#pragma pack(push, 1)
struct FlicFrameHeader {
uint32_t frame_size; // 当前帧的总大小
uint16_t frame_type; // 帧类型,0xF1FA为普通帧,0xF1FB为前缀帧
uint16_t chunk_count; // 当前帧包含的块数量
uint8_t reserved[8]; // 保留字段
};
#pragma pack(pop)
逐帧解析核心逻辑
解析流程分为三步:首先读取文件头验证格式合法性,然后循环读取每一帧的帧头,最后根据帧头信息读取并处理帧内的数据块。
文件头验证与读取
打开FLIC文件后先读取文件头,检查魔术数字是否符合FLI或FLC的规范,同时提取动画的宽度、高度、总帧数等参数。
bool readFlicFileHeader(std::ifstream& file, FlicFileHeader& header) {
if (!file.read(reinterpret_cast<char*>(&header), sizeof(FlicFileHeader))) {
std::cerr << "读取FLIC文件头失败" << std::endl;
return false;
}
// 验证魔术数字
if (header.magic != 0xAF11 && header.magic != 0xAF12) {
std::cerr << "不是合法的FLIC文件" << std::endl;
return false;
}
std::cout << "FLIC文件信息:宽度=" << header.width
<< ",高度=" << header.height
<< ",总帧数=" << header.frames
<< ",颜色深度=" << header.depth << std::endl;
return true;
}
单帧数据解析
读取帧头后,根据chunk_count循环读取帧内的每个数据块,不同块类型对应不同的数据处理逻辑,常见的有调色板块和像素数据块。
// 帧内数据块头
#pragma pack(push, 1)
struct FlicChunkHeader {
uint32_t chunk_size; // 块大小
uint16_t chunk_type; // 块类型
};
#pragma pack(pop)
// 解析单帧数据
bool parseFlicFrame(std::ifstream& file, const FlicFrameHeader& frameHeader, int frameIndex) {
std::cout << "解析第" << frameIndex << "帧,帧大小=" << frameHeader.frame_size
<< ",块数量=" << frameHeader.chunk_count << std::endl;
for (int i = 0; i < frameHeader.chunk_count; ++i) {
FlicChunkHeader chunkHeader;
if (!file.read(reinterpret_cast<char*>(&chunkHeader), sizeof(FlicChunkHeader))) {
std::cerr << "读取帧内块头失败" << std::endl;
return false;
}
// 跳过当前块的数据部分,移动到下一个块
file.seekg(chunkHeader.chunk_size - sizeof(FlicChunkHeader), std::ios::cur);
}
return true;
}
完整解析流程示例
将前面的逻辑整合,实现完整的FLIC文件帧数据解析流程,输出每一帧的基础信息。
int main() {
std::ifstream flicFile("test.flc", std::ios::binary);
if (!flicFile.is_open()) {
std::cerr << "无法打开FLIC文件" << std::endl;
return 1;
}
FlicFileHeader fileHeader;
if (!readFlicFileHeader(flicFile, fileHeader)) {
return 1;
}
// 跳过文件头之后的保留区域,移动到第一帧开头
flicFile.seekg(80, std::ios::beg);
for (int i = 0; i < fileHeader.frames; ++i) {
FlicFrameHeader frameHeader;
if (!flicFile.read(reinterpret_cast<char*>(&frameHeader), sizeof(FlicFrameHeader))) {
std::cerr << "读取第" << i << "帧头失败" << std::endl;
break;
}
if (!parseFlicFrame(flicFile, frameHeader, i)) {
break;
}
}
flicFile.close();
return 0;
}
注意事项
- FLIC文件采用小端字节序存储,在x86架构下可以直接读取结构体,如果是大端架构需要做字节序转换
- 帧内数据块的大小包含块头本身,读取数据时需要减去块头的大小再偏移文件指针
- FLC和FLI的播放速度单位不同,处理播放逻辑时需要根据魔术数字区分计算
- 解析像素数据时需要结合调色板信息,才能实现正确的颜色还原
C++FLIC_animationframe_data_parsingbinary_file_read修改时间:2026-06-12 20:03:35