More documentation I don't want to lose and some code cleanup as I understand LZH better...er?

This commit is contained in:
Lani Aung
2024-09-22 16:22:04 -06:00
parent 3425a28bf7
commit d39e73a6fa
16 changed files with 5852 additions and 0 deletions
@@ -0,0 +1,917 @@
<!DOCTYPE html>
<!-- https://www.carlbelle.com/Articles/Article/2016/Apr/6/ACloserLookAtHuffmanEncodingUsingCSharp/95 -->
<html>
<head>
<title>A Closer Look at Huffman Encoding Using C# | Articles - carlbelle.com</title>
<meta content="Articles about A Closer Look at Huffman Encoding Using C#." name="description"/>
<meta content="" name="keywords"/>
<meta charset="utf-8"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<link href="/Css/Version1.css" media="screen" rel="stylesheet" type="text/css"/>
<link href="/favicon.ico" rel="shortcut icon" type="image/x-icon">
</head>
<body>
<div class="LayoutContainer">
<div class="Header">
<div class="HeaderImage"><a href="/" title="Home - carlbelle.com">CARL.BELLE</a></div>
<div class="HeaderLinks">
<p>
<a class="NoUnderline" href="https://www.github.com/carlyman77" target="_blank" title="GitHub"><span
class="Symbol">&#XE237;</span></a>
<a class="NoUnderline" href="https://www.linkedin.com/in/carlyman77" target="_blank"
title="LinkedIn"><span class="Symbol">&#XE252;</span></a>
<a class="NoUnderline" href="https://www.twitter.com/carlbelle" target="_blank" title="Twitter"><span
class="Symbol">&#XE286;</span></a>
<a class="NoUnderline" href="https://instagram.com/carlyman77" target="_blank" title="Instagram"><span
class="Symbol">&#XE300;</span></a>
<a class="NoUnderline" href="https://open.spotify.com/artist/2kEspNx7mSo6Oe8BWpkGpj" target="_blank"
title="Spotify"><span class="Symbol">&#XE279;</span></a>
<a class="NoUnderline" href="https://www.youtube.com/channel/UCEen0eKme7kKca_yDS7gDzw" target="_blank"
title="SoundCloud"><span class="Symbol">&#XE299;</span></a>
<a class="NoUnderline" href="https://soundcloud.com/carl-belle" target="_blank" title="SoundCloud"><span
class="Symbol">&#XE278;</span></a>
</p>
<p>
<a href="/" title="Home">Home</a> &raquo;
<a href="/Articles/Home" title="Articles">Articles</a> &raquo;
<a href="/QSharp" title="QSharp">QSharp</a> &raquo;
<a href="/Home/Contact" title="Contact">Contact</a>
</p>
</div>
</div>
<div class="HorizontalSolidLine"></div>
<div class="HorizontalSpacer"></div>
<div class="Layout">
<h1>A Closer Look at Huffman Encoding Using C#</h1>
<p>06 Apr 2016
<p>
<p>
<a href="/Articles/Category/Algorithms/18" title="Algorithms">Algorithms</a>, <a
href="/Articles/Category/CSharp/11" title="C#">C#</a></p>
<br/>
<p>On this page:</p>
<ul>
<li><a href="#HowItWorks">How it Works</a></li>
<li><a href="#ConstructingTheBinaryTree">Constructing the Binary Tree</a></li>
<li><a href="#EncodingTheData">Encoding the Data</a></li>
<li><a href="#UnpackingTheData">Unpacking the Data</a></li>
<li><a href="#FinalThoughts">Final Thoughts</a></li>
<li><a href="#TheCode">The Code</a></li>
</ul>
<p>Huffman encoding is a compression technique developed by David Huffman and published in his 1952 paper 'A
Method for the Construction of Minimum-Redundancy Codes'. The technique centres on encoding characters as a
list of code words, which are then used to generate a coded version of the original data. Huffman encoding
can be very efficient, and should generally yield compression as low as 20% of the original data size.</p>
<a name="#HowItWorks"></a>
<p><strong>How it Works</strong></p>
<p>The input data is scanned, and a list of characters and their corresponding weight (the number of instances
the character appears in the data). The weights of each character are used to compute the length of the code
words that represent that character: larger weights should yield smaller code words, a key aspect of
compressing the data. Characters are then arranged into a binary tree, where the resulting layout is used to
determine the encoding table. The last step is using the encoding table to create the new file.</p>
<a name="#ConstructingTheBinaryTree"></a>
<p><strong>Constructing the Binary Tree</strong></p>
<p>Consider the following text:</p>
<pre>
abccccddddddefghijjjjj
</pre>
<p>Represented as a list of characters with corresponding weights:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
Index Char Weight
0 a 1
1 b 1
2 c 4
3 d 6
4 e 1
5 f 1
6 g 1
7 h 1
8 i 1
9 j 5
</pre>
<!-- END AUTO FORMAT -->
<p>This list is now turned into a binary tree. A binary tree is a tree where each node may have a maximum of two
child nodes. The left child node is consider the 0 child node, whereas the right child node is considered
the 1 child node. The first two nodes are removed from the list, and a new parent node is constructed. This
parent node's weight is the combined weight of both children. In our example, after the first iteration our
list would look like this:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
Index Char Weight
00 null null <span class="Comment">// used to be a</span>
01 null null <span class="Comment">// used to be b</span>
02 e 1
03 f 1
04 g 1
05 h 1
06 i 1
07 c 4
08 j 5
09 d 6
10 a + b 2 <span class="Comment">// new parent node</span>
</pre>
<!-- END AUTO FORMAT -->
<p>The next two lowest weight nodes are added to a parent:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
Index Char Weight
00 null null <span class="Comment">// used to be a</span>
01 null null <span class="Comment">// used to be b</span>
02 null null <span class="Comment">// used to be e</span>
03 null null <span class="Comment">// used to be f</span>
04 g 1
05 h 1
06 i 1
07 c 4
08 j 5
09 d 6
10 a + b 2 <span class="Comment">// new parent node</span>
11 e + f 2 <span class="Comment">// new parent node</span>
</pre>
<!-- END AUTO FORMAT -->
<p>And so on. At the end of this round of processing, the list looks like this:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
Index Char Weight
00 null null <span class="Comment">// used to be a</span>
01 null null <span class="Comment">// used to be b</span>
02 null null <span class="Comment">// used to be e</span>
03 null null <span class="Comment">// used to be f</span>
04 null null <span class="Comment">// used to be g</span>
05 null null <span class="Comment">// used to be h</span>
06 null null <span class="Comment">// used to be i</span>
07 null null <span class="Comment">// used to be c</span>
08 null null <span class="Comment">// used to be j</span>
09 null null <span class="Comment">// used to be d</span>
10 a + b 2 <span class="Comment">// new parent node</span>
11 e + f 2 <span class="Comment">// new parent node</span>
12 g + h 2 <span class="Comment">// new parent node</span>
13 i + c 5 <span class="Comment">// new parent node</span>
14 j + d 11 <span class="Comment">// new parent node</span>
</pre>
<!-- END AUTO FORMAT -->
<p>Null nodes are removed from the list:</p>
<pre>
Index Char Weight
0 a + b 2
1 e + f 2
2 g + h 2
3 i + c 5
4 j + d 11
</pre>
<p>And the next round of processing begins, always taking the smallest weighted nodes and combining them under a
new parent. At the end of the next round of processing, the list looks like this:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
Index Char Weight
0 null null <span class="Comment">// a + b</span>
1 null null <span class="Comment">// e + f</span>
2 null null <span class="Comment">// g + h</span>
3 null null <span class="Comment">// i + c</span>
4 j + d 11
5 a + b (2), e + f (2) 4 <span class="Comment">// new parent node</span>
6 g + h (2), i + c (5) 7 <span class="Comment">// new parent node</span>
</pre>
<!-- END AUTO FORMAT -->
<p>Null nodes are removed from the list:</p>
<pre>
Index Char Weight
0 a + b (2), e + f (2) 4
1 g + h (2), i + c (5) 7
2 j + d 11
</pre>
<p>At the end of the next round of processing:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
Index Char Weight
0 null 4 <span class="Comment">// a + b (2), e + f (2)</span>
1 null 7 <span class="Comment">// g + h (2), i + c (5) </span>
2 j + d 11 <span class="Comment">// new parent node</span>
3 (a + b, e + f) - (4), (g + h, i + c) - (7) 11 <span class="Comment">// new parent node</span>
</pre>
<!-- END AUTO FORMAT -->
<p>After removing null nodes from the list, we are ready for the final round of processing:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
Index Char Weight
0 j + d 11 <span class="Comment">// new parent node</span>
1 (a + b, e + f) - (4), (g + h, i + c) - (7) 11 <span class="Comment">// new parent node</span>
</pre>
<!-- END AUTO FORMAT -->
<p>And finally, after all processing, there is only one node left:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
Index Char Weight
0 (j + d) - 11, (a + b, e + f, g + h, i + c) - (11) 22 <span class="Comment">// new parent node</span>
</pre>
<!-- END AUTO FORMAT -->
<p>This final node forms the root of the tree, and is used to generate a list of binary words that represent
each character. Starting at the root of the tree, following a left child node inserts a 0 into the binary
word, following a right child node inserts a 1 into the binary word. Using this method, the following list
of binary words is created:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
Char Weight BinaryWord Int32Word
j 5 00 0 <span class="Comment">// This column is for sorting purposes only</span>
d 6 01 1
a 1 1000 8
b 1 1001 9
e 1 1010 10
f 1 1011 11
g 1 1100 12
h 1 1101 13
I 1 1110 14
c 4 1111 15
</pre>
<!-- END AUTO FORMAT -->
<p>Note that nodes without a character value are not present in this list, but are able to be inferred from the
list sorting operations. For the moment, I'll leave you to construct the tree using the information
above.</p>
<a name="#EncodingTheData"></a>
<p><strong>Encoding the Data</strong></p>
<p>The next step is to encode the original data using the binary words created from the previous steps. Moving
forward through the data one byte at a time, each character is used to perform a lookup for its
corresponding binary word. This binary word is added into a binary word buffer. When the binary word buffer
reaches sufficient length, 8 values are read off and removed to construct a new byte. Consider the
following:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
Index Char BinaryWord ByteBuffer
00 a 1000 <span class="Comment">// Only 4 bits here, not enough for a new byte</span>
01 b 1001 <span class="Comment">// Combined with the previous entry, there are enough bits here for a new byte: 10001001 => 137, remove 8 entries from the buffer</span>
02 c 1111 <span class="Comment">// Start a new byte using these 4 bits</span>
03 c 1111 <span class="Comment">// 11111111 => 255</span>
04 c 1111 <span class="Comment">// Start a new byte</span>
05 c 1111 <span class="Comment">// 11111111 => 255</span>
06 d 01 <span class="Comment">// Start a new byte</span>
07 d 01 <span class="Comment">// 0101 - the buffer is only half full</span>
08 d 01 <span class="Comment">// 010101</span>
09 d 01 <span
class="Comment">// 01010101 => 85 - note that this one byte actually represents 4 chars!</span>
10 d 01 <span class="Comment">// Start a new byte</span>
11 d 01 <span class="Comment">// 0101</span>
12 e 1010 <span class="Comment">// 01011010 => 90 - this one byte represents 3 chars</span>
13 f 1011 <span class="Comment">// Start a new byte</span>
14 g 1100 <span class="Comment">// 10111100 => 188</span>
15 h 1101 <span class="Comment">// New byte</span>
16 i 1110 <span class="Comment">// 11011110 => 222</span>
17 j 00 <span class="Comment">// New byte</span>
18 j 00 <span class="Comment">// 0000</span>
19 j 00 <span class="Comment">// 000000</span>
20 j 00 <span class="Comment">// 00000000 => 0</span>
21 j 00 <span class="Comment">// Start a new byte</span>
</pre>
<!-- END AUTO FORMAT -->
<p>With the processing of the final character, the byte buffer only contains 2 values, which is not enough to
construct a byte, however as the binary word is 00 it is simply converted to 0. Therefore, the final byte to
be constructed is 0. Processing of these 22 characters has yielded a list containing only 9 bytes, about 41%
of the original data size.</p>
<a name="#UnpackingTheData"></a>
<p><strong>Unpacking the Data</strong></p>
<p>Decompression (in this example at least) is relatively straight forward. The byte list is read, and each byte
converted into a binary word, which is added into a buffer. If the buffer is less than 8 values, then it is
'fixed' (more on that later). Reading each bit from the binary word indicates which path to take through the
binary tree (constructed above): starting at the root node, follow the left child for a 0 value, and follow
the right for a 1 value. The entire decompression process for the above data:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
Index Byte Characters
0 137 =&gt; 10001001 a, b
1 255 =&gt; 11111111 c, c
2 255 =&gt; 11111111 c, c
3 85 =&gt; 01010101 d, d, d, d
4 90 =&gt; 01011010 d, d, e
5 188 =&gt; 10111100 f, g
6 222 =&gt; 11011110 h, i
7 0 =&gt; 00000000 j, j, j, j
8 0 => 00 j <span class="Comment">// This is the last value, so rather than pad it out, we can just go directly to the char value</span>
</pre>
<!-- END AUTO FORMAT -->
<p>The output data now contains 22 characters, the data has been successfully unpacked.</p>
<a name="#FinalThoughts"></a>
<p><strong>Final Thoughts</strong></p>
<p>Conveniently, this example ignores completely the size of the encoding table, as well as how storage and
retrieval mechanisms, so theoretically 9 bytes is probably as small as we can get. If this compressed data
were to be used outside this example, then the binary tree and code words would also need to be stored along
with the byte list in order to unpack the data at some other time.</p>
<a name="#TheCode"></a>
<p><strong>The Code</strong></p>
<p>I've included my somewhat rambling encoding and decoding process, in C# below.</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
<span class="Keyword">public</span> <span class="Keyword">class</span> <span class="Type">HuffmanCompressor</span>
{
<span class="Keyword">private</span> <span class="Type">HuffmanNode</span> oRootHuffmanNode;
<span class="Keyword">private</span> <span class="Type">List</span>&lt;<span class="Type">HuffmanNode</span>&gt; oValueHuffmanNodes;
<span class="Keyword">private</span> <span class="Type">List</span>&lt;<span class="Type">HuffmanNode</span>&gt; BuildBinaryTree(<span
class="Keyword">string</span> Value)
{
<span class="Type">List</span>&lt;<span class="Type">HuffmanNode</span>&gt; oHuffmanNodes = GetInitialNodeList();
<span class="Comment">// Update node weights</span>
Value.ToList().ForEach(m =&gt; oHuffmanNodes[m].Weight++);
<span class="Comment">// Select only nodes that have a weight</span>
oHuffmanNodes = oHuffmanNodes
.Where(m =&gt; (m.Weight &gt; 0))
.OrderBy(m =&gt; (m.Weight))
.ThenBy(m =&gt; (m.Value))
.ToList();
<span class="Comment">// Assign parent nodes</span>
oHuffmanNodes = UpdateNodeParents(oHuffmanNodes);
oRootHuffmanNode = oHuffmanNodes[0];
oHuffmanNodes.Clear();
<span class="Comment">// Sort nodes into a tree</span>
SortNodes(oRootHuffmanNode, oHuffmanNodes);
<span class="Keyword">return</span> oHuffmanNodes;
}
<span class="Keyword">public</span> <span class="Keyword">void</span> Compress(<span class="Keyword">string</span> FileName)
{
<span class="Type">FileInfo</span> oFileInfo = <span class="Keyword">new</span> <span
class="Type">FileInfo</span>(FileName);
<span class="Keyword">if</span> (oFileInfo.Exists == <span class="Keyword">true</span>)
{
<span class="Keyword">string</span> sFileContents = <span class="Literal">""</span>;
<span class="Keyword">using</span> (<span class="Type">StreamReader</span> oStreamReader = <span
class="Keyword">new</span> <span class="Type">StreamReader</span>(<span class="Type">File</span>.OpenRead(FileName)))
{
sFileContents = oStreamReader.ReadToEnd();
}
<span class="Type">List</span>&lt;<span class="Type">HuffmanNode</span>&gt; oHuffmanNodes = BuildBinaryTree(sFileContents);
<span class="Comment">// Filter to nodes we care about</span>
oValueHuffmanNodes = oHuffmanNodes
.Where(m =&gt; (m.Value.HasValue == <span class="Keyword">true</span>))
.OrderBy(m => (m.BinaryWord)) <span
class="Comment">// Not really required, for presentation purposes</span>
.ToList();
<span class="Comment">// Construct char to binary word dictionary for quick value to binary word resolution</span>
<span class="Type">Dictionary</span>&lt;char, <span class="Keyword">string</span>&gt; oCharToBinaryWordDictionary = <span
class="Keyword">new</span> <span class="Type">Dictionary</span>&lt;char, <span
class="Keyword">string</span>&gt;();
<span class="Keyword">foreach</span> (<span class="Type">HuffmanNode</span> oHuffmanNode <span
class="Keyword">in</span> oValueHuffmanNodes)
{
oCharToBinaryWordDictionary.Add(oHuffmanNode.Value.Value, oHuffmanNode.BinaryWord);
}
<span class="Type">StringBuilder</span> oStringBuilder = <span class="Keyword">new</span> <span
class="Type">StringBuilder</span>();
<span class="Type">List</span>&lt;<span class="Keyword">byte</span>&gt; oByteList = <span class="Keyword">new</span> <span
class="Type">List</span>&lt;<span class="Keyword">byte</span>&gt;();
<span class="Keyword">for</span> (<span class="Keyword">int</span> i = 0; i &lt; sFileContents.Length; i++)
{
<span class="Keyword">string</span> sWord = <span class="Literal">""</span>;
<span class="Comment">// Append the binary word value using the char located at the current file position</span>
oStringBuilder.Append(oCharToBinaryWordDictionary[sFileContents[i]]);
<span class="Comment">// Once we have at least 8 chars, we can construct a byte</span>
<span class="Keyword">while</span> (oStringBuilder.Length &gt;= 8)
{
sWord = oStringBuilder.ToString().Substring(0, 8);
<span class="Comment">// Remove the word to be constructed from the buffer</span>
oStringBuilder.Remove(0, sWord.Length);
}
<span class="Comment">// Convert the word and add it onto the list</span>
<span class="Keyword">if</span> (<span class="Type">String</span>.IsNullOrEmpty(sWord) == <span
class="Keyword">false</span>)
{
oByteList.Add(Convert.ToByte(sWord, 2));
}
}
<span class="Comment">// If there is anything in the buffer, make sure it is retrieved</span>
<span class="Keyword">if</span> (oStringBuilder.Length &gt; 0)
{
<span class="Keyword">string</span> sWord = oStringBuilder.ToString();
<span class="Comment">// Convert the word and add it onto the list</span>
<span class="Keyword">if</span> (<span class="Type">String</span>.IsNullOrEmpty(sWord) == <span
class="Keyword">false</span>)
{
oByteList.Add(Convert.ToByte(sWord, 2));
}
}
<span class="Comment">// Write compressed file</span>
<span class="Keyword">string</span> sCompressedFileName = Path.Combine(oFileInfo.Directory.FullName, <span
class="Type">String</span>.Format(<span class="Literal">"{0}.compressed"</span>, oFileInfo.Name.Replace(oFileInfo.Extension, <span
class="Literal">""</span>)));
<span class="Keyword">if</span> (<span class="Type">File</span>.Exists(sCompressedFileName) == <span
class="Keyword">true</span>)
{
<span class="Type">File</span>.Delete(sCompressedFileName);
}
<span class="Keyword">using</span> (<span class="Type">FileStream</span> oFileStream = <span class="Type">File</span>.OpenWrite(sCompressedFileName))
{
oFileStream.Write(oByteList.ToArray(), 0, oByteList.Count);
}
}
}
<span class="Keyword">public</span> <span class="Keyword">void</span> Decompress(<span class="Keyword">string</span> FileName)
{
<span class="Type">FileInfo</span> oFileInfo = <span class="Keyword">new</span> <span
class="Type">FileInfo</span>(FileName);
<span class="Keyword">if</span> (oFileInfo.Exists == <span class="Keyword">true</span>)
{
<span class="Keyword">string</span> sCompressedFileName = <span class="Type">String</span>.Format(<span
class="Literal">"{0}.compressed"</span>, oFileInfo.FullName.Replace(oFileInfo.Extension, <span
class="Literal">""</span>));
<span class="Keyword">byte</span>[] oBuffer = <span class="Keyword">null</span>;
<span class="Keyword">using</span> (<span class="Type">FileStream</span> oFileStream = <span class="Type">File</span>.OpenRead(sCompressedFileName))
{
oBuffer = <span class="Keyword">new</span> <span class="Keyword">byte</span>[oFileStream.Length];
oFileStream.Read(oBuffer, 0, oBuffer.Length);
}
<span class="Comment">// Find the zero node</span>
<span class="Type">HuffmanNode</span> oZeroHuffmanNode = oRootHuffmanNode;
<span class="Keyword">while</span> (oZeroHuffmanNode.Left != <span class="Keyword">null</span>)
{
oZeroHuffmanNode = oZeroHuffmanNode.Left;
}
<span class="Comment">// Unpack the file contents</span>
<span class="Type">HuffmanNode</span> oCurrentHuffmanNode = <span class="Keyword">null</span>;
<span class="Type">StringBuilder</span> oStringBuilder = <span class="Keyword">new</span> <span
class="Type">StringBuilder</span>();
<span class="Keyword">for</span> (<span class="Keyword">int</span> i = 0; i &lt; oBuffer.Length; i++)
{
<span class="Keyword">string</span> sBinaryWord = <span class="Literal">""</span>;
<span class="Keyword">byte</span> oByte = oBuffer[i];
<span class="Keyword">if</span> (oByte == 0)
{
sBinaryWord = oZeroHuffmanNode.BinaryWord;
}
<span class="Keyword">else</span>
{
sBinaryWord = Convert.ToString(oByte, 2);
}
<span class="Keyword">if</span> ((sBinaryWord.Length &lt; 8) && (i &lt; (oBuffer.Length - 1)))
{
<span class="Comment">// Pad binary word out to 8 places</span>
<span class="Type">StringBuilder</span> oBinaryStringBuilder = <span
class="Keyword">new</span> <span class="Type">StringBuilder</span>(sBinaryWord);
<span class="Keyword">while</span> (oBinaryStringBuilder.Length &lt; 8)
{
oBinaryStringBuilder.Insert(0, <span class="Literal">"0"</span>);
}
sBinaryWord = oBinaryStringBuilder.ToString();
}
<span class="Comment">// Use the binary word to navigate the tree looking for the value</span>
<span class="Keyword">for</span> (<span class="Keyword">int</span> j = 0; j &lt; sBinaryWord.Length; j++)
{
<span class="Keyword">char</span> cValue = sBinaryWord[j];
<span class="Keyword">if</span> (oCurrentHuffmanNode == <span class="Keyword">null</span>)
{
oCurrentHuffmanNode = oRootHuffmanNode;
}
<span class="Keyword">if</span> (cValue == '0')
{
oCurrentHuffmanNode = oCurrentHuffmanNode.Left;
}
<span class="Keyword">else</span>
{
oCurrentHuffmanNode = oCurrentHuffmanNode.Right;
}
<span class="Keyword">if</span> ((oCurrentHuffmanNode.Left == <span class="Keyword">null</span>) && (oCurrentHuffmanNode.Right == <span
class="Keyword">null</span>))
{
<span class="Comment">// No more child nodes to choose from, so this must be a value node</span>
oStringBuilder.Append(oCurrentHuffmanNode.Value.Value);
oCurrentHuffmanNode = <span class="Keyword">null</span>;
}
}
}
<span class="Comment">// Write out file</span>
<span class="Keyword">string</span> sUncompressedFileName = Path.Combine(oFileInfo.Directory.FullName, <span
class="Type">String</span>.Format(<span class="Literal">"{0}.uncompressed"</span>, oFileInfo.Name.Replace(oFileInfo.Extension, <span
class="Literal">""</span>)));
<span class="Keyword">if</span> (<span class="Type">File</span>.Exists(sUncompressedFileName) == <span
class="Keyword">true</span>)
{
<span class="Type">File</span>.Delete(sUncompressedFileName);
}
<span class="Keyword">using</span> (<span class="Type">StreamWriter</span> oStreamWriter = <span
class="Keyword">new</span> <span class="Type">StreamWriter</span>(<span class="Type">File</span>.OpenWrite(sUncompressedFileName)))
{
oStreamWriter.Write(oStringBuilder.ToString());
}
}
}
<span class="Keyword">private</span> <span class="Keyword">static</span> <span class="Type">List</span>&lt;<span
class="Type">HuffmanNode</span>&gt; GetInitialNodeList()
{
<span class="Type">List</span>&lt;<span class="Type">HuffmanNode</span>&gt; oGetInitialNodeList = <span
class="Keyword">new</span> <span class="Type">List</span>&lt;<span class="Type">HuffmanNode</span>&gt;();
<span class="Keyword">for</span> (<span class="Keyword">int</span> i = Char.MinValue; i &lt; Char.MaxValue; i++)
{
oGetInitialNodeList.Add(<span class="Keyword">new</span> <span class="Type">HuffmanNode</span>((<span
class="Keyword">char</span>)(i)));
}
<span class="Keyword">return</span> oGetInitialNodeList;
}
<span class="Keyword">private</span> <span class="Keyword">static</span> <span class="Keyword">void</span> SortNodes(<span
class="Type">HuffmanNode</span> Node, <span class="Type">List</span>&lt;<span
class="Type">HuffmanNode</span>&gt; Nodes)
{
<span class="Keyword">if</span> (Nodes.Contains(Node) == <span class="Keyword">false</span>)
{
Nodes.Add(Node);
}
<span class="Keyword">if</span> (Node.Left != <span class="Keyword">null</span>)
{
SortNodes(Node.Left, Nodes);
}
<span class="Keyword">if</span> (Node.Right != <span class="Keyword">null</span>)
{
SortNodes(Node.Right, Nodes);
}
}
<span class="Keyword">private</span> <span class="Keyword">static</span> <span class="Type">List</span>&lt;<span
class="Type">HuffmanNode</span>&gt; UpdateNodeParents(<span class="Type">List</span>&lt;<span
class="Type">HuffmanNode</span>&gt; Nodes)
{
<span class="Comment">// Assign parent nodes</span>
<span class="Keyword">while</span> (Nodes.Count &gt; 1)
{
<span class="Keyword">int</span> iOperations = (Nodes.Count / 2);
<span class="Keyword">for</span> (<span class="Keyword">int</span> iOperation = 0, i = 0, j = 1; iOperation &lt; iOperations; iOperation++, i += 2, j += 2)
{
<span class="Keyword">if</span> (j &lt; Nodes.Count)
{
<span class="Type">HuffmanNode</span> oParentHuffmanNode = <span class="Keyword">new</span> <span
class="Type">HuffmanNode</span>(Nodes[i], Nodes[j]);
Nodes.Add(oParentHuffmanNode);
Nodes[i] = <span class="Keyword">null</span>;
Nodes[j] = <span class="Keyword">null</span>;
}
}
<span class="Comment">// Remove null nodes</span>
Nodes = Nodes
.Where(m =&gt; (m != <span class="Keyword">null</span>))
.OrderBy(m => (m.Weight)) <span class="Comment">// Choose the lowest weightings first</span>
.ToList();
}
<span class="Keyword">return</span> Nodes;
}
</pre>
<!-- END AUTO FORMAT -->
<p>Here's the code for the HuffmanNode type itself:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
<span class="Keyword">public</span> <span class="Keyword">class</span> <span class="Type">HuffmanNode</span>
{
<span class="Keyword">public</span> <span class="Type">HuffmanNode</span>()
{
}
<span class="Keyword">public</span> <span class="Type">HuffmanNode</span>(<span class="Keyword">char</span> Value)
{
cValue = Value;
}
<span class="Keyword">public</span> <span class="Type">HuffmanNode</span>(<span class="Type">HuffmanNode</span> Left, <span
class="Type">HuffmanNode</span> Right)
{
oLeft = Left;
oLeft.oParent = <span class="Keyword">this</span>;
oLeft.bIsLeftNode = <span class="Keyword">true</span>;
oRight = Right;
oRight.oParent = <span class="Keyword">this</span>;
oRight.bIsRightNode = <span class="Keyword">true</span>;
iWeight = (oLeft.Weight + oRight.Weight);
}
<span class="Keyword">private</span> <span class="Keyword">string</span> sBinaryWord;
<span class="Keyword">private</span> <span class="Keyword">bool</span> bIsLeftNode;
<span class="Keyword">private</span> <span class="Keyword">bool</span> bIsRightNode;
<span class="Keyword">private</span> <span class="Type">HuffmanNode</span> oLeft;
<span class="Keyword">private</span> <span class="Type">HuffmanNode</span> oParent;
<span class="Keyword">private</span> <span class="Type">HuffmanNode</span> oRight;
<span class="Keyword">private</span> <span class="Keyword">char</span>? cValue;
<span class="Keyword">private</span> <span class="Keyword">int</span> iWeight;
<span class="Keyword">public</span> <span class="Keyword">string</span> BinaryWord
{
<span class="Keyword">get</span>
{
<span class="Keyword">string</span> sReturnValue = <span class="Literal">""</span>;
<span class="Keyword">if</span> (<span class="Type">String</span>.IsNullOrEmpty(sBinaryWord) == <span
class="Keyword">true</span>)
{
<span class="Type">StringBuilder</span> oStringBuilder = <span class="Keyword">new</span> <span
class="Type">StringBuilder</span>();
<span class="Type">HuffmanNode</span> oHuffmanNode = <span class="Keyword">this</span>;
<span class="Keyword">while</span> (oHuffmanNode != <span class="Keyword">null</span>)
{
<span class="Keyword">if</span> (oHuffmanNode.bIsLeftNode == <span class="Keyword">true</span>)
{
oStringBuilder.Insert(0, <span class="Literal">"0"</span>);
}
<span class="Keyword">if</span> (oHuffmanNode.bIsRightNode == <span class="Keyword">true</span>)
{
oStringBuilder.Insert(0, <span class="Literal">"1"</span>);
}
oHuffmanNode = oHuffmanNode.oParent;
}
sReturnValue = oStringBuilder.ToString();
sBinaryWord = sReturnValue;
}
<span class="Keyword">else</span>
{
sReturnValue = sBinaryWord;
}
<span class="Keyword">return</span> sReturnValue;
}
}
<span class="Keyword">public</span> <span class="Type">HuffmanNode</span> Left
{
<span class="Keyword">get</span>
{
<span class="Keyword">return</span> oLeft;
}
}
<span class="Keyword">public</span> <span class="Type">HuffmanNode</span> Parent
{
<span class="Keyword">get</span>
{
<span class="Keyword">return</span> oParent;
}
}
<span class="Keyword">public</span> <span class="Type">HuffmanNode</span> Right
{
<span class="Keyword">get</span>
{
<span class="Keyword">return</span> oRight;
}
}
<span class="Keyword">public</span> <span class="Keyword">char</span>? Value
{
<span class="Keyword">get</span>
{
<span class="Keyword">return</span> cValue;
}
}
<span class="Keyword">public</span> <span class="Keyword">int</span> Weight
{
<span class="Keyword">get</span>
{
<span class="Keyword">return</span> iWeight;
}
<span class="Keyword">set</span>
{
iWeight = <span class="Keyword">value</span>;
}
}
<span class="Keyword">public</span> <span class="Keyword">override</span> <span class="Keyword">string</span> ToString()
{
<span class="Type">StringBuilder</span> oStringBuilder = <span class="Keyword">new</span> <span class="Type">StringBuilder</span>();
<span class="Keyword">if</span> (cValue.HasValue == <span class="Keyword">true</span>)
{
oStringBuilder.AppendFormat(<span class="Literal">"'{0}' ({1}) - {2} ({3})"</span>, cValue.Value, iWeight, BinaryWord, BinaryWord.BinaryStringToInt32());
}
<span class="Keyword">else</span>
{
<span class="Keyword">if</span> ((oLeft != <span class="Keyword">null</span>) && (oRight != <span
class="Keyword">null</span>))
{
<span class="Keyword">if</span> ((oLeft.Value.HasValue == <span class="Keyword">true</span>) && (oRight.Value.HasValue == <span
class="Keyword">true</span>))
{
oStringBuilder.AppendFormat(<span class="Literal">"{0} + {1} ({2})"</span>, oLeft.Value, oRight.Value, iWeight);
}
<span class="Keyword">else</span>
{
oStringBuilder.AppendFormat(<span class="Literal">"{0}, {1} - ({2})"</span>, oLeft, oRight, iWeight);
}
}
<span class="Keyword">else</span>
{
oStringBuilder.Append(iWeight);
}
}
<span class="Keyword">return</span> oStringBuilder.ToString();
}
}
</pre>
<!-- END AUTO FORMAT -->
<p>And finally, an extension method that is also required for the code to compile:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
<span class="Keyword">public</span> <span class="Keyword">static</span> <span class="Keyword">int</span> BinaryStringToInt32(<span
class="Keyword">this</span> <span class="Keyword">string</span> Value)
{
<span class="Keyword">int</span> iBinaryStringToInt32 = 0;
<span class="Keyword">for</span> (<span class="Keyword">int</span> i = (Value.Length - 1), j = 0; i &gt;= 0; i--, j++)
{
iBinaryStringToInt32 += ((Value[j] == '0' ? 0 : 1) * (<span class="Keyword">int</span>)(<span
class="Type">Math</span>.Pow(2, i)));
}
<span class="Keyword">return</span> iBinaryStringToInt32;
}
</pre>
<!-- END AUTO FORMAT -->
<p>To get this code running, it will need to be stitched together, perhaps in a console application:</p>
<!-- BEGIN AUTO FORMAT -->
<pre>
<span class="Keyword">public</span> <span class="Keyword">static</span> <span class="Keyword">void</span> Main(<span
class="Keyword">string</span>[] Arguments)
{
<span class="Type">HuffmanCompressor</span> oHuffmanCompressor = <span class="Keyword">new</span> <span
class="Type">HuffmanCompressor</span>();
oHuffmanCompressor.Compress(<span class="Literal">"Huffman.txt"</span>);
<span class="Type">Console</span>.WriteLine();
oHuffmanCompressor.Decompress(<span class="Literal">"Huffman.txt"</span>);
<span class="Type">Console</span>.WriteLine();
}
</pre>
<!-- END AUTO FORMAT -->
<p>Note that for this example, decompression is only possible if the binary tree has been constructed, i,e., the
data has already been compressed.</p>
<br/>
<p>&nbsp;</p>
</div>
<div class="HorizontalSpacer"></div>
<div class="HorizontalSolidLine"></div>
<p align="center">Copyright &copy; 2024 <a href="/" title="carlbelle.com">carlbelle.com</a></p>
<div class="HorizontalSpacer"></div>
</div>
<script type="text/javascript">
var sGuid = '3C9AAA06-9031-4B3E-8AFE-586AE8F6E17F';
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-26910229-1']);
_gaq.push(['_trackPageview']);
(function () {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
</html>
@@ -0,0 +1,374 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!-- http://archive.gamedev.net/archive/reference/articles/article295.html -->
<html xmlns:fb="http://www.facebook.com/2008/fbml" xmlns:og="http://opengraphprotocol.org/schema/">
<head>
<title>GameDev.net - Data Compression Algorithms of LARC and LHarc</title>
<meta content="Data Compression Algorithms of LARC and LHarc" property="og:title"/>
<meta content="article" property="og:type"/>
<meta content="http://www.gamedev.net/reference/articles/article295.asp" property="og:url"/>
<meta content="../../../images.gamedev.net/icons/gamedev_net.png" property="og:image"/>
<meta content="GameDev.net" property="og:site_name"/>
<meta content="103355463445" property="fb:app_id"/>
<meta content="Covers a number of compression algorithms, including LZSS, LZW, Huffman, Arithmetic, LZARI, and LZHUF."
property="og:description"/>
<link href="../../css/reference.css" rel=stylesheet type="text/css">
<link href="../../pics/gdicon.png" rel="icon" type="image/png">
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-279474-1']);
_gaq.push(['_trackPageview']);
(function () {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script>
<script src="../../../partner.googleadservices.com/gampad/google_service.js" type="text/javascript"></script>
<script type="text/javascript">
GS_googleAddAdSenseService("ca-pub-3167291168602081");
GS_googleEnableAllServices();
</script>
<script type="text/javascript">
GA_googleAddSlot("ca-pub-3167291168602081", "Leaderboard_Main");
GA_googleAddSlot("ca-pub-3167291168602081", "Box_Main");
</script>
<script type="text/javascript">
GA_googleFetchAds();
</script>
</head>
<body>
<div id="header"><a href="../../index.html">GameDev.net</a>Data Compression Algorithms of LARC and LHarc</div>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function () {
FB.init({appId: '103355463445', status: true, cookie: true, xfbml: true});
};
(function () {
var e = document.createElement('script');
e.async = true;
e.src = document.location.protocol +
'//connect.facebook.net/en_US/all.js';
document.getElementById('fb-root').appendChild(e);
}());
</script>
<center>
<script type="text/javascript">
GA_googleFillSlot("Leaderboard_Main");
</script>
</center>
<table border="0" cellpadding="5" cellspacing="0" width="100%">
<tr>
<td>
<CENTER><SPAN CLASS="title">Data Compression Algorithms of LARC and LHarc</SPAN>
<BR><SPAN CLASS="author">by Haruhiko Okumura*</SPAN></CENTER>
<P><I>*The author is the sysop of the Science SIG of PV-VAN. His address is: 12-2-404 Green Heights, 580
Nagasawa, Yokosuka 239 Japan</I>
<H1>Introduction</H1>
<P>In the spring of 1988, I wrote a very simple data compression program named LZSS in C language, and
uploaded it to the Science SIG (forum) of PC-VAN, Japan's biggest personal computer network.
<P>That program was based on Storer and Szymanski's slightly modified version of one of Lempel and Ziv's
algorithms. Despite its simplicity, for most files its compression outperformed the archivers then
widely used.
<P>Kazuhiko Miki rewrote my LZSS in Turbo Pascal and assembly language, and soon made it evolve into a
complete archiver, which he named LARC.
<P>The first versions of LZSS and LARC were rather slow. So I rewrote my LZSS using a binary tree, and so
did Miki. Although LARC's encoding was slower than the fastest archiver available, its decoding was
quite fast, and its algorithm was so simple that even self-extracting files (compressed files plus
decoder) it created were usually smaller than non-self-extracting files from other archivers.
<P>Soon many hobby programmers joined the archiver project at the forum. Very many suggestions were made,
and LARC was revised again and again. By the summer of 1988, LARC's speed and compression have improved
so much that LARC-compressed programs were beginning to be uploaded in many forums of PC-VAN and other
networks.
<P>In that summer I wrote another program, LZARI, which combined the LZSS algorithm with adaptive arithmetic
compression. Although it was slower than LZSS, its compression performance was amazing.
<P>Miki, the author of LARC, uploaded LZARI to NIFTY-Serve, another big information network in Japan. In
NIFTY-Serve, Haruyasu Yoshizaki replaced LZARI's adaptive arithmetic coding with a version of adaptive
Huffman coding to increase speed. Based on this algorithm, which he called LZHUF, he developed yet
another archiver, LHarc.
<P>In what follows, I will review several of these algorithms and supply simplified codes in C language.
<H1>Simple coding methods</H1>
<P>Replacing several (usually 8 or 4) "space" characters by one "tab" character is a very primitive method
for data compression. Another simple method is run-length coding, which encodes the message
"AAABBBBAACCCC" into "3A4B2A4C", for example.
<H1>LZSS coding</H1>
<P>This scheme is initiated by Ziv and Lempel [<A HREF="#ref">1</A>]. A slightly modified version is
described by Storer and Szymanski [<A HREF="#ref">2</A>]. An implementation using a binary tree is
proposed by Bell [<A HREF="#ref">3</A>]. The algorithm is quite simple: Keep a ring buffer, which
initially contains "space" characters only. Read several letters from the file to the buffer. Then
search the buffer for the longest string that matches the letters just read, and send its length and
position in the buffer.
<P>If the buffer size is 4096 bytes, the position can be encoded in 12 bits. If we represent the match
length in four bits, the [position, length] pair is two bytes long. If the longest match is no more than
two characters, then we send just one character without encoding, and restart the process with the next
letter. We must send one extra bit each time to tell the decoder whether we are sending a [position,
length] pair or an unencoded character.
<P>The accompanying file LZSS.C is a version of this algorithm. This implementation uses multiple binary
trees to speed up the search for the longest match. All the programs in this article are written in
draft-proposed ANSI C. I tested them with Turbo C 2.0.
<H1>LZW coding</H1>
<P>This scheme was devised by Ziv and Lempel [<A HREF="#ref">4</A>], and modified by Welch [<A
HREF="#ref">5</A>].
<P>The LZW coding has been adopted by most of the existing archivers, such as ARC and PKZIP. The algorithm
can be made relatively fast, and is suitable for hardware implementation as well.
<P>The algorithm can be outlined as follows: Prepare a table that can contain several thousand items.
Initially register in its 0th through 255th positions the usual 256 characters. Read several letters
from the file to be encoded, and search the table for the longest match. Suppose the longest match is
given by the string "ABC". Send the position of "ABC" in the table. Read the next character from the
file. If it is "D", then register a new string "ABCD" in the table, and restart the process with the
letter "D". If the table becomes full, discard the oldest item or, preferably, the least used.
<P>A Pascal program for this algorithm is given in Storer's book [<A HREF="#ref">6</A>].
<H1>Huffman coding</H1>
<P>Classical Huffman coding is invented by Huffman [<A HREF="#ref">7</A>]. A fairly readable account is
given in Sedgewick [<A HREF="#ref">8</A>].
<P>Suppose the text to be encoded is "ABABACA", with four A's, two B's, and a C. We represent this situation
as follows:
<P>
<CENTER>
<TABLE>
<TR>
<TD><PRE><DIV CLASS="code">4 2 1
| | |
A B C
</DIV></PRE>
</TD>
</TR>
</TABLE>
</CENTER>
<P>Combine the least frequent two characters into one, resulting in the new frequency 2 + 1 = 3:
<P>
<CENTER>
<TABLE>
<TR>
<TD><PRE><DIV CLASS="code">4 3
| / \
A B C
</DIV></PRE>
</TD>
</TR>
</TABLE>
</CENTER>
<P>Repeat the above step until the whole characters combine into a tree:
<P>
<CENTER>
<TABLE>
<TR>
<TD><PRE><DIV CLASS="code"> 7
/ \
/ 3
/ / \
A B C
</DIV></PRE>
</TD>
</TR>
</TABLE>
</CENTER>
<P>Start at the top ("root") of this encoding tree, and travel to the character you want to encode. If you
go left, send a "0"; otherwise send a "1". Thus, "A" is encoded by "0", "B" by "10", "C" by "11".
Algotether, "ABABACA" will be encoded into ten bits, "0100100110".
<P>To decode this code, the decoder must know the encoding tree, which must be sent separately.
<P>A modification to this classical Huffman coding is the adaptive, or dynamic, Huffman coding. See, e.g.,
Gallager [<A HREF="#ref">9</A>]. In this method, the encoder and the decoder processes the first letter
of the text as if the frequency of each character in the file were one, say. After the first letter has
been processed, both parties increment the frequency of that character by one. For example, if the first
letter is 'C', then freq['C'] becomes two, whereas every other frequencies are still one. Then the both
parties modify the encoding tree accordingly. Then the second letter will be encoded and decoded, and so
on.
<H1>Arithmetic coding</H1>
<P>The original concept of arithmetic coding is proposed by P. Elias. An implementation in C language is
described by Witten and others [<A HREF="#ref">10</A>].
<P>Although the Huffman coding is optimal if each character must be encoded into a fixed (integer) number of
bits, arithmetic coding wins if no such restriction is made.
<P>As an example we shall encode "AABA" using arithmetic coding. For simplicity suppose we know beforehand
that the probabilities for "A" and "B" to appear in the text are 3/4 and 1/4, respectively.
<P>Initially, consider an interval:
<P>
<CENTER><PRE><DIV CLASS="code">0 <= x < 1.
</DIV></PRE>
</CENTER>
<P>Since the first character is "A" whose probability is 3/4, we shrink the interval to the lower 3/4:
<P>
<CENTER><PRE><DIV CLASS="code">0 <= x < 3/4.
</DIV></PRE>
</CENTER>
<P>The next character is "A" again, so we take the lower 3/4:
<P>
<CENTER><PRE><DIV CLASS="code">0 <= x < 9/16.
</DIV></PRE>
</CENTER>
<P>Next comes "B" whose probability is 1/4, so we take the upper 1/4:
<P>
<CENTER><PRE><DIV CLASS="code">27/64 <= x < 9/16,
</DIV></PRE>
</CENTER>
<P>because "B" is the second element in our alphabet, {A, B}. The last character is "A" and the interval is
<P>
<CENTER><PRE><DIV CLASS="code">27/64 <= x < 135/256,
</DIV></PRE>
</CENTER>
<P>which can be written in binary notation
<P>
<CENTER><PRE><DIV CLASS="code">0.011011 <= x < 0.10000111.
</DIV></PRE>
</CENTER>
<P>Choose from this interval any number that can be represented in fewest bits, say 0.1, and send the bits
to the right of "0."; in this case we send only one bit, "1". Thus we have encoded four letters into one
bit! With the Huffman coding, four letters could not be encoded into less than four bits.
<P>To decode the code "1", we just reverse the process: First, we supply the "0." to the right of the
received code "1", resulting in "0.1" in binary notation, or 1/2. Since this number is in the first 3/4
of the initial interval 0 <= x < 1, the first character must be "A". Shrink the interval into the lower
3/4. In this new interval, the number 1/2 lies in the lower 3/4 part, so the second character is again
"A", and so on. The number of letters in the original file must be sent separately (or a special 'EOF'
character must be appended at the end of the file).
<P>The algorithm described above requires that both the sender and receiver know the probability
distribution for the characters. The adaptive version of the algorithm removes this restriction by first
supposing uniform or any agreed-upon distribution of characters that approximates the true distribution,
and then updating the distribution after each character is sent and received.
<H1>LZARI</H1>
<P>In each step the LZSS algorithm sends either a character or a [position, length] pair. Among these,
perhaps character "e" appears more frequently than "x", and a [position, length] pair of length 3 might
be commoner than one of length 18, say. Thus, if we encode the more frequent in fewer bits and the less
frequent in more bits, the total length of the encoded text will be diminished. This consideration
suggests that we use Huffman or arithmetic coding, preferably of adaptive kind, along with LZSS.
<P>This is easier said than done, because there are many possible [position, length] combinations. Adaptive
compression must keep running statistics of frequency distribution. Too many items make statistics
unreliable.
<P>What follows is not even an approximate solution to the problem posed above, but anyway this was what I
did in the summer of 1988.
<P>I extended the character set from 256 to three-hundred or so in size, and let characters 0 through 255 be
the usual 8-bit characters, whereas characters 253 + n represent that what follows is a position of
string of length n, where n = 3, 4 , .... These extended set of characters will be encoded with adaptive
arithmetic compression.
<P>I also observed that longest-match strings tend to be the ones that were read relatively recently.
Therefore, recent positions should be encoded into fewer bits. Since 4096 positions are too many to
encode adaptively, I fixed the probability distribution of the positions "by hand." The distribution
function given in the accompanying LZARI.C is rather tentative; it is not based on thorough
experimentation. In retrospect, I could encode adaptively the most significant 6 bits, say, or perhaps
by some more ingenious method adapt the parameters of the distribution function to the running
statistics.
<P>At any rate, the present version of LZARI treats the positions rather separately, so that the overall
compression is by no means optimal. Furthermore, the string length threshold above which strings are
coded into [position, length] pairs is fixed, but logically its value must change according to the
length of the [position, length] pair we would get.
<H1>LZHUF</H1>
<P>LZHUF, the algorithm of Haruyasu Yoshizaki's archiver LHarc, replaces LZARI's adaptive arithmetic coding
with adaptive Huffman. LZHUF encodes the most significant 6 bits of the position in its 4096-byte buffer
by table lookup. More recent, and hence more probable, positions are coded in less bits. On the other
hand, the remaining 6 bits are sent verbatim. Because Huffman coding encodes each letter into a fixed
number of bits, table lookup can be easily implemented.
<P>Though theoretically Huffman cannot exceed arithmetic compression, the difference is very slight, and
LZHUF is fairly fast.
<P>The accompanying file LZHUF.C was written by Yoshizaki. I translated the comments into English and made a
few trivial changes to make it conform to the ANSI C standard.
<A NAME="ref"><H1>References</H1></A>
<DL COMPACT>
<DT>[1]
<DD>J. Ziv and A. Lempel, IEEE Trans. IT-23, 337-343 (1977).
<DT>[2]
<DD>J. A. Storer and T. G. Szymanski, J. ACM, 29, 928-951 (1982).
<DT>[3]
<DD>T. C. Bell, IEEE Trans. COM-34, 1176-1182 (1986).
<DT>[4]
<DD>J. Ziv and A. Lempel, IEEE Trans. IT-24, 530-536 (1978).
<DT>[5]
<DD>T. A. Welch, Computer, 17, No.6, 8-19 (1984).
<DT>[6]
<DD>J. A. Storer, Data Compression: Methods and Theory (Computer Science Press, 1988).
<DT>[7]
<DD>D. A. Huffman, Proc IRE 40, 1098-1101 (1952).
<DT>[8]
<DD>R. Sedgewick, Algorithms, 2nd ed. (Addison-Wesley, 1988).
<DT>[9]
<DD>R. G. Gallager, IEEE Trans. IT-24, 668-674 (1978).
<DT>[10]
<DD>I. E. Witten, R. M. Neal, and J. G. Cleary, Commun. ACM 30, 520-540 (1987).
<p align="center"><b><a
href="http://archive.gamedev.net/community/forums/topic.asp?key=featart&amp;uid=295&amp;forum_id=35&amp;Topic_Title=Data+Compression+Algorithms+of+LARC+and+LHarc">Discuss
this article in the forums</a></b></p>
<p>
<br><span class="maintext-2">Date this article was posted to GameDev.net: <b>7/16/1999</b>
<br>(Note that this date does not necessarily correspond to the date the article was written)</span>
<p><b>See Also:</b><br>
<a href="../list6d5b.html?categoryid=125">Compression Algorithms </a><br>
<p align="center">&copy; 1999-2011 Gamedev.net. All rights reserved. <a
href="../../info/legal.htm#copyright"><u>Terms of Use</u></a> <a
href="../../info/legal.htm#privacy"><u>Privacy Policy</u></a>
<br><span class="maintext-1">Comments? Questions? Feedback? <a href="../../info/faq.html">Click here!</a></span>
</p>
</td>
</tr>
</table>
<!-- start Vibrant Media IntelliTXT Tooltip style sheet -->
<style type="text/css">
.iTt {
FONT-FAMILY: Verdana, Arial, Helvetica;
FONT-SIZE: 11px;
FONT-STYLE: normal;
FONT-WEIGHT: normal;
COLOR: black;
BACKGROUND-COLOR: lightyellow;
BORDER: black 1px solid;
PADDING: 2px;
}
.iTt a {
COLOR: 0000CC;
}
.iTt a:visited {
COLOR: 0000CC;
}
.iTt a:hover {
COLOR: 6666CC;
}
.iTt TD {
COLOR: 999999;
}
</style>
<!-- end Vibrant Media IntelliTXT style sheet -->
<!-- start Vibrant Media IntelliTXT script section -->
<script src="http://gamedev.us.intellitxt.com/intellitxt/front.asp?ipid=1966" type="text/javascript"></script>
<!-- end Vibrant Media IntelliTXT script section -->
</body>
</html>
File diff suppressed because it is too large Load Diff
+741
View File
@@ -0,0 +1,741 @@
The Wayback Machine -
https://web.archive.org/web/20090409112451/http://www.msen.com:80/~crandall/fkiss.html
KiSS .cnf layout, FKiSS/FKiSS2 command reference, and various PlayFKiSS
notes.
* written by Chad Randall crandall@msen.com
<https://web.archive.org/web/20090409112451/http://www.msen.com/~crandall/email.html>
* Last updated, December 31, 1997
Terminology
Action
See Command
Cel
A graphical image where any pixel using color 0 is transparent. The
basic building blocks for a KiSS set. Cels are identified by name. When
issuing FKiSS events and commands, there should never be more than one
cel per KiSS set with the same name. Such instances are undefined and
undesired results may occur.
Cherry Kiss (ckiss)
A recent cel format allowing 24 and 32 bit color. This includes an alpha
channel for variable transparency. This format requires quite a bit of
memory and horsepower. But as computers get faster and cheaper, this may
become more widespread. PlayKis s for Acorn, WKiss32, and KissLD are the
only viewers capable of ckiss right now. The next release of PlayFKiSS
*will* have ckiss support. Internally, pfks072 can already handle it.
Command
A keyword that issues changes or activates something else.
Fix
A numerical value ranging from 0 up to 32000, applied to an object. An
object may not be moved by the user unless the fix value is equal to
zero. Any time the user clicks on the object, it's fix value is reduced
by one.
FKiSS
An addition to the KiSS format that allows more user interactivity.
FKiSS comprises events, commands, and the newer flags. Most FKiSS
innovations come from the Japanese side of the Pacific.
FKiSS2
Extra commands and features developed by Chad Randall and John Stiles.
Included are object collision detection and relative movement.
FKiSS2.1
Even more commands, including "if" conditional timers and random movement.
Flags
A recent FKiSS addition that allows the set creator to set certain
states without requiring a command entry. An example is the ;%t128
portion of a cel definition line.
Event
A keyword specifying a group of commands that will be executed when a
specific condition occurs.
Object
A group of cels. This is a convenient way to issue commands and events
to a bunch of cels, such as an item of clothing. Such item can contain
one cel for the foreground, one for the inside, one for the sleeve, etc.
Objects are usually proceeded by an " #".
Palette
A number of colors consisting at the minimum of 16 pens up to 256 pens.
There can also be up to 10 palette groups per palette. The maximum
number of pens that can be specified is 256*10 or 2560.
Set
The group of cels displayed at any one time. There can be up to 10 sets
per KiSS set.
Format of the .cnf file
The sample layout of a .cnf file follows:
|
;comments
%palette1.kcf
%palette2.kcf
(640,480) ; Environment size
[0 ; border color
;more comments
#3 fog.cel *1 :0 1 2 3 4 5 6 7 8 9 ;%t128 transparent fog
#0.10 dress.cel *0 :0 1 2 3 4 5 6 7 8 9 ; lady's dress
#1.32000 body.cel *0 :0 1 2 3 4 5 6 7 8 9 ; lady
#0.10 dress_.cel *0 :0 1 2 3 4 5 6 7 8 9 ; back of lady's dress
;even more comments
;@EventHandler()
;@initalize()
;@ unmap("fog.cel")
;some more comments
$0 120,50 220,150 65,65
$1 120,50 220,150 65,65
';end of file
|
Now to break down each line and explain what all those symbols mean.
|
%palette1.kcf
|
||
||
This indicates a palette file. There may be up to 256 pens per KiSS set.
There may be up to 16 palette files if each holds 16 pens. Any pens over
256 are ignored. Also, each palette file can hold up to 10 groups of
pens, for a total of 2560 pens. These lines are required! There is no
default pen set defined.
This line maybe omitted in Cherry Kiss files. Since 24/32 bit graphics
define their own palettes, there is no requirement for a palette. If you
intend to mix ckiss and kiss/gs cels, this line will still be required.
|
(640,480)
|
||
||
This specifies the playfield size. If this line is omitted, it will
default to 448 x 320.
|
[0
|
||
||
This is the border color, as a pen number. Some programs will ignore
this value. Other programs such as DOS based ones will fill the area
around the playfield with this color. It will default to 0 if omitted.
|
#0 dress.cel *0 :0 1 2 3 4 5 6 7 8 9 ;%t128 ;comment
|
This is broken down farther:
|
#0.10
|
||
||
The object number this individual cel is grouped with. The number
following the "." is the fix value of the related object. This value is
optional and will default to 0.
|
dress.cel
|
||
||
The name of the cel. While this can be any name you want, there is a
certain convention to follow if you plan on distributing the file. Keep
filenames compliant to the 8.3 filename MS-DOS naming restriction. Also
use lowercase lette rs to conform under case-sensitive systems such as UNIX.
|
*0
|
||
||
This indicates the offset into the palette pen group. This only applies
to those cels with 16 colors. 256 color cels will default to offset 0.
|
:0 1 2 3 4 5 6 7 8 9
|
||
||
This section specifies which set groups the cel is visible in. A cel
will default to all groups if this section is omitted.
|
;%t128
|
||
||
This indicates the cel's transparent value. Any number from 0 to 255 may
be used, with 0 as opaque and 255 completely invisible. This setting is
not used by older KiSS viewers, so be aware of Yours truly, target
audience. See also t ransparent()
* I intend to allow other letters here in future releases of PlayFKiSS.
Such actions as lighten, darken, additive, subtractive, displacement,
XOR, AND, OR, and other graphics mixing operators. This will allow the
end user to recreate many PhotoShop l ayering features.
Even further down we find:
|
;@EventHandler
;@initalize()
;@ unmap("fog.cel")
|
This is a section of FKiSS code. The ";@" symbols always indicate FKiSS
commands are on that line. EventHandler is a *case-sensitive* flag that
starts the FKiSS section of code. Likewise, all commands and events
*must* *be* in *lowerca se* to be properly recognized by most FKiSS
parsers. A complete list of FKiSS and FKiSS2 commands follow at the end
of this document.
|
$0 120,50 220,150 65,65
$1 120,50 220,150 65,65
|
||
||
This line basically says that for set 0, use palette group 0. Place
object 0 at 120 x 50, object 1 at 220 x 150, and object 2 at 65 x 65.
Likewise, set 1 is identical except that palette group 1 is used. Don't
attempt to edit these values by hand. Most any KiSS viewer program will
save these for you. You can cut and paste them from a temporary .cnf
file into your master .cnf file if you don't wish to loose other
information such as comments.
Standard FKiSS/FKiSS2 Events
alarm(number)
Triggered when a timer reaches zero. Note: Most viewer programs will
only accept timer numbers from 0 up to 64. The newer FKiSS2 proposal
will allow from 0 up to 511.
apart("celname.cel", "celname2.cel")
Triggered when cel1 no longer touches cel2, but only if they touching
before the move. A touch consists of non-transparent pixels that
overlap. *Note: This is a proposed FKiSS2.1 event and is not generally
supported by other viewers.*
**
**
begin()
Triggered after the first screen is activated. This is a good place to
start the animations using timer()
catch(#object) / catch("celname.cel")
Triggered when the user "grabs" an object or cel, and the fix value is
at zero (movable).
col(number)
Similar to set(), but triggered when the palette group is changed. Note:
col is probably short for color.
collide("celname.cel", "celname2.cel")
Triggered when cel1 touches cel2, but only if they were not touching
before the move. A touch consists of non-transparent pixels that
overlap. *Note: This is a proposed FKiSS2.1 event and is not generally
supported by other viewers.*
**
**
drop(#object) / catch("celname.cel")
Triggered when the user "drops" an object or cel, and the fix value is
at zero (movable).
end()
Triggered when the user closes the KiSS set. This is a good place to
play or display any farewell messages.
fixcatch(#object) / fixcatch("celname.cel")
Similar to catch(), but only triggered when the object is fixed in place.
fixdrop(#object) / catch("celname.cel")
Similar to drop(), but only triggered when the object is fixed in place.
in(#object1,#object2)
Triggered when one object's bounding boxes overlap another. The bounding
boxes are formed by adding all the visible and mapped cels's rectangles
to form a geometric shape. It is *not* the total rectangle size, but
rather a combination of individua l rectangles. This event only occurs
when 2 objects are apart and are then moved together. *Note: This is a
proposed FKiSS2 event and is not generally supported by other viewers.*
**
**
initialize()
Triggered commands before any visual information is displayed. This is a
good place to unmap cels.
never()
This is *never* triggered. It is a good place to hide code you don't
wish to run. Note: You can precede FKiSS2 code with this event to hide
it from older FKiSS viewers.
out(#object1,#object2)
Similar to in(), but triggered when 2 objects are moved apart. *Note:
This is a proposed FKiSS2 event and is not generally supported by other
viewers.*
**
**
press(#object) / fixcatch("celname.cel")
Similar to catch(), but triggered regardless of the fix value.
release(#object) / fixcatch("celname.cel")
Similar to drop(), but triggered regardless of the fix value.
set(number)
Triggered when the user or changeset() command changes the current set.
stillin(#object1,#object2)
Triggered when one object's bounding boxes overlap another. The bounding
boxes are formed by adding all the visible and mapped cels's rectangles
to form a geometric shape. It is *not* the total rectangle size, but
rather a combination of individua l rectangles. Unlike in(), this event
occurs whenever the object is moved and "touches" the second object,
regardless of the initial states. *Note: This is a proposed FKiSS2 event
and is not generally supported by other viewers.*
**
**
stillout(#object1,#object2)
Similar to stillin(), but triggered when 2 objects are moved apart.
*Note: This is a proposed FKiSS2 event and is not generally supported by
other viewers.*
**
**
unfix(#object)
Triggered when the fix value reaches zero.
version(set_version_number)
Triggered when the current viewer version is equal or greater than the
FKiSS command/event version number. This can be used to inform the user
that the KiSS set uses commands that are unsupported by the viewer. See
the extended notes at the bottom of t his file for an effective example
on the usage of this event. *Note: This is a proposed FKiSS2 event and
is not generally supported by other viewers.*
**
Standard FKiSS/FKiSS2 Commands
altmap(#object) / altmap("celname.cel")
Turns any mapped cels to unmapped and vice-versa.
changecol(palette)
Forces another palette group to become the active one.
changeset(set)
Forces another set to become the active one.
debug("messagestring")
Displays a message. Each viewer may have a different method to display
debug information.
iffixed(#obj, timer, value)
Sets timer if the object specified by #obj is fixed (fix value is
greater than zero). *Note: This is a proposed FKiSS2.1 event and is not
generally supported by other viewers.*
**
**
ifmapped("cel", timer, value)
Sets timer if the cel specified by "cel" is mapped. *Note: This is a
proposed FKiSS2.1 event and is not generally supported by other viewers.*
**
**
ifmoved (#obj, timer, value)
Sets timer if the object specified by #obj has been moved. An object is
defined as moved if it's coordinates are not equal to the coordinates
specified in the .CNF file. *Note: This is a proposed FKiSS2.1 event and
is not generally supported by other viewers.*
**
**
ifnotfixed(#obj, timer, value)
Sets timer if the object specified by #obj is not fixed (fix value is
equal to zero). *Note: This is a proposed FKiSS2.1 event and is not
generally supported by other viewers.*
**
**
ifnotmapped("cel", timer, value)
Sets timer if the cel specified by "cel" is not mapped. *Note: This is a
proposed FKiSS2.1 event and is not generally supported by other viewers.*
**
**
ifnotmoved (#obj, timer, value)
Sets timer if the object specified by #obj has not been moved. An object
is defined as moved if it's coordinates are not equal to the coordinates
specified in the .CNF file. *Note: This is a proposed FKiSS2.1 event and
is not generally supported by o ther viewers.*
**
**
map(#object) / map("celname.cel")
Maps all cels.
move(#object,offsetx,offsety)
Moves an object using the offsets specified. Note: PlayFKiSS will never
move an object off-screen, even when "Enforce Boundaries" is turned off.
movebyx(#object,#object,offsetx)
Moves an object using the offset from another object.*Note: This is a
proposed FKiSS2 command, and is not generally supported by other viewers*.
movebyy(#object,#object,offsetx)
Moves an object using the offset from another object. *Note: This is a
proposed FKiSS2 command, and is not generally supported by other viewers.*
**
**
moverandx(#object,min,max)
Moves an object using random values. The new coordinate is relative,
based on the min and max values. Ex: If min is -1 and max is 1, the new
location may be either 1 pixel less, one pixel more, or the same
location.*Note: This is a proposed FKiSS2.1 command, and is not
generally supported by other viewers*.
moverandy(#object,min,max)
Moves an object using random values. The new coordinate is relative,
based on the min and max values. Ex: If min is -1 and max is 1, the new
location may be either 1 pixel less, one pixel more, or the same
location.*Note: This is a proposed FKiSS2.1 command, and is not
generally supported by other viewers*.
moveto(#object,x,y)
Moves an object to the absolute coordinates specified. Note: PlayFKiSS
will never move an object off-screen, even when "Enforce Boundaries" is
turned off. *Note: This is a proposed FKiSS2 command, and is not
generally supported by other viewers.*
*< /DIR> *
**
movetorand(#object)
Moves an object to random coordinates. Note: PlayFKiSS will never move
an object off-screen, even when "Enforce Boundaries" is turned off.
*Note: This is a proposed FKiSS2.1 command, and is not generally
supported by other viewers.*
**
**
music ("filename.mid")
Plays a MIDI song. *Note: This is a proposed FKiSS2 command, and is not
generally supported by other viewers.*
**
**
nop()
Does nothing. NOP is short for "No Operation" in assembly language.
notify("message string")
Currently a synonym for debug() *Note: This is a proposed FKiSS2
command, and is not generally supported by other viewers.*
**
**
quit()
Forces the viewer to exit. Note: PlayFKiSS retains this command when
saving, but will not quit when issued.
randomtimer(number,minimum,maximum)
Sets a timer based on a random number.
setfix(#object,fixval)
Manually sets the fix val for the object. If the value is 0, any unfix()
events linked to this object will be triggered. *Note: This is a
proposed FKiSS2.1 command, and is not generally supported by other viewers.*
**
sound("filename.wav") / sound("filename.au")
Plays a sound. Not all viewers support .au format and vice-versa. If
possible, include 2 versions of your .cnf file and convert all sounds to
both formats.
timer(number,duration)
Starts a timer, using duration microseconds for the countdown. Setting a
timer that is already active will reset to the duration value. Setting a
timer to 0 will effectively turn it off. A microsecond is 1/100 of a
second; A timer set to 100 microsecon ds will trigger an alarm 1 second
later. Note: PlayFKiSS has a scale of 10 microseconds between each alarm
event, and will only visually update the screen every 100 microseconds.
transparent(#object, reloffset) / transparent("celname.cel", reloffset)
Sets the transparency level of the cels/object. reloffset is the value
to add to the current transparency level. The maximum range is from 0
(totally opaque) up to 255 (totally transparent). Note: This command is
fairly recent and is not supported by a ll FKiSS viewers. Also, some
viewers show cels dithered and other's change to true-color mode (16
million colors).
unmap(#object) / unmap("celname.cel")
First case, unmaps all cels belonging to object "#object". Second case
unmaps a specific
viewport(x,y)
Changes the viewport offset. Note: PlayFKiSS ignores this command, but
will retain it when saving.
windowsize(x,y)
Changes the window size. Note: PlayFKiSS ignores this command, but will
retain it when saving.
Extended FKiSS2 information
The commands and events marked as FKiSS2 and FKiSS2.1 are, at present,
only supported by PlayFKiSS for Win95/NT4 and PlayKiss for Acorn. French
KiSS has FKiSS2 support, minus music() and all transparency related
stuff. WKiss32g s upports a subset of FKiSS2 including (but not limited
to) music(), in(), out(), movebyx(), movebyy(), collide(), apart(),
movetorand(), and moveto(). KissLD7x also supports a few FKiSS2
commands. If you would like to see more FKiSS2 commands, and/or set s,
spread the word. Ask your favorite KiSS programmer to implement the
commands. Ask your favorite artist to either insert FKiSS2 commands, or
create an enhanced .cnf file. Only the support of users will determine
the outcome of the entire KiSS project.
To use the version() event, code your EventHandler as follows:
|
;@EventHandler
;@nothing() ; This tricks older viewers into ignoring the next 2 lines.
;@version(2) ; If the viewer supports FKiSS2,
; ; revision 2, run the following commands.
;@ unmap("warning.cel")
; ; warning.cel is a huge poster saying
; ; "You need a FKiSS2 viewer to play this set."
;@begin()
;; If this warning is only a warning (maybe the set will still work
without FKiSS2), you could allow the user to dismiss the warning by
clicking on it:
;@release("warning.cel")
;@ unmap("warning.cel")
|
FKiSS3 specs
<https://web.archive.org/web/20090409112451/http://www.msen.com/~crandall/fkiss3.html>
+417
View File
@@ -0,0 +1,417 @@
------------------------------------------------------------------------------
KISS FORMAT DOCUMENT
This is a poor translation of kissgs.doc. Comments, questions and
corrections are greatly welcomed.
_KISS/GS ( KISS General Specification ) Manual by K.O.S._
>>>>>>>
This is an abridged translation of the official document about KISS
data format. However parts of it are undefined and will be updated
gradually.
_________________________________________________________________
Outline
1. Introduction
2. Abstract of New Specification
3. Basic Concepts
4. KISS/GS New features
5. Details of the Configuration File
6. Details of Cel Files
7. Palette File Detail
8. KISS/GS Ranks
9. Appendix
_________________________________________________________________
1. _Introduction_
KISS - a paper doll program has been developped for computers
based on NEC PC-9801VM2 architecture since March 1991. Many people
have enjoyed and implemented KISS for other computers. Now KISS is
running on many platforms.
Now we make a reference manual about new KISS data format, that
is,
KISS General Specification : KISS/GS
and release this.
2. _Abstract of new specification_
New style KISS data is upper compatible with the old style.
* color (cel) 16 colors -> 16 or 256 colors
* color (palette) 4096 colors -> 4096 or 16,777,216 colors
* screen size 640 x 400 -> 768 x 480 or more
* cel and object max 128 -> 256 or more
Multiple palette files available.
(-> 4-1 multipalette)
A header is added to cel/palette files to store additional
information. At implemention some limitations may exist about
hardware and software resources. So implementation level is
indicated in the following form:
_KISS/GSn ( n = 1,2,3,... )_
(-> 7 KISS/GS Ranks)
3. _Basic Concepts_
_ 3-1:_ _KISS Functions_
KISS is a image viewer program with transparency
processing. It is able to display overlapped pictures and
allow the user to manipulate them with real-time mouse
operation.
_3-2: _ _Files_
KISS needs the following files.
o Cel file
Image data to be moved. An array of pixels. Pixel code 0
is transparent pixel.
o Palette file
Defines actual colors of pixels.
o Configuration file
Describes the overlapping order and positions of cels,
etc. KISS reads this first and refers it to read other
files. This file is a text file created with a text
editor. Some parts of it may be changed by KISS
program's save function.
_3-3: _ _Technical terms_
o Cel
A minimum element of pictures.
o Object
A unit of pictures for moving; composed of one or more
cels.
o Palette group
Color information of one screen.
o Set
Data composed of a palette group and object positions.
4. _ GS-KISS New Feature _
_4-1: _ _Multipalette _
Before KISS/GS, all cels are drawn according to a single
palette table in the palette file. Now, cels can be drawn
with individual palette tables.
The total number of colors must be less than or equal to
256.
Each cel requires information about which palette it
uses.
5. _ Details of the configuration file _
Each line must be shorter than 256 bytes.
Palette/cel filenames are composed of the basename (max 8 bytes) +
the suffix (max 3 bytes). Upper and lower cases are identical. The
character set used in filenames is [_0-9a-z].
The character at the top of each line is one of the following:
+ _ '='_ _Memory size _
Description: =<memory >K
For KISS v1.0 compatibility. KISS v2.0 or later ignores it.
Its use is discouraged.
Example:
|
|=260K
|
+ _'('_ _ Screen size_
Description: (<horizontal size >, <vertical size >)
Defines the screen size.
If omitted, (448,320) is assumed for KISS v2.18
compatibility. Maximum size of screen is (640,400) on
KISS/GS2. (-> 7 KISS/GS Ranks)
Example:
|
|(640,400)
|
+ _'%'_ _Palette file_
Description: % <Palette filename >
Describes a palette file. Palette files are numbered as
0,1,2... in the order of appearance.
All colors in the palette file #0 are used. But the first
(transparent) color in the other palette files is ignored.
Total number of colors must be less than or equal to 256.
All palette files must be described before description about
cel files.
Example:
|
|%COL.KCF
|%COL2.KCF
|
+ _'['_ _ Border color_
Description: [ <Border color's pixel code >
Outside of the screen is filled with this pixel code.
Example:
|
|[12
|
+ _'#'_ _ Cel file_
Description: #<Mark >.<Fix > <Cel filename > [* <Palette
number >] [: <Set number >...]
<Mark >:
Identification number to specify object. Cels of
the same Mark are unified and make an object.
Object number is from 0 to 255 on KISS/GS2. (->7
KISS/GS Ranks)
<Fix >:
Fixed value. Specify this for fixed objects such as
the doll's body. An object with a big value is hard
to move. Value is a number from 0 to 32767. If
omitted, treated as 0 (not fixed).
<Cel filename >:
Describes the filename with suffix.
<Palette number >:
Indicates which palette file this cel uses. If
omitted, treated as 0.
<Set number >:
The cel is drawn only in the sets specified here. 0
- 9 is available. If omitted, this cel is drawn in
all sets. The order of cel file descriptions
determines the priority in drawing cels. The number
of cels is max 256. (- >7 KISS/GS Ranks)
Example:
|
|#2 data1.cel ; forward (near)
|#3 data2.cel :2 3 4 ;
|#4.255 data3.cel ; fixed
|#5 data4.cel *1 :5 ;
|#2 data1_.cel ; backward (far)
|
data1.cel and data1_.cel make one object.
+ _'$'_ _ Set information_
Description: $ <Palette group > [ <xpos,ypos >...]
Palette group and positions of object for each set. There are
max 10 sets. This section can be overwritten by KISS save
function.
A long description is folded, and the following lines start
with a ' ' (blank) character to indicate that the lines are
continued from the previous line.
<Palette group >:
Palette group number. 0 ... 9.
<xpos,ypos >:
Position of object (from object mark 0). Max 256
positions are described on KISS/GS2. (- >7 KISS/GS
Ranks) '*' means a non-existent object.
Example:
|
|$2 192,11 * 56,176 55,21 259,62 15,24 375,63
|$3 43,115 154,62 372,108 253,156 * * * 165,207
| * 162,198 * 119,56 152,44 * * *
| 16,355 394,362 108,355 * * * 125,261
|$0 192,11 * 56,176 55,21 259,62 15,24 375,63
|
+ _';'_ _ Comment_
Description: ; <Comment >
This line is ignored.
Future extension may determine how to include various
information of the data (title, author etc.) as comments.
+ Others
Reserved for extention.
6. _ Details of cel files _
Cel files have a 32-byte header.
offset size contents
+0 4B Identifier 'KiSS' ( 4Bh 69h 53h 53h )
+4 B Cel file mark ( 20h )
+5 B bits per pixel ( 4 or 8 )
+6 W Reserved
+8 W(L,H) Width ( 1 ... XMAX )
+10 W(L,H) Height ( 1 ... YMAX )
+12 W(L,H) x-offset ( 0 ... XMAX-1 )
+14 W(L,H) y-offset ( 0 ... YMAX-1 )
+16 16B Reserved
Caution: the reserved field must be filled with 0.
Cels of the same object are aligned at the top left corner.
X,y-offsets are the offsets from this alignment point.
+32... _Pixel data_
+ Pixel data order (4 bits/pixel)
One raster:
|| || ||
MSB LSB MSB LSB MSB LSB
| pix0 | pix1 | | pix2 | pix3 | | pix4 | pix5 | ......... | pixN |
If the width is odd, add a padding pixel of color 0. The number
of rasters is indicated in the height field.
+ Pixel data order (8 bits/pixel)
One raster:
|| || ||
MSB LSB MSB LSB MSB LSB
| pix0 | | pix1 | | pix2 | ... | pixN |
The number of rasters is indicated in the height field.
If the top 4-byte identifier is not 'KiSS', the file format is as
follows:
+0 W(L,H) Width
+2 W(L,H) Height
+4... Pixel data
4 bits/pixel. X and y-offset are 0. This is the conventional format.
7. _Palette file detail _
Palette files have a 32-byte header.
offset size contents
+0 4B Identifier 'KiSS' ( 4Bh 69h 53h 53h )
+4 B Palette file mark ( 10h )
+5 B bits per color ( 12 or 24 )
+6 W Reserved
+8 W(L,H) number of colors in one palette group ( 1 ... 256 )
+10 W(L,H) number of palette groups ( 1 ... 10 )
+12 W Reserved
+14 W Reserved
+16 16B Reserved
Caution: the reserved fields must be filled with 0.
+32... _Palette data_
+ Palette data order (12 bits = 4096 colors)
A color consists of 2 bytes. 4 bits each for red, green,
blue.
|| ||
MSB LSB MSB LSB
| rrrr | bbbb | | 0000 | gggg | ....
+ Palette data order (24 bit = 16,777,216 colors) A color
consists of 3 bytes. 8 bits each for red, green, blue.
|| || ||
MSB LSB MSB LSB MSB LSB
| rrrrrrrr | | gggggggg | | bbbbbbbb | ...
If the number of palette groups is less than 10, colors of the
remaining palette groups will be copied from Group 0.
If the top 4-byte identifier is not 'KiSS', the file format is as
follows:
+0... palette data
12 bits/color, 16 colors in a palette group, 10 groups. This is
the conventional format.
8. _ KISS/GS Ranks _
Rank size colors max cels
--------------- ---------- ---- ----------
KISS/GS1 640 x 400 16 128 ; KISS v2.24c
KISS/GS2 640 x 400 256 256 ; KISS v2.37
KISS/GS3 768 x 480 256 256 ; draft
KISS/GS4 768 x 480 256 512 ; draft
--------------- ---------- ---- ----------
These are just standards. When you implement a KISS program on your
system, you should support the maximum ability of the hardware.
When you create KISS data you are encouraged to consider these
ranks. The lower rank the data conforms to, the more users can
play with it. However, you don't have to conform it to
unnecessarily low ranks.
9. _ Appendix _
This document is in public domain. Send bug reports, questions,
comments and problems to _ UHD98984@pcvan.or.jp _ (yav). Thank
you.
_____________________________________________________________
_ ITO Takayuki <yuki@is.s.u-tokyo.ac.jp >
Graduate student, Department of Information Science,
Faculty of Science, University of Tokyo, Japan.
_