1+ #pragma description FLC/FLIC animation file
2+ #pragma endian little
3+ #pragma magic [12 AF] @ 0x4
4+
5+ // A flic file could contain many DELTA_FLC chunks, which contain many RLE packets
6+ //#define DECODE_DELTA_FLC
7+
8+ import std.mem;
9+ import std.core;
10+ import std.io;
11+ import type.color;
12+
13+ using Color = type::RGB8 [[hex::inline_visualize("color", r, g, b, 0xff)]];
14+ bitfield Color64 {
15+ r : 6;
16+ padding : 2;
17+ g : 6;
18+ padding : 2;
19+ b : 6;
20+ padding : 2;
21+
22+ u8 r8 = r << 2 | r >> 4;
23+ u8 g8 = g << 2 | g >> 4;
24+ u8 b8 = b << 2 | b >> 4;
25+ } [[hex::inline_visualize("color", r8, g8, b8, 0xff)]];
26+
27+ enum FlicType : u16 {
28+ FLI = 0xAF11,
29+ FLC_8bit = 0xAF12,
30+ FLIC_other = 0xAF44,
31+ FLIC_Huffmann = 0xAF30,
32+ FLIC_frame_shift = 0xAF31,
33+ };
34+
35+ enum ChunkType : u16 {
36+ CEL_DATA = 3, // Registration and transparency
37+ COLOR_256 = 4, // 256-level colour palette
38+ DELTA_FLC = 7, // Delta image, word oriented RLE (FLI_SS2)
39+ COLOR_64 = 11, // 64-level colour palette
40+ DELTA_FLI = 12, // Delta image, byte oriented RLE (FLI_LC)
41+ BLACK = 13, // Full black frame (rare)
42+ BYTE_RUN = 15, // Full image, byte oriented RLE (FLI_BRUN)
43+ FLI_COPY = 16, // Uncompressed image (rare)
44+ PSTAMP = 18, // Postage stamp (icon of the first frame)
45+ DTA_BRUN = 25, // Full image, pixel oriented RLE
46+ DTA_COPY = 26, // Uncompressed image
47+ DTA_LC = 27, // Delta image, pixel oriented RLE
48+ LABEL = 31, // Frame label
49+ BMP_MASK = 32, // Bitmap mask
50+ MLEV_MASK = 33, // Multilevel mask
51+ SEGMENT = 34, // Segment information
52+ KEY_IMAGE = 35, // Key image, similar to BYTE_RUN / DTA_BRUN
53+ KEY_PAL = 36, // Key palette, similar to COLOR_256
54+ REGION = 37, // Region of frame differences
55+ WAVE = 38, // Digitized audio
56+ USERSTRING = 39, // General purpose user data
57+ RGN_MASK = 40, // Region mask
58+ LABELEX = 41, // Extended frame label (includes symbolic name)
59+ SHIFT = 42, // Scanline delta shifts (compression)
60+ PATHMAP = 43, // Path map (segment transitions)
61+
62+ PREFIX_TYPE = 0xF100, // Prefix chunk
63+ SCRIPT_CHUNK = 0xF1E0, // Embedded "Small" script
64+ FRAME_TYPE = 0xF1FA, // Frame chunk
65+ SEGMENT_TABLE = 0xF1FB, // Segment table chunk
66+ HUFFMAN_TABLE = 0xF1FC // Huffman compression table chunk
67+ };
68+
69+ bitfield ExtFlags {
70+ segment_table_present : 1;
71+ regular_key_imagees : 1;
72+ identical_palette : 1;
73+ huffmann_compressed : 1;
74+ bwt_huffmann_compressed : 1;
75+ bitmap_masks_present : 1;
76+ multilevel_masks_present : 1;
77+ region_data_present : 1;
78+ password_protected : 1;
79+ digitized_audio : 1;
80+ contains_script : 1;
81+ region_masks_present : 1;
82+ contains_overlays : 1;
83+ frame_shift_compressed : 1;
84+ padding : 2;
85+ };
86+
87+ u16 pixels_per_line = 0;
88+ u16 lines = 0;
89+
90+ struct Header {
91+ u32 size [[comment("Size of FLIC including this header")]];
92+ FlicType type [[comment("File type 0xAF11, 0xAF12, 0xAF30, 0xAF44, ...")]];
93+ u16 frames [[comment("Number of frames in first segment")]];
94+ u16 width [[comment("FLIC width in pixels")]];
95+ u16 height [[comment("FLIC height in pixels")]];
96+ u16 depth [[comment("Bits per pixel (usually 8)")]];
97+ u16 flags [[comment("Set to zero or to three")]];
98+ u32 speed [[comment("Delay between frames")]];
99+ u16 reserved1 [[comment("Set to zero")]];
100+ u32 created [[comment("Date of FLIC creation (FLC only)")]];
101+ u32 creator [[comment("Serial number or compiler id (FLC only)")]];
102+ u32 updated [[comment("Date of FLIC update (FLC only)")]];
103+ u32 updater [[comment("Serial number (FLC only), see creator")]];
104+ u16 aspect_dx [[comment("Width of square rectangle (FLC only)")]];
105+ u16 aspect_dy [[comment("Height of square rectangle (FLC only)")]];
106+ ExtFlags ext_flags [[comment("EGI: flags for specific EGI extensions")]];
107+ u16 keyframes [[comment("EGI: key-image frequency")]];
108+ u16 totalframes [[comment("EGI: total number of frames (segments)")]];
109+ u32 req_memory [[comment("EGI: maximum chunk size (uncompressed)")]];
110+ u16 max_regions [[comment("EGI: max. number of regions in a CHK_REGION chunk")]];
111+ u16 transp_num [[comment("EGI: number of transparent levels")]];
112+ u8 reserved2[24] [[comment("Set to zero")]];
113+ u32 oframe1 [[comment("Offset to frame 1 (FLC only)")]];
114+ u32 oframe2 [[comment("Offset to frame 2 (FLC only)")]];
115+ u8 reserved3[40] [[comment("Set to zero")]];
116+
117+ pixels_per_line = width;
118+ lines = height;
119+ };
120+
121+ namespace DeltaFLCChunk {
122+ enum OpcodeType : u8 {
123+ Count = 0b00,
124+ Undefined = 0b01,
125+ LowByte = 0b10,
126+ LineSkipCount = 0b11,
127+ };
128+
129+ struct Packet {
130+ u8 skip_count;
131+ s8 count;
132+ if (count < 0) {
133+ u8 replicate_bytes[2];
134+ } else {
135+ u8 literal_bytes[count * 2];
136+ }
137+ };
138+
139+ u16 packets_in_line;
140+
141+ fn format_opcode(auto opcode) {
142+ match (opcode.type) {
143+ (OpcodeType::Count): return std::format("Packet(s): {0}", opcode.value);
144+ (OpcodeType::Undefined): return "Undefined";
145+ (OpcodeType::LowByte): return std::format("Last byte: {0:02x}", opcode.value);
146+ (OpcodeType::LineSkipCount): {
147+ s16 total = 0xC000 | opcode.value;
148+ return std::format("Lines skipped: {0}", total);
149+ }
150+ }
151+ };
152+
153+ bitfield Opcode {
154+ value: 14;
155+ OpcodeType type: 2;
156+
157+ if (type == OpcodeType::Count) {
158+ packets_in_line = value;
159+ break;
160+ }
161+ } [[format("DeltaFLCChunk::format_opcode")]];
162+
163+ struct Line {
164+ packets_in_line = 0;
165+ Opcode opcodes[while(true)];
166+ Packet packets[packets_in_line];
167+ };
168+ }
169+
170+ namespace ByteRunChunk {
171+ u16 pixels_in_line_counter = 0;
172+
173+ struct Packet {
174+ s8 count;
175+ if (count < 0) {
176+ pixels_in_line_counter = -count + pixels_in_line_counter;
177+ u8 literal_bytes[-count];
178+ } else {
179+ pixels_in_line_counter = count + pixels_in_line_counter;
180+ u8 replicate_byte;
181+ }
182+ } [[format("format_byte_run_packet")]];
183+
184+ struct Line {
185+ pixels_in_line_counter = 0;
186+ u8 packet_count;
187+ Packet packets[while(pixels_in_line_counter < pixels_per_line)];
188+ };
189+ }
190+
191+ struct ColorPalettePacket<C> {
192+ u8 skip;
193+ u8 copy;
194+ if (copy == 0) {
195+ C color[256];
196+ } else {
197+ C color[copy];
198+ }
199+ };
200+
201+ fn format_chunk(auto chunk) {
202+ return std::format("{}", chunk.type);
203+ };
204+
205+ fn format_byte_run_packet(auto packet) {
206+ if (packet.count == -1) {
207+ return std::format("One {}", packet.literal_bytes[0]);
208+ } else if (packet.count < 0) {
209+ return std::format("{} bytes", -packet.count);
210+ } else {
211+ return std::format("{}x color {}", packet.count, packet.replicate_byte);
212+ }
213+ };
214+
215+ struct Chunk {
216+ u32 size;
217+ ChunkType type;
218+
219+ if (type == ChunkType::FRAME_TYPE) {
220+ u16 chunks;
221+ u16 delay;
222+ u16 reserved;
223+ u16 width;
224+ u16 height;
225+ // Not sure if this is the intended operation here?
226+ if (width > 0) {
227+ pixels_per_line = width;
228+ }
229+ if (height > 0) {
230+ lines = height;
231+ }
232+ Chunk subchunks[chunks];
233+ } else if (type == ChunkType::COLOR_256) {
234+ u16 num_packets;
235+ ColorPalettePacket<Color> packets[num_packets];
236+ } else if (type == ChunkType::COLOR_64) {
237+ u16 num_packets;
238+ ColorPalettePacket<Color64> packets[num_packets];
239+ } else if (type == ChunkType::BYTE_RUN) {
240+ ByteRunChunk::Line lines[lines];
241+ } else if (type == ChunkType::DELTA_FLC) {
242+ u16 line_count;
243+ #ifdef DECODE_DELTA_FLC
244+ DeltaFLCChunk::Line lines[line_count];
245+ #endif
246+ #ifndef DECODE_DELTA_FLC
247+ u8 data[size - ($ - addressof(this))];
248+ #endif
249+ } else if (type == ChunkType::PSTAMP) {
250+ u16 height;
251+ u16 width;
252+ u16 xlate;
253+ u8 data[size - ($ - addressof(this))];
254+ } else if (type == ChunkType::FLI_COPY) {
255+ u8 pixels[lines * pixels_per_line];
256+ }
257+ s128 remainingBytes = size - ($ - addressof(this));
258+ // When the chunk size is rounded up to the next even number, it might need a padding byte.
259+ // Unrecognized chunk types will also fill out the padding
260+ if (remainingBytes > 0) {
261+ padding[size - ($ - addressof(this))];
262+ } else if (remainingBytes < 0) {
263+ std::warning("Chunk size wasn't large enough");
264+ }
265+ } [[format("format_chunk")]];
266+
267+ Header header @ $;
268+ Chunk chunks[while(!std::mem::eof())] @ $;
0 commit comments