From 6b090712e42dafd7c7b394a7e693ddd97c0c7e0f Mon Sep 17 00:00:00 2001 From: Nathan Date: Mon, 22 Jan 2024 08:14:39 -0500 Subject: [PATCH] Implement FLIC --- 3rdparty/flic/flic.h | 85 ++++-- CMakeLists.txt | 1 + LEGO1/flic.cpp | 370 ++++++++++++++++++++++++ LEGO1/library_flic.h | 32 +- LEGO1/omni/src/video/mxflcpresenter.cpp | 2 +- 5 files changed, 443 insertions(+), 47 deletions(-) create mode 100644 LEGO1/flic.cpp diff --git a/3rdparty/flic/flic.h b/3rdparty/flic/flic.h index c828b2f4..34617967 100644 --- a/3rdparty/flic/flic.h +++ b/3rdparty/flic/flic.h @@ -3,44 +3,69 @@ #include +enum FLI_CHUNK_TYPE { + FLI_CHUNK_COLOR256 = 4, // 256-level color palette info + FLI_CHUNK_SS2 = 7, // Word-oriented delta compression + FLI_CHUNK_COLOR64 = 11, // 64-level color palette info + FLI_CHUNK_LC = 12, // Byte-oriented delta compression + FLI_CHUNK_BLACK = 13, // Entire frame is color index 0 + FLI_CHUNK_BRUN = 15, // Byte run length compression + FLI_CHUNK_COPY = 16, // No compression + FLI_CHUNK_PSTAMP = 18, // Postage stamp sized image + FLI_CHUNK_FRAME = 0xf1fa // Frame +}; + +#pragma pack(push, 1) // A basic FLIC header structure from the "EGI" documentation. Source: https://www.compuphase.com/flic.htm#FLICHEADER // This also goes over the FLIC structures: https://github.com/thinkbeforecoding/nomemalloc.handson/blob/master/flic.txt 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 */ + DWORD size; /* Size of the chunk, including subchunks */ // 0x0; + WORD type; // 0x4; +} FLIC_CHUNK; + +typedef struct : FLIC_CHUNK { + WORD frames; /* Number of frames in first segment */ // 0x6 + short width; /* FLIC width in pixels */ // 0x8 + short height; /* FLIC height in pixels */ // 0xa + WORD depth; /* Bits per pixel (usually 8) */ // 0xc + WORD flags; /* Set to zero or to three */ // 0xe + DWORD speed; /* Delay between frames */ // 0x10 + WORD reserved1; /* Set to zero */ // 0x14 + DWORD created; /* Date of FLIC creation (FLC only) */ // 0x18 + DWORD creator; /* Serial number or compiler id (FLC only) */ // 0x1c + DWORD updated; /* Date of FLIC update (FLC only) */ // 0x20 + DWORD updater; /* Serial number (FLC only), see creator */ // 0x24 + WORD aspect_dx; /* Width of square rectangle (FLC only) */ // 0x28 + WORD aspect_dy; /* Height of square rectangle (FLC only) */ // 0x2a + WORD ext_flags; /* EGI: flags for specific EGI extensions */ // 02c + WORD keyframes; /* EGI: key-image frequency */ // 0x2e + WORD totalframes; /* EGI: total number of frames (segments) */ // 0x30 + DWORD req_memory; /* EGI: maximum chunk size (uncompressed) */ // 0x32 + WORD max_regions; /* EGI: max. number of regions in a CHK_REGION chunk */ // 0x36 + WORD transp_num; /* EGI: number of transparent levels */ // 0x38 + BYTE reserved2[24]; /* Set to zero */ // 3e + DWORD oframe1; /* Offset to frame 1 (FLC only) */ // 0x56 + DWORD oframe2; /* Offset to frame 2 (FLC only) */ // 0x5a + BYTE reserved3[40]; /* Set to zero */ // 0x5e } FLIC_HEADER; +#pragma pack(pop) + +typedef struct : FLIC_CHUNK { + short chunks; /* Number of subchunks */ // 0x6 + WORD delay; /* Delay in milliseconds */ // 0x8 + WORD reserved; /* Always zero */ // 0xa + WORD width; /* Frame width override (if non-zero) */ // 0xc + WORD height; /* Frame height override (if non-zero) */ // 0xe +} FLIC_FRAME; extern "C" { void DecodeFLCFrame( - LPBITMAPINFOHEADER bitmapHeader, - void* pixelData, - FLIC_HEADER* flcHeader, - void* flcFrame, - unsigned char* decodedColorMap + LPBITMAPINFOHEADER p_bitmapHeader, + byte* p_pixelData, + FLIC_HEADER* p_flcHeader, + FLIC_FRAME* p_flcFrame, + unsigned char* p_decodedColorMap ); } diff --git a/CMakeLists.txt b/CMakeLists.txt index 04ca97a9..30f43618 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -252,6 +252,7 @@ add_library(lego1 SHARED LEGO1/viewmanager/viewlodlist.cpp LEGO1/viewmanager/viewmanager.cpp LEGO1/viewmanager/viewroi.cpp + LEGO1/flic.cpp ) if (MINGW) diff --git a/LEGO1/flic.cpp b/LEGO1/flic.cpp new file mode 100644 index 00000000..fecbfb59 --- /dev/null +++ b/LEGO1/flic.cpp @@ -0,0 +1,370 @@ +#include "flic.h" + +// Private forward declarations +void WritePixel(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, short p_column, short p_row, byte p_pixel); +void WritePixels( + LPBITMAPINFOHEADER p_bitmapHeader, + byte* p_pixelData, + short p_column, + short p_row, + byte* p_data, + short p_count +); +int ClampLine(LPBITMAPINFOHEADER p_bitmapHeader, short& p_column, short& p_row, short& p_count); +void WritePixelRun( + LPBITMAPINFOHEADER p_bitmapHeader, + byte* p_pixelData, + short p_column, + short p_row, + byte p_pixel, + short p_count +); +void WritePixelPairs( + LPBITMAPINFOHEADER p_bitmapHeader, + byte* p_pixelData, + short p_column, + short p_row, + WORD p_pixel, + short p_count +); +short DecodeChunks( + LPBITMAPINFOHEADER p_bitmapHeader, + byte* p_pixelData, + FLIC_HEADER* p_flcHeader, + FLIC_FRAME* p_flcFrame, + byte* p_flcSubchunks, + unsigned char* p_decodedColorMap +); +void DecodeColors256(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_data); +void DecodeColorPackets(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_data); +void DecodeColorPacket(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_data, short p_index, WORD p_count); +void DecodeColors64(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_data); +void DecodeBrun(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, byte* p_data, FLIC_HEADER* p_flcHeader); +void DecodeLC(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, byte* p_data, FLIC_HEADER* p_flcHeader); +void DecodeSS2(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, byte* p_data, FLIC_HEADER* p_flcHeader); +void DecodeBlack(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, byte* p_data, FLIC_HEADER* p_flcHeader); +void DecodeCopy(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, byte* p_data, FLIC_HEADER* p_flcHeader); + +// FUNCTION: LEGO1 0x100bd530 +void WritePixel(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, short p_column, short p_row, byte p_pixel) +{ + if (p_column >= 0 && p_row >= 0 && p_column < p_bitmapHeader->biWidth && p_row < p_bitmapHeader->biHeight) { + *(((p_bitmapHeader->biWidth + 3) & -4) * p_row + p_column + p_pixelData) = p_pixel; + } +} + +// FUNCTION: LEGO1 0x100bd580 +void WritePixels( + LPBITMAPINFOHEADER p_bitmapHeader, + byte* p_pixelData, + short p_column, + short p_row, + byte* p_data, + short p_count +) +{ + short col = p_column; + if (ClampLine(p_bitmapHeader, p_column, p_row, p_count)) { + short offset = p_column - col; + byte* pixels = offset ? p_data + offset : p_data; + memcpy(((p_bitmapHeader->biWidth + 3) & -4) * p_row + p_column + p_pixelData, pixels, p_count); + } +} + +// FUNCTION: LEGO1 0x100bd600 +int ClampLine(LPBITMAPINFOHEADER p_bitmapHeader, short& p_column, short& p_row, short& p_count) +{ + short lp_count; + short end = p_column + p_count; + if (p_row >= 0 && p_row < p_bitmapHeader->biHeight && end >= 0 && p_column < p_bitmapHeader->biWidth) { + if (p_column < 0) { + lp_count = end; + p_count = end; + p_column = 0; + } + if (end > p_bitmapHeader->biWidth) { + lp_count -= end; + lp_count += p_bitmapHeader->biWidth; + p_count = lp_count; + } + return lp_count >= 0; + } + return 0; +} + +// FUNCTION: LEGO1 0x100bd680 +void WritePixelRun( + LPBITMAPINFOHEADER p_bitmapHeader, + byte* p_pixelData, + short p_column, + short p_row, + byte p_pixel, + short p_count +) +{ + short col = p_column; + if (ClampLine(p_bitmapHeader, p_column, p_row, p_count)) { + byte* dst = ((p_bitmapHeader->biWidth + 3) & -4) * p_row + p_column + p_pixelData; + while (--p_count >= 0) { + *(dst++) = p_pixel; + } + } +} + +// FUNCTION: LEGO1 0x100bd6e0 +void WritePixelPairs( + LPBITMAPINFOHEADER p_bitmapHeader, + byte* p_pixelData, + short p_column, + short p_row, + WORD p_pixel, + short p_count +) +{ + p_count <<= 1; + if (ClampLine(p_bitmapHeader, p_column, p_row, p_count)) { + short odd = p_count & 1; + p_count >>= 1; + WORD* dst = (WORD*) (((p_bitmapHeader->biWidth + 3) & -4) * p_row + p_column + p_pixelData); + while (--p_count >= 0) { + *(dst++) = p_pixel; + } + if (odd) + *((byte*) dst) = p_pixel; + } +} + +// FUNCTION: LEGO1 0x100bd760 +short DecodeChunks( + LPBITMAPINFOHEADER p_bitmapHeader, + byte* p_pixelData, + FLIC_HEADER* p_flcHeader, + FLIC_FRAME* p_flcFrame, + byte* p_flcSubchunks, + unsigned char* p_decodedColorMap +) +{ + *p_decodedColorMap = 0; + for (short subchunk = 0; subchunk < p_flcFrame->chunks; subchunk++) { + FLIC_CHUNK* chunk = (FLIC_CHUNK*) p_flcSubchunks; + p_flcSubchunks += chunk->size; + switch (chunk->type) { + case FLI_CHUNK_COLOR256: + DecodeColors256(p_bitmapHeader, (byte*) (chunk + 1)); + *p_decodedColorMap = 1; + break; + case FLI_CHUNK_SS2: + DecodeSS2(p_bitmapHeader, p_pixelData, (byte*) (chunk + 1), p_flcHeader); + break; + case FLI_CHUNK_COLOR64: + DecodeColors64(p_bitmapHeader, (byte*) (chunk + 1)); + *p_decodedColorMap = 1; + break; + case FLI_CHUNK_LC: + DecodeLC(p_bitmapHeader, p_pixelData, (byte*) (chunk + 1), p_flcHeader); + break; + case FLI_CHUNK_BLACK: + DecodeBlack(p_bitmapHeader, p_pixelData, (byte*) (chunk + 1), p_flcHeader); + break; + case FLI_CHUNK_BRUN: + DecodeBrun(p_bitmapHeader, p_pixelData, (byte*) (chunk + 1), p_flcHeader); + break; + case FLI_CHUNK_COPY: + DecodeCopy(p_bitmapHeader, p_pixelData, (byte*) (chunk + 1), p_flcHeader); + break; + } + } + return 0; +} + +// FUNCTION: LEGO1 0x100bd880 +void DecodeColors256(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_data) +{ + DecodeColorPackets(p_bitmapHeader, p_data); +} + +// FUNCTION: LEGO1 0x100bd8a0 +void DecodeColorPackets(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_data) +{ + WORD colorIndex = 0; + byte* colors = p_data + 2; + for (short packet = *((short*) p_data) - 1; packet >= 0; packet--) { + colorIndex += colors[0]; + short colorCount = colors[1]; + colors++; + colors++; + if (!colorCount) + colorCount = 256; + DecodeColorPacket(p_bitmapHeader, colors, colorIndex, colorCount); + colorIndex += colorCount; + colors += (colorCount * 3); + } +} + +// FUNCTION: LEGO1 0x100bd8f0 +void DecodeColorPacket(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_data, short index, WORD p_count) +{ + byte* palette = (byte*) (p_bitmapHeader) + p_bitmapHeader->biSize + index * 4; + while (p_count-- > 0) { + palette[2] = p_data[0]; + palette[1] = p_data[1]; + palette[0] = p_data[2]; + palette += 4; + p_data += 3; + } +} + +// FUNCTION: LEGO1 0x100bd940 +void DecodeColors64(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_data) +{ + DecodeColorPackets(p_bitmapHeader, p_data); +} + +// FUNCTION: LEGO1 0x100bd960 +void DecodeBrun(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, byte* p_data, FLIC_HEADER* p_flcHeader) +{ + byte* offset = ((p_bitmapHeader->biWidth + 3) & -4) * (p_flcHeader->height - 1) + p_pixelData; + short line = p_flcHeader->height; + while (--line >= 0) { + p_data++; + for (short p_pixel = 0; p_pixel < p_flcHeader->width;) { + char p_count = *(p_data++); + if (p_count >= 0) { + for (short i = p_count; i > 0; i--) { + *(offset++) = *p_data; + } + p_data++; + } + else { + for (short i = -p_count; i > 0; i--) { + *(offset++) = *(p_data++); + } + } + p_pixel += p_count; + } + offset -= (((p_bitmapHeader->biWidth + 3) & -4) + p_flcHeader->width); + } +} + +// FUNCTION: LEGO1 0x100bda10 +void DecodeLC(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, byte* p_data, FLIC_HEADER* p_flcHeader) +{ + short row = p_flcHeader->height - *((short*) p_data) - 1; + byte* pixels = p_data + 4; + short numLines = *((short*) (p_data + 2)); + while (--numLines >= 0) { + WORD column = 0; + byte i = *(pixels++); + while (i) { + column += *(pixels++); + char type = *(pixels++); + short p_count; + if (type < 0) { + p_count = -type; + WritePixelRun(p_bitmapHeader, p_pixelData, column, row, *(pixels++), p_count); + } + else { + p_count = type; + WritePixels(p_bitmapHeader, p_pixelData, column, row, pixels, p_count); + pixels += p_count; + } + column += p_count; + i--; + } + row--; + } +} + +// FUNCTION: LEGO1 0x100bdac0 +void DecodeSS2(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, byte* p_data, FLIC_HEADER* p_flcHeader) +{ + short width = p_flcHeader->width - 1; + short row = p_flcHeader->height - 1; + short lines = *p_data; + byte* data = p_data + 2; + do { + short token; + while (true) { + token = *((WORD*) data); + data += 2; + if (token < 0) { + if (token & 0x4000) { + row += token; + continue; + } + } + break; + } + if (token < 0) { + WritePixel(p_bitmapHeader, p_pixelData, width, row, token); + token = *((WORD*) data); + data += 2; + if (!token) { + row--; + continue; + } + } + short column = 0; + do { + column += *(data++); + short type = ((short) *(data++)); + type += type; + if (type >= 0) { + WritePixels(p_bitmapHeader, p_pixelData, column, row, data, type); + column += type; + data += type; + } + else { + type = -type; + short p_pixel = *((WORD*) data); + data += 2; + WritePixelPairs(p_bitmapHeader, p_pixelData, column, row, p_pixel, type); + column += type; + } + } while (--token); + + } while (--lines > 0); +} + +// FUNCTION: LEGO1 0x100bdc00 +void DecodeBlack(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, byte* p_data, FLIC_HEADER* p_flcHeader) +{ + short width = p_flcHeader->width; + byte pixel[2]; + pixel[1] = 0; + pixel[0] = 0; + short line = p_flcHeader->height; + while (--line >= 0) { + short p_count = width / 2; + short odd = width & 1; + WritePixelPairs(p_bitmapHeader, p_pixelData, 0, line, *((WORD*) pixel), p_count); + if (odd) { + WritePixel(p_bitmapHeader, p_pixelData, width - 1, line, 0); + } + } +} + +// FUNCTION: LEGO1 0x100bdc90 +void DecodeCopy(LPBITMAPINFOHEADER p_bitmapHeader, byte* p_pixelData, byte* p_data, FLIC_HEADER* p_flcHeader) +{ + short line = p_flcHeader->height; + int width = p_flcHeader->width; + while (--line >= 0) { + WritePixels(p_bitmapHeader, p_pixelData, 0, line, p_data, p_flcHeader->width); + p_data += width; + } +} + +// FUNCTION: LEGO1 0x100bdce0 +void DecodeFLCFrame( + LPBITMAPINFOHEADER p_bitmapHeader, + byte* p_pixelData, + FLIC_HEADER* p_flcHeader, + FLIC_FRAME* p_flcFrame, + unsigned char* p_decodedColorMap +) +{ + if (p_flcFrame->type == FLI_CHUNK_FRAME) { + DecodeChunks(p_bitmapHeader, p_pixelData, p_flcHeader, p_flcFrame, (byte*) (p_flcFrame + 1), p_decodedColorMap); + } +} diff --git a/LEGO1/library_flic.h b/LEGO1/library_flic.h index ece2d6f9..d9dd4ff4 100644 --- a/LEGO1/library_flic.h +++ b/LEGO1/library_flic.h @@ -1,51 +1,51 @@ #ifdef 0 -// LIBRARY: LEGO1 0x100bd530 +// XLIBRARY: LEGO1 0x100bd530 // _FUN_100bd530 -// LIBRARY: LEGO1 0x100bd580 +// XLIBRARY: LEGO1 0x100bd580 // _FUN_100bd580 -// LIBRARY: LEGO1 0x100bd600 +// XLIBRARY: LEGO1 0x100bd600 // _FUN_100bd600 -// LIBRARY: LEGO1 0x100bd680 +// XLIBRARY: LEGO1 0x100bd680 // _FUN_100bd680 -// LIBRARY: LEGO1 0x100bd6e0 +// XLIBRARY: LEGO1 0x100bd6e0 // _FUN_100bd6e0 -// LIBRARY: LEGO1 0x100bd760 +// XLIBRARY: LEGO1 0x100bd760 // _FUN_100bd760 -// LIBRARY: LEGO1 0x100bd880 +// XLIBRARY: LEGO1 0x100bd880 // _FUN_100bd880 -// LIBRARY: LEGO1 0x100bd8a0 +// XLIBRARY: LEGO1 0x100bd8a0 // _FUN_100bd8a0 -// LIBRARY: LEGO1 0x100bd8f0 +// XLIBRARY: LEGO1 0x100bd8f0 // _FUN_100bd8f0 -// LIBRARY: LEGO1 0x100bd940 +// XLIBRARY: LEGO1 0x100bd940 // _FUN_100bd940 -// LIBRARY: LEGO1 0x100bd960 +// XLIBRARY: LEGO1 0x100bd960 // _FUN_100bd960 -// LIBRARY: LEGO1 0x100bda10 +// XLIBRARY: LEGO1 0x100bda10 // _FUN_100bda10 -// LIBRARY: LEGO1 0x100bdac0 +// XLIBRARY: LEGO1 0x100bdac0 // _FUN_100bdac0 -// LIBRARY: LEGO1 0x100bdc00 +// XLIBRARY: LEGO1 0x100bdc00 // _FUN_100bdc00 -// LIBRARY: LEGO1 0x100bdc90 +// XLIBRARY: LEGO1 0x100bdc90 // _FUN_100bdc90 -// LIBRARY: LEGO1 0x100bdce0 +// XLIBRARY: LEGO1 0x100bdce0 // _DecodeFLCFrame #endif diff --git a/LEGO1/omni/src/video/mxflcpresenter.cpp b/LEGO1/omni/src/video/mxflcpresenter.cpp index 2fc22265..2e04885d 100644 --- a/LEGO1/omni/src/video/mxflcpresenter.cpp +++ b/LEGO1/omni/src/video/mxflcpresenter.cpp @@ -58,7 +58,7 @@ void MxFlcPresenter::LoadFrame(MxStreamChunk* p_chunk) &m_bitmap->GetBitmapInfo()->m_bmiHeader, m_bitmap->GetBitmapData(), m_flcHeader, - data, + (FLIC_FRAME*) data, &decodedColorMap );