/*---------------------------------------------------------------------------* Project: compress/uncompress library File: CXCompression.h Programmer: Makoto Takano Copyright 2006 Nintendo. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Log: CXCompression.c,v $ Revision 1.9 07/05/2006 13:06:42 takano_makoto revised indents Revision 1.8 07/05/2006 10:38:39 takano_makoto fixed a bug when a 1-byte over-access could occur when compression fails Revision 1.7 07/05/2006 09:31:05 takano_makoto separated HuffConvertData() into another function Added ASSERT. Revision 1.6 07/05/2006 08:12:20 takano_makoto minor comment revisions Revision 1.5 07/05/2006 05:54:29 takano_makoto fixed a bug for the scope of Huffman table initialization Revision 1.4 07/04/2006 13:18:55 takano_makoto changed LZ compressed to a high-speed version using the work buffer revised to void use of static variables in Huffman compression $NoKeywords: $ *---------------------------------------------------------------------------*/ #include #include #include #include #include "CXUtil.h" //=========================================================================== // LZ Encoding //=========================================================================== // Temporary information for LZ high-speed encoding typedef struct { u16 windowPos; // Initial position of the history window. u16 windowLen; // Length of the history window. s16 *LZOffsetTable; // Offset buffer of the history window. s16 *LZByteTable; // Pointer to the most recent character history s16 *LZEndTable; // Pointer to the oldest character history } LZCompressInfo; static void LZInitTable( LZCompressInfo * info, void *work ); static u8 SearchLZ ( LZCompressInfo * info, const u8 *nextp, u32 remainSize, u16 *offset ); static void SlideByte ( LZCompressInfo * info, const u8 *srcp ); static inline void LZSlide ( LZCompressInfo * info, const u8 *srcp, u32 n ); /*---------------------------------------------------------------------------* Name: CXCompressLZFast Description: Function that performs LZ77 compression Arguments: srcp Pointer to compression source data size Size of compression source data dstp Pointer to destination for compressed data Buffer must be larger than size of compression source data. work Temporary buffer for comprerssion A region for the size of CX_LZ_FAST_COMPRESS_WORK_SIZE is necessary. Returns: The data size after compression. If compressed data is larger than original data, compression is terminated and 0 gets returned. *---------------------------------------------------------------------------*/ u32 CXCompressLZ(const u8 *srcp, u32 size, u8 *dstp, void *work) { u32 LZDstCount; // Number of bytes of compressed data u8 LZCompFlags; // Flag series indicating whether there is a compression u8 *LZCompFlagsp; // Point to memory regions storing LZCompFlags u16 lastOffset; // Offset to matching data (the longest matching data at the time) u8 lastLength; // Length of matching data (the longest matching data at the time) u8 i; u32 dstMax; LZCompressInfo info; // Temporary LZ compression information ASSERT( ((u32)srcp & 0x1) == 0 ); ASSERT( work != NULL ); ASSERT( size > 4 ); *(u32 *)dstp = CXiConvertEndian_( size << 8 | CX_COMPRESSION_LZ ); // data header dstp += 4; LZDstCount = 4; dstMax = size; LZInitTable(&info, work); while ( size > 0 ) { LZCompFlags = 0; LZCompFlagsp = dstp++; // Designation for storing flag series LZDstCount++; // Since flag series is stored as 8-bit data, loop eight times for ( i = 0; i < 8; i++ ) { LZCompFlags <<= 1; // No meaning for the first time (i=0) if (size <= 0) { // When reached the end, quit after shifting flag to the end. continue; } if ((lastLength = SearchLZ( &info, srcp, size, &lastOffset)) != 0 ) { // Enabled Flag if compression is possible LZCompFlags |= 0x1; if (LZDstCount + 2 >= dstMax) // Quit on error if size becomes larger than source { return 0; } // Divide offset into upper 4 bits and lower 8 bits and store *dstp++ = (u8)((lastLength - 3) << 4 | (lastOffset - 1) >> 8); *dstp++ = (u8)((lastOffset - 1) & 0xff); LZDstCount += 2; LZSlide(&info, srcp, lastLength); srcp += lastLength; size -= lastLength; } else { // No compression if (LZDstCount + 1 >= dstMax) // Quit on error if size becomes larger than source { return 0; } LZSlide(&info, srcp, 1); *dstp++ = *srcp++; size--; LZDstCount++; } } // Complete eight loops *LZCompFlagsp = LZCompFlags; // Store flag series } // Align to 4-byte boundary // Does not include Data0 used for alignment as data size i = 0; while ( (LZDstCount + i) & 0x3 ) { *dstp++ = 0; i++; } return LZDstCount; } //-------------------------------------------------------- // Searches for the longest matching string from the slide window with LZ77 Compression. // Arguments: startp Pointer to starting position of data // nextp Pointer to data where search will start // remainSize Size of remaining data // offset Pointer to region storing matched offset // Return : TRUE if matching string is found. // FALSE if not found. //-------------------------------------------------------- static u8 SearchLZ( LZCompressInfo * info, const u8 *nextp, u32 remainSize, u16 *offset ) { const u8 *searchp; const u8 *headp, *searchHeadp; u16 maxOffset; u8 maxLength = 2; u8 tmpLength; s32 w_offset; s16 *const LZOffsetTable = info->LZOffsetTable; const u16 windowPos = info->windowPos; const u16 windowLen = info->windowLen; if (remainSize < 3) { return 0; } w_offset = info->LZByteTable[*nextp]; while (w_offset != -1) { if (w_offset < windowPos) { searchp = nextp - windowPos + w_offset; } else { searchp = nextp - windowLen - windowPos + w_offset; } /* This isn't needed, but it seems to make it a little faster.*/ if (*(searchp + 1) != *(nextp + 1) || *(searchp + 2) != *(nextp + 2)) { w_offset = LZOffsetTable[w_offset]; continue; } if (nextp - searchp < 2) { // VRAM is accessed in units of 2 bytes (since sometimes data is read from VRAM), // so the search must start 2 bytes prior to the search target. // // Since the offset is stored in 12 bits, the value is 4096 or less break; } tmpLength = 3; searchHeadp = searchp + 3; headp = nextp + 3; // Increments the compression size until the data ends or different data is encountered. while (((u32)(headp - nextp) < remainSize) && (*headp == *searchHeadp)) { headp++; searchHeadp++; tmpLength++; // Since the data length is stored in 4 bits, the value is 18 or less (3 is added) if (tmpLength == (0xF + 3)) { break; } } if (tmpLength > maxLength) { // Update the maximum-length offset maxLength = tmpLength; maxOffset = (u16)(nextp - searchp); if (maxLength == (0xF + 3)) { // This is the largest matching length, so end search. break; } } w_offset = LZOffsetTable[w_offset]; } if (maxLength < 3) { return 0; } *offset = maxOffset; return maxLength; } //-------------------------------------------------------- // Initializes the dictionary index //-------------------------------------------------------- static void LZInitTable(LZCompressInfo * info, void *work) { u16 i; info->LZOffsetTable = (s16*)work; info->LZByteTable = (s16*)( (u32)work + 4096 * sizeof(s16) ); info->LZEndTable = (s16*)( (u32)work + (4096 + 256) * sizeof(s16) ); for ( i = 0; i < 256; i++ ) { info->LZByteTable[i] = -1; info->LZEndTable [i] = -1; } info->windowPos = 0; info->windowLen = 0; } //-------------------------------------------------------- // Slides the dictionary 1 byte //-------------------------------------------------------- static void SlideByte(LZCompressInfo * info, const u8 *srcp) { s16 offset; u8 in_data = *srcp; u16 insert_offset; s16 *const LZByteTable = info->LZByteTable; s16 *const LZOffsetTable = info->LZOffsetTable; s16 *const LZEndTable = info->LZEndTable; const u16 windowPos = info->windowPos; const u16 windowLen = info->windowLen; if (windowLen == 4096) { u8 out_data = *(srcp - 4096); if ((LZByteTable[out_data] = LZOffsetTable[LZByteTable[out_data]]) == -1) { LZEndTable[out_data] = -1; } insert_offset = windowPos; } else { insert_offset = windowLen; } offset = LZEndTable[in_data]; if (offset == -1) { LZByteTable[in_data] = (s16)insert_offset; } else { LZOffsetTable[offset] = (s16)insert_offset; } LZEndTable[in_data] = (s16)insert_offset; LZOffsetTable[insert_offset] = -1; if (windowLen == 4096) { info->windowPos = (u16)((windowPos + 1) % 0x1000); } else { info->windowLen++; } } //-------------------------------------------------------- // Slides the dictionary n bytes //-------------------------------------------------------- static inline void LZSlide(LZCompressInfo * info, const u8 *srcp, u32 n) { u32 i; for (i = 0; i < n; i++) { SlideByte(info, srcp++); } } //=========================================================================== // Run Length Encoding //=========================================================================== /*---------------------------------------------------------------------------* Name: CXCompressRL Description: Function that performs runlength compression Arguments: srcp Pointer to compression source data size Size of compression source data dstp Pointer to destination for compressed data Buffer must be larger than size of compression source data. Returns: The data size after compression. If compressed data is larger than original data, compression is terminated and 0 gets returned. *---------------------------------------------------------------------------*/ u32 CXCompressRL(const u8 *srcp, u32 size, u8 *dstp) { u32 RLDstCount; // Number of bytes of compressed data u32 RLSrcCount; // Processed data volume of the compression target data (in bytes) u8 RLCompFlag; // 1 if performing run length encoding u8 runLength; // Run length u8 rawDataLength; // Length of data not run u32 i; const u8 *startp; // Point to the start of compression target data for each process loop ASSERT( srcp != NULL ); ASSERT( dstp != NULL ); ASSERT( size > 4 ); // Data header (The size after decompression) // Because the same output data as for Nitro is created, an endian strategy is implemented. *(u32 *)dstp = CXiConvertEndian_(size << 8 | CX_COMPRESSION_RL); // data header RLDstCount = 4; RLSrcCount = 0; rawDataLength = 0; RLCompFlag = 0; while (RLSrcCount < size) { startp = &srcp[RLSrcCount]; // Set compression target data for (i = 0; i < 128; i++) // Data volume that can be expressed in 7 bits is 0 to 127 { // Reach the end of the compression target data if (RLSrcCount + rawDataLength >= size) { rawDataLength = (u8)(size - RLSrcCount); break; } if (RLSrcCount + rawDataLength + 2 < size) { if (startp[i] == startp[i + 1] && startp[i] == startp[i + 2]) { RLCompFlag = 1; break; } } rawDataLength++; } // Store data that is not encoded // If the 8th bit of the data length storage byte is 0, the data series that is not encoded. // The data length is x - 1, so 0-127 becomes 1-128. if (rawDataLength) { if (RLDstCount + rawDataLength + 1 >= size) // Quit on error if size becomes larger than source { return 0; } dstp[RLDstCount++] = (u8)(rawDataLength - 1); // Store "data length - 1" (7 bits) for (i = 0; i < rawDataLength; i++) { dstp[RLDstCount++] = srcp[RLSrcCount++]; } rawDataLength = 0; } // Run Length Encoding if (RLCompFlag) { runLength = 3; for (i = 3; i < 128 + 2; i++) { // Reach the end of the data for compression if (RLSrcCount + runLength >= size) { runLength = (u8)(size - RLSrcCount); break; } // If run is interrupted if (srcp[RLSrcCount] != srcp[RLSrcCount + runLength]) { break; } // Run continues runLength++; } // If the 8th bit of the data length storage byte is 1, the data series that is encoded. if (RLDstCount + 2 >= size) // Quit on error if size becomes larger than source { return 0; } dstp[RLDstCount++] = (u8)(0x80 | (runLength - 3)); // Add 3, and store 3 to 130 dstp[RLDstCount++] = srcp[RLSrcCount]; RLSrcCount += runLength; RLCompFlag = 0; } } // Align to 4-byte boundary // Does not include Data0 used for alignment as data size i = 0; while ((RLDstCount + i) & 0x3) { dstp[RLDstCount + i] = 0; i++; } return RLDstCount; } //=========================================================================== // Huffman encoding //=========================================================================== #define HUFF_END_L 0x80 #define HUFF_END_R 0x40 typedef struct { u32 Freq; // Frequency of occurrence u16 No; // Data No. s16 PaNo; // Parent No. s16 ChNo[2]; // Child No (0: left side, 1: right side) u16 PaDepth; // Parent node depth u16 LeafDepth; // Depth to leaf u32 HuffCode; // Huffman code u8 Bit; // Node's bit data u8 _padding; u16 HWord; // For each intermediate node, the amount of memory needed to store the subtree that has the node as its root in HuffTree } HuffData; // Total of 24 bytes typedef struct { u8 leftOffsetNeed; // 1 if offset to left child node is required u8 rightOffsetNeed; // 1 if offset to right child node is required u16 leftNodeNo; // The left child node No. u16 rightNodeNo; // Right child node no. } HuffTreeCtrlData; // Total of 6 bytes // Structure of the Huffman work buffer typedef struct { HuffData *huffTable; // huffTable[ 512 ]; 12288B u8 *huffTree; // huffTree[ 256 * 2 ]; 512B HuffTreeCtrlData *huffTreeCtrl; // huffTreeCtrl[ 256 ]; 1536B u8 huffTreeTop; // u8 padding_[3]; // } HuffCompressionInfo; // Total is 14340B static void HuffInitTable( HuffCompressionInfo* info, void* work, u16 dataNum ); static void HuffCountData( HuffData* table, const u8 *srcp, u32 size, u8 bitSize ); static u16 HuffConstructTree( HuffData* table, u32 dataNum ); static u32 HuffConvertData( const HuffData *table, const u8* srcp, u8* dstp, u32 srcSize, u32 maxSize, u8 bitSize ); static void HuffAddParentDepthToTable( HuffData *table, u16 leftNo, u16 rightNo ); static void HuffAddCodeToTable ( HuffData *table, u16 nodeNo, u32 paHuffCode ); static u8 HuffAddCountHWordToTable ( HuffData *table, u16 nodeNo ); static void HuffMakeHuffTree ( HuffCompressionInfo* info, u16 rootNo ); static void HuffMakeSubsetHuffTree ( HuffCompressionInfo* info, u16 huffTreeNo, u8 rightNodeFlag ); static u8 HuffRemainingNodeCanSetOffset( HuffCompressionInfo* info, u8 costHWord ); static void HuffSetOneNodeOffset ( HuffCompressionInfo* info, u16 huffTreeNo, u8 rightNodeFlag ); /*---------------------------------------------------------------------------* Name: CXCompressHuffman Description: Function that performs Huffman compression Arguments: srcp Pointer to compression source data size Size of compression source data dstp Pointer to destination for compressed data Buffer must be larger than size of compression source data. huffBitSize The number of bits to encode work Work buffer for Huffman compression Returns: The data size after compression. If compressed data is larger than original data, compression is terminated and 0 gets returned. *---------------------------------------------------------------------------*/ u32 CXCompressHuffman( const u8 *srcp, u32 size, u8 *dstp, u8 huffBitSize, void *work ) { u32 huffDstCount; // Number of bytes of compressed data s32 i; u16 rootNo; // Binary tree's root No. u16 huffDataNum = (u16)(1 << huffBitSize); // 8->256, 4->16 HuffCompressionInfo info; ASSERT( srcp != NULL ); ASSERT( dstp != NULL ); ASSERT( huffBitSize == 4 || huffBitSize == 8 ); ASSERT( work != NULL ); ASSERT( ((u32)work & 0x3) == 0 ); ASSERT( size > 4 ); // Initialize table HuffInitTable( &info, work, huffDataNum ); // Check frequency of occurrence HuffCountData( info.huffTable, srcp, size, huffBitSize ); // Create tree table rootNo = HuffConstructTree( info.huffTable, huffDataNum ); // Create HuffTree HuffMakeHuffTree( &info, rootNo ); info.huffTree[0] = --info.huffTreeTop; // data header // Because the same compression data as for Nitro is created, an endian strategy is implemented. *(u32 *)dstp = CXiConvertEndian_(size << 8 | CX_COMPRESSION_HUFFMAN | huffBitSize); huffDstCount = 4; if ( huffDstCount + (info.huffTreeTop + 1) * 2 >= size ) // Quit on error if size becomes larger than source { return 0; } for ( i = 0; i < (u16)( (info.huffTreeTop + 1) * 2 ); i++ ) // Tree table { dstp[ huffDstCount++ ] = ((u8*)info.huffTree)[ i ]; } // Align to 4-byte boundary // Data0 used for alignment is included in data size (as per the decoder algorithm) while ( huffDstCount & 0x3 ) { if ( huffDstCount & 0x1 ) { info.huffTreeTop++; dstp[ 4 ]++; } dstp[ huffDstCount++ ] = 0; } // data conversion via the Huffman table { u32 convSize = HuffConvertData( info.huffTable, srcp, &dstp[ huffDstCount ], size, size - huffDstCount, huffBitSize ); if ( convSize == 0 ) { // compression fails because the compressed data is larger than the source return 0; } huffDstCount += convSize; } return huffDstCount; } /*---------------------------------------------------------------------------* Name: HuffInitTable Description: Initializes the Huffman table. Arguments: table size Returns: None. *---------------------------------------------------------------------------*/ static void HuffInitTable( HuffCompressionInfo* info, void* work, u16 dataNum ) { u32 i; info->huffTable = (HuffData*)(work); info->huffTree = (u8*)( (u32)work + sizeof(HuffData) * 512 ); info->huffTreeCtrl = (HuffTreeCtrlData*)( (u32)info->huffTree + sizeof(u8) * 512 ); info->huffTreeTop = 1; // initialize huffTable { HuffData* table = info->huffTable; const HuffData HUFF_TABLE_INIT_DATA = { 0, 0, 0, {-1, -1}, 0, 0, 0, 0, 0 }; for ( i = 0; i < dataNum * 2U; i++ ) { table[ i ] = HUFF_TABLE_INIT_DATA; table[ i ].No = (u16)i; } } // initialize huffTree and huffTreeCtrl { const HuffTreeCtrlData HUFF_TREE_CTRL_INIT_DATA = { 1, 1, 0, 0 }; u8* huffTree = info->huffTree; HuffTreeCtrlData* huffTreeCtrl = info->huffTreeCtrl; for ( i = 0; i < 256; i++ ) { huffTree[ i * 2 ] = 0; huffTree[ i * 2 + 1 ] = 0; huffTreeCtrl[ i ] = HUFF_TREE_CTRL_INIT_DATA; } } } /*---------------------------------------------------------------------------* Name: HuffCountData Description: Count of appearances. Arguments: table *srcp size bitSize Returns: None. *---------------------------------------------------------------------------*/ static void HuffCountData( HuffData* table, const u8 *srcp, u32 size, u8 bitSize ) { u32 i; u8 tmp; if ( bitSize == 8 ) { for ( i = 0; i < size; i++ ) { table[ srcp[ i ] ].Freq++; // 8-bit encoding } } else { for ( i = 0; i < size; i++ ) // 4-bit encoding { tmp = (u8)( (srcp[ i ] & 0xf0) >> 4 ); table[ tmp ].Freq++; // Store from upper 4 bits forward // Either is OK tmp = (u8)( srcp[ i ] & 0x0f ); table[ tmp ].Freq++; // The problem is the encoding } } } /*---------------------------------------------------------------------------* Name: HuffConstructTree Description: Constructs a Huffman tree. Arguments: *table dataNum Returns: None. *---------------------------------------------------------------------------*/ static u16 HuffConstructTree( HuffData *table, u32 dataNum ) { s32 i; s32 leftNo, rightNo; // Node number for creating binary tree u16 tableTop = (u16)dataNum; // When creating table, the table top No. u16 rootNo; // Binary tree's root No. u16 nodeNum; // Number of valid nodes leftNo = -1; rightNo = -1; while ( 1 ) { // Search for two subtree nodes with low Freq value. At least one should be found. // Search child node (left) for ( i = 0; i < tableTop; i++ ) { if ( ( table[i].Freq == 0 ) || ( table[i].PaNo != 0 ) ) { continue; } if ( leftNo < 0 ) { leftNo = i; } else if ( table[i].Freq < table[ leftNo ].Freq ) { leftNo = i; } } // Search child node (right) for ( i = 0; i < tableTop; i++ ) { if ( ( table[i].Freq == 0 ) || ( table[i].PaNo != 0 ) || ( i == leftNo ) ) { continue; } if ( rightNo < 0 ) { rightNo = i; } else if ( table[i].Freq < table[rightNo].Freq ) { rightNo = i; } } // If only one, then end table creation if ( rightNo < 0 ) { // When only one type of value exists, then create one node that takes the same value for both 0 and 1. if ( tableTop == dataNum ) { table[ tableTop ].Freq = table[ leftNo ].Freq; table[ tableTop ].ChNo[0] = (s16)leftNo; table[ tableTop ].ChNo[1] = (s16)leftNo; table[ tableTop ].LeafDepth = 1; table[ leftNo ].PaNo = (s16)tableTop; table[ leftNo ].Bit = 0; table[ leftNo ].PaDepth = 1; } else { tableTop--; } rootNo = tableTop; nodeNum = (u16)( (rootNo - dataNum + 1) * 2 + 1 ); break; } // Create vertex that combines left subtree and right subtree table[ tableTop ].Freq = table[ leftNo ].Freq + table[ rightNo ].Freq; table[ tableTop ].ChNo[0] = (s16)leftNo; table[ tableTop ].ChNo[1] = (s16)rightNo; if ( table[ leftNo ].LeafDepth > table[ rightNo ].LeafDepth ) { table[ tableTop ].LeafDepth = (u16)( table[ leftNo ].LeafDepth + 1 ); } else { table[ tableTop ].LeafDepth = (u16)( table[ rightNo ].LeafDepth + 1 ); } table[ leftNo ].PaNo = table[ rightNo ].PaNo = (s16)(tableTop); table[ leftNo ].Bit = 0; table[ rightNo ].Bit = 1; HuffAddParentDepthToTable( table, (u16)leftNo, (u16)rightNo ); tableTop++; leftNo = rightNo = -1; } // Generate Huffman code (In Table[i].HuffCode) HuffAddCodeToTable( table, rootNo, 0x00 ); // The Huffman code is the code with HuffCode's lower bit masked for PaDepth bits // For each intermediate node, calculate the amount of memory needed to store the subtree that has this node as the root in HuffTree. HuffAddCountHWordToTable( table, rootNo ); return rootNo; } //----------------------------------------------------------------------- // When creating binary tree and when combining subtrees, add 1 to the depth of every node in the subtree. //----------------------------------------------------------------------- static void HuffAddParentDepthToTable( HuffData *table, u16 leftNo, u16 rightNo ) { table[ leftNo ].PaDepth++; table[ rightNo ].PaDepth++; if ( table[ leftNo ].LeafDepth != 0 ) { HuffAddParentDepthToTable( table, (u16)table[ leftNo ].ChNo[0], (u16)table[ leftNo ].ChNo[1] ); } if ( table[ rightNo ].LeafDepth != 0 ) { HuffAddParentDepthToTable( table, (u16)table[ rightNo ].ChNo[0], (u16)table[ rightNo ].ChNo[1] ); } } //----------------------------------------------------------------------- // Create Huffman code //----------------------------------------------------------------------- static void HuffAddCodeToTable( HuffData* table, u16 nodeNo, u32 paHuffCode ) { table[ nodeNo ].HuffCode = (paHuffCode << 1) | table[ nodeNo ].Bit; if ( table[ nodeNo ].LeafDepth != 0 ) { HuffAddCodeToTable( table, (u16)table[ nodeNo ].ChNo[0], (u16)table[ nodeNo ].HuffCode ); HuffAddCodeToTable( table, (u16)table[ nodeNo ].ChNo[1], (u16)table[ nodeNo ].HuffCode ); } } //----------------------------------------------------------------------- // Data volume required of intermediate node to create HuffTree //----------------------------------------------------------------------- static u8 HuffAddCountHWordToTable( HuffData *table, u16 nodeNo) { u8 leftHWord, rightHWord; switch ( table[ nodeNo ].LeafDepth ) { case 0: return 0; case 1: leftHWord = rightHWord = 0; break; default: leftHWord = HuffAddCountHWordToTable( table, (u16)table[nodeNo].ChNo[0] ); rightHWord = HuffAddCountHWordToTable( table, (u16)table[nodeNo].ChNo[1] ); break; } table[ nodeNo ].HWord = (u16)( leftHWord + rightHWord + 1 ); return (u8)( leftHWord + rightHWord + 1 ); } //----------------------------------------------------------------------- // Creating table of Huffman Code //----------------------------------------------------------------------- static void HuffMakeHuffTree( HuffCompressionInfo* info, u16 rootNo ) { s16 i; s16 costHWord, tmpCostHWord; // Make subtree code table for most-costly node when subtree code table has not been created. s16 costOffsetNeed, tmpCostOffsetNeed; s16 costMaxKey, costMaxRightFlag; // Information for specifying the least costly node from HuffTree u8 offsetNeedNum; u8 tmpKey, tmpRightFlag; info->huffTreeTop = 1; costOffsetNeed = 0; info->huffTreeCtrl[0].leftOffsetNeed = 0; // Do not use (used as table size) info->huffTreeCtrl[0].rightNodeNo = rootNo; while ( 1 ) // Until return { // Calculate the number of nodes required for setting offset offsetNeedNum = 0; for ( i = 0; i < info->huffTreeTop; i++ ) { if ( info->huffTreeCtrl[ i ].leftOffsetNeed ) { offsetNeedNum++; } if ( info->huffTreeCtrl[ i ].rightOffsetNeed ) { offsetNeedNum++; } } // Search for node with greatest cost costHWord = -1; costMaxKey = -1; tmpKey = 0; tmpRightFlag = 0; for ( i = 0; i < info->huffTreeTop; i++ ) { tmpCostOffsetNeed = (u8)( info->huffTreeTop - i ); // Evaluate cost of left child node if (info->huffTreeCtrl[i].leftOffsetNeed) { tmpCostHWord = (s16)info->huffTable[ info->huffTreeCtrl[i].leftNodeNo ].HWord; if ( (tmpCostHWord + offsetNeedNum) > 64 ) { goto leftCostEvaluationEnd; } if ( ! HuffRemainingNodeCanSetOffset( info, (u8)tmpCostHWord ) ) { goto leftCostEvaluationEnd; } if ( tmpCostHWord > costHWord ) { costMaxKey = i; costMaxRightFlag = 0; } else if ( (tmpCostHWord == costHWord) && (tmpCostOffsetNeed > costOffsetNeed) ) { costMaxKey = i; costMaxRightFlag = 0; } } leftCostEvaluationEnd:{} // Evaluate cost of right child node if ( info->huffTreeCtrl[i].rightOffsetNeed) { tmpCostHWord = (s16)info->huffTable[info->huffTreeCtrl[i].rightNodeNo].HWord; if ( (tmpCostHWord + offsetNeedNum) > 64 ) { goto rightCostEvaluationEnd; } if ( ! HuffRemainingNodeCanSetOffset( info, (u8)tmpCostHWord ) ) { goto rightCostEvaluationEnd; } if ( tmpCostHWord > costHWord ) { costMaxKey = i; costMaxRightFlag = 1; } else if ( (tmpCostHWord == costHWord) && (tmpCostOffsetNeed > costOffsetNeed) ) { costMaxKey = i; costMaxRightFlag = 1; } } rightCostEvaluationEnd:{} } // Store entire subtree in HuffTree if ( costMaxKey >= 0 ) { HuffMakeSubsetHuffTree( info, (u8)costMaxKey, (u8)costMaxRightFlag); goto nextTreeMaking; } else { // Search for largest node with required offset for ( i = 0; i < info->huffTreeTop; i++ ) { u16 tmp = 0; tmpRightFlag = 0; if (info->huffTreeCtrl[i].leftOffsetNeed) { tmp = info->huffTable[ info->huffTreeCtrl[i].leftNodeNo ].HWord; } if (info->huffTreeCtrl[i].rightOffsetNeed) { if ( info->huffTable[info->huffTreeCtrl[i].rightNodeNo ].HWord > tmp ) { tmpRightFlag = 1; } } if ( (tmp != 0) || (tmpRightFlag) ) { HuffSetOneNodeOffset( info, (u8)i, tmpRightFlag); goto nextTreeMaking; } } } return; nextTreeMaking:{} } } //----------------------------------------------------------------------- // Store entire subtree in HuffTree //----------------------------------------------------------------------- static void HuffMakeSubsetHuffTree( HuffCompressionInfo* info, u16 huffTreeNo, u8 rightNodeFlag ) { u8 i; i = info->huffTreeTop; HuffSetOneNodeOffset( info, huffTreeNo, rightNodeFlag); if ( rightNodeFlag ) { info->huffTreeCtrl[ huffTreeNo ].rightOffsetNeed = 0; } else { info->huffTreeCtrl[ huffTreeNo ].leftOffsetNeed = 0; } while ( i < info->huffTreeTop ) { if ( info->huffTreeCtrl[ i ].leftOffsetNeed ) { HuffSetOneNodeOffset( info, i, 0); info->huffTreeCtrl[ i ].leftOffsetNeed = 0; } if ( info->huffTreeCtrl[ i ].rightOffsetNeed ) { HuffSetOneNodeOffset( info, i, 1); info->huffTreeCtrl[ i ].rightOffsetNeed = 0; } i++; } } //----------------------------------------------------------------------- // Check if there is any problems with huffTree construction if subtree of obtained data size is decompressed. //----------------------------------------------------------------------- static u8 HuffRemainingNodeCanSetOffset( HuffCompressionInfo* info, u8 costHWord ) { u8 i; s16 capacity; capacity = (s16)( 64 - costHWord ); // The offset value is larger for smaller values of i, so you should calculate without sorting, with i=0 -> huffTreeTop for ( i = 0; i < info->huffTreeTop; i++ ) { if ( info->huffTreeCtrl[i].leftOffsetNeed ) { if ( (info->huffTreeTop - i) <= capacity ) { capacity--; } else { return 0; } } if ( info->huffTreeCtrl[i].rightOffsetNeed ) { if ( (info->huffTreeTop - i) <= capacity ) { capacity--; } else { return 0; } } } return 1; } /*---------------------------------------------------------------------------* Name: HuffSetOneNodeOffset Description: Create Huffman code table for one node Arguments: *info a pointer to data for constructing a Huffman tree huffTreeNo rightNodeFlag flag for whether node is at right Returns: None. *---------------------------------------------------------------------------*/ static void HuffSetOneNodeOffset( HuffCompressionInfo* info, u16 huffTreeNo, u8 rightNodeFlag ) { u16 nodeNo; u8 offsetData = 0; HuffData* huffTable = info->huffTable; u8* huffTree = info->huffTree; HuffTreeCtrlData* huffTreeCtrl = info->huffTreeCtrl; u8 huffTreeTop = info->huffTreeTop; if (rightNodeFlag) { nodeNo = huffTreeCtrl[ huffTreeNo ].rightNodeNo; huffTreeCtrl[ huffTreeNo ].rightOffsetNeed = 0; } else { nodeNo = huffTreeCtrl[ huffTreeNo ].leftNodeNo; huffTreeCtrl [huffTreeNo ].leftOffsetNeed = 0; } // Left child node if ( huffTable[ huffTable[nodeNo].ChNo[0] ].LeafDepth == 0) { offsetData |= 0x80; huffTree[ huffTreeTop * 2 + 0 ] = (u8)huffTable[ nodeNo ].ChNo[0]; huffTreeCtrl[ huffTreeTop ].leftNodeNo = (u8)huffTable[ nodeNo ].ChNo[0]; huffTreeCtrl[ huffTreeTop ].leftOffsetNeed = 0; // Offset no longer required } else { huffTreeCtrl[ huffTreeTop ].leftNodeNo = (u16)huffTable[ nodeNo ].ChNo[0]; // Offset required } // Right child node if ( huffTable[ huffTable[ nodeNo ].ChNo[1] ].LeafDepth == 0 ) { offsetData |= 0x40; huffTree[ huffTreeTop * 2 + 1 ] = (u8)huffTable[nodeNo].ChNo[1]; huffTreeCtrl[ huffTreeTop ].rightNodeNo = (u8)huffTable[ nodeNo ].ChNo[1]; huffTreeCtrl[ huffTreeTop ].rightOffsetNeed = 0; // Offset no longer required } else { huffTreeCtrl[ huffTreeTop ].rightNodeNo = (u16)huffTable[ nodeNo ].ChNo[1]; // Offset required } offsetData |= (u8)( huffTreeTop - huffTreeNo - 1 ); huffTree[ huffTreeNo * 2 + rightNodeFlag ] = offsetData; info->huffTreeTop++; } /*---------------------------------------------------------------------------* Name: HuffConvertData Description: Data conversion based on Huffman table. Arguments: *table srcp dstp srcSize bitSize Returns: None. *---------------------------------------------------------------------------*/ static u32 HuffConvertData( const HuffData *table, const u8* srcp, u8* dstp, u32 srcSize, u32 maxSize, u8 bitSize ) { u32 i, ii, iii; u8 srcTmp; u32 bitStream = 0; u32 streamLength = 0; u32 dstSize = 0; // Huffman encoding for ( i = 0; i < srcSize; i++ ) { // Data compression if ( bitSize == 8 ) { // 8-bit Huffman bitStream = (bitStream << table[ srcp[i] ].PaDepth) | table[ srcp[i] ].HuffCode; streamLength += table[ srcp[i] ].PaDepth; if ( dstSize + (streamLength / 8) >= maxSize ) { // error if size becomes larger than source return 0; } for ( ii = 0; ii < streamLength / 8; ii++ ) { dstp[ dstSize++ ] = (u8)(bitStream >> (streamLength - (ii + 1) * 8)); } streamLength %= 8; } else // 4-bit Huffman { for ( ii = 0; ii < 2; ii++ ) { if ( ii ) { srcTmp = (u8)( srcp[ i ] >> 4 ); // first four bits come later } else { srcTmp = (u8)( srcp[ i ] & 0x0F ); // last four bits come first (because the decoder accesses in a little-endian method) } bitStream = (bitStream << table[ srcTmp ].PaDepth) | table[ srcTmp ].HuffCode; streamLength += table[ srcTmp ].PaDepth; if ( dstSize + (streamLength / 8) >= maxSize ) { // error if size becomes larger than source return 0; } for ( iii = 0; iii < streamLength / 8; iii++ ) { dstp[ dstSize++ ] = (u8)(bitStream >> (streamLength - (iii + 1) * 8)); } streamLength %= 8; } } } if ( streamLength != 0 ) { if ( dstSize + 1 >= maxSize ) { // error if size becomes larger than source return 0; } dstp[ dstSize++ ] = (u8)( bitStream << (8 - streamLength) ); } // Align to 4-byte boundary // Data0 for alignment is included in data size // This is special to Huffman encoding! Data is stored after the alignment-boundary data in order to convert to little-endian. while ( dstSize & 0x3 ) { dstp[ dstSize++ ] = 0; } // Little endian conversion for ( i = 0; i < dstSize / 4; i++ ) { u8 tmp; tmp = dstp[i * 4 + 0]; dstp[i * 4 + 0] = dstp[i * 4 + 3]; dstp[i * 4 + 3] = tmp; // Swap tmp = dstp[i * 4 + 1]; dstp[i * 4 + 1] = dstp[i * 4 + 2]; dstp[i * 4 + 2] = tmp; // Swap } return dstSize; }