More documentation I don't want to lose and some code cleanup as I understand LZH better...er?
This commit is contained in:
@@ -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"></span></a>
|
||||
<a class="NoUnderline" href="https://www.linkedin.com/in/carlyman77" target="_blank"
|
||||
title="LinkedIn"><span class="Symbol"></span></a>
|
||||
<a class="NoUnderline" href="https://www.twitter.com/carlbelle" target="_blank" title="Twitter"><span
|
||||
class="Symbol"></span></a>
|
||||
<a class="NoUnderline" href="https://instagram.com/carlyman77" target="_blank" title="Instagram"><span
|
||||
class="Symbol"></span></a>
|
||||
<a class="NoUnderline" href="https://open.spotify.com/artist/2kEspNx7mSo6Oe8BWpkGpj" target="_blank"
|
||||
title="Spotify"><span class="Symbol"></span></a>
|
||||
<a class="NoUnderline" href="https://www.youtube.com/channel/UCEen0eKme7kKca_yDS7gDzw" target="_blank"
|
||||
title="SoundCloud"><span class="Symbol"></span></a>
|
||||
<a class="NoUnderline" href="https://soundcloud.com/carl-belle" target="_blank" title="SoundCloud"><span
|
||||
class="Symbol"></span></a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="/" title="Home">Home</a> »
|
||||
<a href="/Articles/Home" title="Articles">Articles</a> »
|
||||
<a href="/QSharp" title="QSharp">QSharp</a> »
|
||||
<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 => 10001001 a, b
|
||||
1 255 => 11111111 c, c
|
||||
2 255 => 11111111 c, c
|
||||
3 85 => 01010101 d, d, d, d
|
||||
4 90 => 01011010 d, d, e
|
||||
5 188 => 10111100 f, g
|
||||
6 222 => 11011110 h, i
|
||||
7 0 => 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><<span class="Type">HuffmanNode</span>> oValueHuffmanNodes;
|
||||
|
||||
<span class="Keyword">private</span> <span class="Type">List</span><<span class="Type">HuffmanNode</span>> BuildBinaryTree(<span
|
||||
class="Keyword">string</span> Value)
|
||||
{
|
||||
<span class="Type">List</span><<span class="Type">HuffmanNode</span>> oHuffmanNodes = GetInitialNodeList();
|
||||
|
||||
<span class="Comment">// Update node weights</span>
|
||||
Value.ToList().ForEach(m => oHuffmanNodes[m].Weight++);
|
||||
|
||||
<span class="Comment">// Select only nodes that have a weight</span>
|
||||
oHuffmanNodes = oHuffmanNodes
|
||||
.Where(m => (m.Weight > 0))
|
||||
.OrderBy(m => (m.Weight))
|
||||
.ThenBy(m => (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><<span class="Type">HuffmanNode</span>> oHuffmanNodes = BuildBinaryTree(sFileContents);
|
||||
|
||||
<span class="Comment">// Filter to nodes we care about</span>
|
||||
oValueHuffmanNodes = oHuffmanNodes
|
||||
.Where(m => (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><char, <span class="Keyword">string</span>> oCharToBinaryWordDictionary = <span
|
||||
class="Keyword">new</span> <span class="Type">Dictionary</span><char, <span
|
||||
class="Keyword">string</span>>();
|
||||
<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><<span class="Keyword">byte</span>> oByteList = <span class="Keyword">new</span> <span
|
||||
class="Type">List</span><<span class="Keyword">byte</span>>();
|
||||
<span class="Keyword">for</span> (<span class="Keyword">int</span> i = 0; i < 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 >= 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 > 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 < 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 < 8) && (i < (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 < 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 < 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><<span
|
||||
class="Type">HuffmanNode</span>> GetInitialNodeList()
|
||||
{
|
||||
<span class="Type">List</span><<span class="Type">HuffmanNode</span>> oGetInitialNodeList = <span
|
||||
class="Keyword">new</span> <span class="Type">List</span><<span class="Type">HuffmanNode</span>>();
|
||||
|
||||
<span class="Keyword">for</span> (<span class="Keyword">int</span> i = Char.MinValue; i < 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><<span
|
||||
class="Type">HuffmanNode</span>> 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><<span
|
||||
class="Type">HuffmanNode</span>> UpdateNodeParents(<span class="Type">List</span><<span
|
||||
class="Type">HuffmanNode</span>> Nodes)
|
||||
{
|
||||
<span class="Comment">// Assign parent nodes</span>
|
||||
<span class="Keyword">while</span> (Nodes.Count > 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 < iOperations; iOperation++, i += 2, j += 2)
|
||||
{
|
||||
<span class="Keyword">if</span> (j < 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 => (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 >= 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> </p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="HorizontalSpacer"></div>
|
||||
<div class="HorizontalSolidLine"></div>
|
||||
|
||||
<p align="center">Copyright © 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&uid=295&forum_id=35&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">© 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
Executable
+741
@@ -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>
|
||||
|
||||
Executable
+417
@@ -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.
|
||||
_
|
||||
|
||||
Reference in New Issue
Block a user