From bb7b1872750be7ea04a22582a21837dee5c9033c Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Mon, 16 Apr 2018 07:03:17 +0000 Subject: [PATCH] Load FLI/FLC autodesk animator files. --- src/const.h | 1 + src/fileformats.c | 4 +- src/fileformats.h | 4 + src/loadsave.c | 3 +- src/miscfileformats.c | 414 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 422 insertions(+), 4 deletions(-) diff --git a/src/const.h b/src/const.h index 1bcfaaea..6564eee6 100644 --- a/src/const.h +++ b/src/const.h @@ -147,6 +147,7 @@ enum FILE_FORMATS FORMAT_XPM, FORMAT_ICO, FORMAT_INFO, + FORMAT_FLI, FORMAT_MISC, ///< Must be last of enum: others formats recognized by SDL_image }; diff --git a/src/fileformats.c b/src/fileformats.c index 8b1da38d..9cdce691 100644 --- a/src/fileformats.c +++ b/src/fileformats.c @@ -215,8 +215,6 @@ void Save_IMG(T_IO_Context * context) } else { - fclose(file); - Remove_file(context); File_error=1; } } @@ -1946,7 +1944,7 @@ void Load_IFF(T_IO_Context * context) else if (memcmp(section, "BODY", 4) == 0) { long offset = ftell(IFF_file); - if (file_size > (offset + section_size + 8)) + if (file_size > (unsigned long)(offset + section_size + 8)) { // Chunk RAST is placed AFTER the BODY, but we need the palette now to decode the image // In addition, some files break the IFF standard by not aligning diff --git a/src/fileformats.h b/src/fileformats.h index fee43222..a2766d6a 100644 --- a/src/fileformats.h +++ b/src/fileformats.h @@ -137,4 +137,8 @@ void Save_PNG(T_IO_Context *); void Test_INFO(T_IO_Context *, FILE *); void Load_INFO(T_IO_Context *); +// -- FLI/FLC (Autodesk animator) ------------------------------------------- +void Test_FLI(T_IO_Context *, FILE *); +void Load_FLI(T_IO_Context *); + #endif diff --git a/src/loadsave.c b/src/loadsave.c index bb04656e..9a2dff64 100644 --- a/src/loadsave.c +++ b/src/loadsave.c @@ -77,7 +77,7 @@ void Load_Recoil_Image(T_IO_Context *); // ENUM Name TestFunc LoadFunc SaveFunc PalOnly Comment Layers Ext Exts const T_Format File_formats[] = { - {FORMAT_ALL_IMAGES, "(all)", NULL, NULL, NULL, 0, 0, 0, "", "gif;png;bmp;2bp;pcx;pkm;iff;lbm;ilbm;sham;ham;ham6;ham8;acbm;pic;anim;img;sci;scq;scf;scn;sco;pi1;pc1;cel;neo;c64;koa;koala;fli;bml;cdu;prg;tga;pnm;xpm;xcf;jpg;jpeg;tif;tiff;ico;ic2;cur;cm5;pph;info"}, + {FORMAT_ALL_IMAGES, "(all)", NULL, NULL, NULL, 0, 0, 0, "", "gif;png;bmp;2bp;pcx;pkm;iff;lbm;ilbm;sham;ham;ham6;ham8;acbm;pic;anim;img;sci;scq;scf;scn;sco;pi1;pc1;cel;neo;c64;koa;koala;fli;bml;cdu;prg;tga;pnm;xpm;xcf;jpg;jpeg;tif;tiff;ico;ic2;cur;cm5;pph;info;fli;flc"}, {FORMAT_ALL_PALETTES, "(pal)", NULL, NULL, NULL, 1, 0, 0, "", "kcf;pal;gpl"}, {FORMAT_ALL_FILES, "(*.*)", NULL, NULL, NULL, 0, 0, 0, "", "*"}, {FORMAT_GIF, " gif", Test_GIF, Load_GIF, Save_GIF, 0, 1, 1, "gif", "gif"}, @@ -106,6 +106,7 @@ const T_Format File_formats[] = { {FORMAT_XPM, " xpm", NULL, NULL, Save_XPM, 0, 0, 0, "xpm", "xpm"}, {FORMAT_ICO, " ico", Test_ICO, Load_ICO, Save_ICO, 0, 0, 0, "ico", "ico;ic2;cur"}, {FORMAT_INFO," info",Test_INFO,Load_INFO,NULL, 0, 0, 0, "info", "info"}, + {FORMAT_FLI, " fli", Test_FLI, Load_FLI, NULL, 0, 0, 0, "fli", "fli;flc"}, {FORMAT_MISC,"misc.",NULL, NULL, NULL, 0, 0, 0, "", "tga;pnm;xpm;xcf;jpg;jpeg;tif;tiff"}, }; diff --git a/src/miscfileformats.c b/src/miscfileformats.c index ede9a3ca..80da546c 100644 --- a/src/miscfileformats.c +++ b/src/miscfileformats.c @@ -42,6 +42,7 @@ #include "struct.h" #include "windows.h" #include "oldies.h" +#include "pages.h" #include "fileformats.h" //////////////////////////////////// PAL //////////////////////////////////// @@ -3794,3 +3795,416 @@ void Save_PPH(T_IO_Context* context) // R: use 16 used colors (or 16 first?) // B1: find the 16 colors used in a line? Or assume they are in-order already? } + + +/////////////////////////////////// FLI/FLC ///////////////////////////////// +typedef struct { + dword size; /* Size of FLIC including this header */ + word type; /* File type 0xAF11, 0xAF12, 0xAF30, 0xAF44, ... */ + word frames; /* Number of frames in first segment */ + word width; /* FLIC width in pixels */ + word height; /* FLIC height in pixels */ + word depth; /* Bits per pixel (usually 8) */ + word flags; /* Set to zero or to three */ + dword speed; /* Delay between frames */ + word reserved1; /* Set to zero */ + dword created; /* Date of FLIC creation (FLC only) */ + dword creator; /* Serial number or compiler id (FLC only) */ + dword updated; /* Date of FLIC update (FLC only) */ + dword updater; /* Serial number (FLC only), see creator */ + word aspect_dx; /* Width of square rectangle (FLC only) */ + word aspect_dy; /* Height of square rectangle (FLC only) */ + word ext_flags; /* EGI: flags for specific EGI extensions */ + word keyframes; /* EGI: key-image frequency */ + word totalframes; /* EGI: total number of frames (segments) */ + dword req_memory; /* EGI: maximum chunk size (uncompressed) */ + word max_regions; /* EGI: max. number of regions in a CHK_REGION chunk */ + word transp_num; /* EGI: number of transparent levels */ + byte reserved2[24]; /* Set to zero */ + dword oframe1; /* Offset to frame 1 (FLC only) */ + dword oframe2; /* Offset to frame 2 (FLC only) */ + byte reserved3[40]; /* Set to zero */ +} T_FLIC_Header; + +static void Load_FLI_Header(FILE * file, T_FLIC_Header * header) +{ + if (!(Read_dword_le(file,&header->size) + && Read_word_le(file,&header->type) + && Read_word_le(file,&header->frames) + && Read_word_le(file,&header->width) + && Read_word_le(file,&header->height) + && Read_word_le(file,&header->depth) + && Read_word_le(file,&header->flags) + && Read_dword_le(file,&header->speed) + && Read_word_le(file,&header->reserved1) + && Read_dword_le(file,&header->created) + && Read_dword_le(file,&header->creator) + && Read_dword_le(file,&header->updated) + && Read_dword_le(file,&header->updater) + && Read_word_le(file,&header->aspect_dx) + && Read_word_le(file,&header->aspect_dy) + && Read_word_le(file,&header->ext_flags) + && Read_word_le(file,&header->keyframes) + && Read_word_le(file,&header->totalframes) + && Read_dword_le(file,&header->req_memory) + && Read_word_le(file,&header->max_regions) + && Read_word_le(file,&header->transp_num) + && Read_bytes(file,header->reserved2,24) + && Read_dword_le(file,&header->oframe1) + && Read_dword_le(file,&header->oframe2) + && Read_bytes(file,header->reserved2,40) )) + { + File_error=1; + } +} + +void Test_FLI(T_IO_Context * context, FILE * file) +{ + T_FLIC_Header header; + (void)context; + + File_error=0; + Load_FLI_Header(file, &header); + if (File_error != 0) return; + + switch (header.type) + { + case 0xAF11: // standard FLI + case 0xAF12: // FLC (8bpp) +#if 0 + case 0xAF30: // Huffman or BWT compression + case 0xAF31: // frame shift compression + case 0xAF44: // bpp != 8 +#endif + File_error=0; + break; + default: + File_error=1; + } +} + +void Load_FLI(T_IO_Context * context) +{ + FILE * file; + unsigned long file_size; + T_FLIC_Header header; + dword chunk_size; + word chunk_type; + word sub_chunk_count, sub_chunk_index; + dword sub_chunk_size; + word sub_chunk_type; + word frame_delay, frame_width, frame_height; + int current_frame = 0; + + file = Open_file_read(context); + if (file == NULL) + { + File_error=1; + return; + } + File_error=0; + file_size = File_length_file(file); + Load_FLI_Header(file, &header); + if (File_error != 0) + { + fclose(file); + return; + } + if (header.size == 12) + { + // special "magic carpet" format + header.depth = 8; + header.speed = 66; // about 15fps + fseek(file, 12, SEEK_SET); + } + else if (file_size != header.size) + Warning("Load_FLI(): file size mismatch in header"); + + if (header.speed == 0) + { + if (header.type == 0xAF11) // FLI + header.speed = 1; // 1/70th seconds + else + header.speed = 10; // 10ms + } + + Pre_load(context, header.width,header.height,file_size,FORMAT_FLI,PIXEL_SIMPLE,header.depth); + if (context->Type == CONTEXT_MAIN_IMAGE) + { + Main.backups->Pages->Image_mode = IMAGE_MODE_ANIMATION; + Update_screen_targets(); + } + + while (File_error == 0 + && Read_dword_le(file,&chunk_size) && Read_word_le(file,&chunk_type)) + { + chunk_size -= 6; + switch (chunk_type) + { + case 0xf1fa: // FRAME + Read_word_le(file, &sub_chunk_count); + Read_word_le(file, &frame_delay); + fseek(file, 2, SEEK_CUR); + Read_word_le(file, &frame_width); + Read_word_le(file, &frame_height); + if (frame_width == 0) + frame_width = header.width; + if (frame_height == 0) + frame_height = header.height; + if (frame_delay == 0) + frame_delay = header.speed; + chunk_size -= 10; + for (sub_chunk_index = 0; sub_chunk_index < sub_chunk_count; sub_chunk_index++) + { + if (!(Read_dword_le(file,&sub_chunk_size) && Read_word_le(file,&sub_chunk_type))) + File_error = 1; + else + { + chunk_size -= sub_chunk_size; + sub_chunk_size -= 6; + if (sub_chunk_type == 0x04 || sub_chunk_type == 0x0b) // color map + { + word packet_count; + int i = 0; + sub_chunk_size -= 2; + if (!Read_word_le(file, &packet_count)) + File_error = 1; + else + while (packet_count-- > 0 && File_error == 0) + { + byte skip, count; + if (!(Read_byte(file, &skip) && Read_byte(file, &count))) + File_error = 1; + else + { + sub_chunk_size -= 2; + i += skip; // count 0 means 256 + do + { + byte r, g, b; + if (!(Read_byte(file, &r) && Read_byte(file, &g) && Read_byte(file, &b))) + { + File_error = 1; + break; + } + if (sub_chunk_type == 0x0b || header.size == 12) // 6bit per color + { + r = (r << 2) | (r >> 4); + g = (g << 2) | (g >> 4); + b = (b << 2) | (b >> 4); + } + context->Palette[i].R = r; + context->Palette[i].G = g; + context->Palette[i].B = b; + i++; + sub_chunk_size -= 3; + } while (--count != 0); + } + } + } + else if (sub_chunk_type == 0x0f) // full frame RLE + { + word x, y; + if (header.type == 0xAF11) // FLI + Set_frame_duration(context, (frame_delay * 100) / 7); // 1/70th sec + else + Set_frame_duration(context, frame_delay); // msec + for (y = 0; y < frame_height && File_error == 0; y++) + { + byte count, data; + Read_byte(file, &count); // packet count, but dont rely on it + sub_chunk_size--; + for (x = 0; x < frame_width; ) + { + if (!Read_byte(file, &count)) + { + File_error = 1; + break; + } + sub_chunk_size--; + if ((count & 0x80) == 0) + { + if (!Read_byte(file, &data)) // repeat data count times + { + File_error = 1; + break; + } + sub_chunk_size--; + while (count-- > 0 && x < frame_width) + Set_pixel(context, x++, y, data); + } + else + while (count++ != 0 && x < frame_width) // copy count bytes + { + if (!Read_byte(file, &data)) + { + File_error = 1; + break; + } + Set_pixel(context, x++, y, data); + sub_chunk_size--; + } + } + } + if (context->Type == CONTEXT_PREVIEW || context->Type == CONTEXT_PREVIEW_PALETTE) + { // load only 1st frame in preview + fclose(file); + return; + } + } + else if (sub_chunk_type == 0x0c) // delta image, RLE + { + word x, y, line_count; + + Set_loading_layer(context, ++current_frame); + if (header.type == 0xAF11) // FLI + Set_frame_duration(context, (frame_delay * 100) / 7); // 1/70th sec + else + Set_frame_duration(context, frame_delay); // msec + if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION) + { + // Copy the content of previous frame + memcpy( + Main.backups->Pages->Image[Main.current_layer].Pixels, + Main.backups->Pages->Image[Main.current_layer-1].Pixels, + Main.backups->Pages->Width*Main.backups->Pages->Height); + } + + Read_word_le(file, &y); + Read_word_le(file, &line_count); + sub_chunk_size -= 4; + while (sub_chunk_size > 0 && line_count > 0 && File_error == 0) + { + byte packet_count; + + x = 0; + if (!Read_byte(file, &packet_count)) + File_error = 1; + else + { + sub_chunk_size--; + while (packet_count-- > 0 && File_error == 0) + { + byte skip, count, data; + if (!(Read_byte(file, &skip) && Read_byte(file, &count))) + File_error = 1; + else + { + sub_chunk_size -= 2; + x += skip; + if (count & 0x80) + { + Read_byte(file, &data); + sub_chunk_size--; + while (count++ != 0) + Set_pixel(context, x++, y, data); + } + else + while (count-- > 0) + { + Read_byte(file, &data); + sub_chunk_size--; + Set_pixel(context, x++, y, data); + } + } + } + } + y++; + line_count--; + } + } + else if (sub_chunk_type == 0x07) // FLC delta image + { + word opcode, y, line_count; + + Set_loading_layer(context, ++current_frame); + if (header.type == 0xAF11) // FLI + Set_frame_duration(context, (frame_delay * 100) / 7); // 1/70th sec + else + Set_frame_duration(context, frame_delay); // msec + if (context->Type == CONTEXT_MAIN_IMAGE && Main.backups->Pages->Image_mode == IMAGE_MODE_ANIMATION) + { + // Copy the content of previous frame + memcpy( + Main.backups->Pages->Image[Main.current_layer].Pixels, + Main.backups->Pages->Image[Main.current_layer-1].Pixels, + Main.backups->Pages->Width*Main.backups->Pages->Height); + } + + y = 0; + Read_word_le(file, &line_count); + sub_chunk_size -= 2; + while (line_count > 0) + { + Read_word_le(file, &opcode); + sub_chunk_size -= 2; + if ((opcode & 0xc000) == 0x0000) // packet count + { + word x = 0; + while (opcode-- > 0) + { + byte skip, count, data1, data2; + if (!(Read_byte(file, &skip) && Read_byte(file, &count))) + File_error = 1; + else + { + sub_chunk_size -= 2; + x += skip; + if (count & 0x80) + { + Read_byte(file, &data1); + Read_byte(file, &data2); + sub_chunk_size -= 2; + while (count++ != 0) + { + Set_pixel(context, x++, y, data1); + Set_pixel(context, x++, y, data2); + } + } + else + while (count-- > 0) + { + Read_byte(file, &data1); + Set_pixel(context, x++, y, data1); + Read_byte(file, &data2); + Set_pixel(context, x++, y, data2); + sub_chunk_size -= 2; + } + } + } + y++; + line_count--; + } + else if ((opcode & 0xc000) == 0xc000) // line skip + { + y -= opcode; + } + else if ((opcode & 0xc000) == 0x8000) // last byte + { + Set_pixel(context, frame_width - 1, y, opcode & 0xff); + } + else + { + Warning("Unsupported opcode"); + File_error = 2; + break; + } + } + } + if (sub_chunk_size > 0) + { + fseek(file, sub_chunk_size, SEEK_CUR); + } + } + } + break; + default: // skip + Warning("Load_FLI(): unrecognized chunk"); + } + if (chunk_size > 0 && header.size != 12) + { + fseek(file, chunk_size, SEEK_CUR); + } + } + fclose(file); +}