Commissioned by Gosteleradio
ZXNet echo conference «code.zx»
From Vitaly Vidmirov → To All 2 November 1998
Hello and prosperity to you, All!
After a long sleep I will finally deliver what I promised
xx-twenty years ago ;)
If anyone has already forgotten, then this “that” is nothing more than
lzw algorithm in docs and examples.
I won’t throw out any s-s-sorts (there’s a lot of text, but there’s little sense),
I'll throw in a couple of och. simple examples on ASMA.
The dockets go immediately after this letter.
Here are only brief algorithms and my art.
Written for demonstration purposes only,
therefore completely devoid of real application.
Notes on the examples:
1-data size #00-#0F
2-dictionary size 256 sequences (8 bits)
3-data length <256 bytes
No 4-packing is performed on the bitstream.
Brief algorithms:
=========8<==Cut=Here==8<=============
LZW Compression/Decompression
ROUTINE LZW_COMPRESS
STRING=get input character
WHILE there are still input characters DO
CHARACTER=get input character
IF STRING+CHARACTER is in the string table THEN
STRING=STRING+CHARACTER
ELSE
output the code for STRING
add STRING+CHARACTER to the string table
STRING=CHARACTER
END of IF
END of WHILE
output the code for STRING
ROUTINE LZW_DECOMPRESS
read OLD_CODE
output OLD_CODE
WHILE there are still input characters DO
read NEW_CODE
IF NEW_CODE is not in the translation table THEN
STRING=get translation of OLD_CODE
STRING=STRING+CHARACTER
ELSESTRING=get translation of NEW_CODE
END of IF
output STRING
CHARACTER=first character in STRING
add OLD_CODE+CHARACTER to the translation table
OLD_CODE=NEW_CODE
END of WHILE
=========8<==Cut=Here==8<=============
Actually the programs. Written in Storm
Was not written for the purpose of demonstrating elegant
code, so sorry for the crookedness ;)
=========8<==Cut=Here==8<=============
;LZW algos test Dark/X-Trade'98
;Click the sequence of unpacked
;tetrads into 8-bit codes (fixed size
;van). Very slowly...
;- COMPRESSOR -------------
CHRTB EQU #7000;Packaging Dictionary
PFXTB EQU CHRTB+256;Prefixes
;- EXPANDER ---------------
TRNTB EQU #9000;Dictionary for unpacking
;;PFXTB EQU TRNTB+256;...
TRASHBF EQU #9200;Buffer for string
;---------------------------------------
; All ^^^ buffers are 256 bytes
;Dictionaries have the same format, therefore
;spaced for clarity only.
ORG #6000
LD HL,TEXT; from where we click
LD DE,#8000;where we put it
LD B,TEXTSZ
CALL LZW8CM
EXX
LD HL,-#8000
ADD HL,DE; HL=shrink length
LD B,L
LD HL,#8000;from where click
LD DE,#A000;where we put it
CALL LZW8EX
RET
DW 0
;COMPRESSOR. HL=FROM; DE=TO; B=LEN
;---------------------------------------
LZW8CM INC B
EXX
;Initializing the dictionary.LD A,17:LD (LASTIDX),A
DEC A
;16-используется как пустой префикс.
LD HL,CHRTB
CALL SETDICT
CALL GETC:LD D,A; GET STRING
CM2 CALL GETC:LD E,A; GET CHAR
JR C,CMEOF
CALL FINDSTR:JR C,CM3
LD D,L:JR CM2; FND:PFX=STR+CHAR
CM3 CALL PUTCD; NOTFND:OUTCODE 4 STR
LD HL,LASTIDX; STR+CHR->DICT
LD A,(HL)
INC (HL)
LD L,A
LD H,CHRTB[
LD (HL),E:INC H; CHAR
LD (HL),D; PREFIX
LD D,E
JR CM2
CMEOF CALL PUTCD
RET
;EXPANDER. HL=FROM; DE=TO; B=LEN
;---------------------------------------
LZW8EX INC B
EXX
LD A,17:LD (NXTCODE),A
LD HL,TRNTB
XOR A; PREFIX
CALL SETDICT
CALL GETC:LD D,A; OLDCODE
CALL PUTC
EX1 CALL GETC:LD E,A; NEWCODE
JR C,EXEOF
NXTCODE EQU $+1
CP 0
JR C,EX4; STRING IN DICT
EX3 LD A,C
LD BC,TRASHBF+255
LD (BC),A
LD L,D; STRING=OLDCODE+CHAR
JR EX5
EX4 LD BC,TRASHBF+256
LD L,E; STRING=NEWCODE
EX5 LD H,TRNTB[
CALL EXPSTR
LD HL,TRASHBF+256:OR A:SBC HL,BC
LD A,(BC):EXA
CALL COPYSTR; OUTPUT STRING
EXA ; OLDCODE+CHAR->DICT
LD HL,(NXTCODE):LD H,TRNTB[
LD (HL),A:INC H
LD (HL),D
LD C,A; C=CHARLD A,L:INC A:LD (NXTCODE),A
LD D,E
JR EX1
EXEOF RET
;---------------------------------------
;HL=DICT; BC=BUFF
EXPSTR LD A,(HL):INC H;стринг->буффер
LD L,(HL):DEC H
DEC BC:LD (BC),A
INC L:DEC L:JR NZ,EXPSTR
RET
;BC=SRC; DE'=DST; HL=LEN
COPYSTR LD A,(BC):INC BC;буфер->out
CALL PUTC
DEC HL
LD A,H:OR L:JR NZ,COPYSTR
RET
;E=CHAR; D=PREFIX => NC-> L=NEWPREFIX
FINDSTR LD HL,CHRTB!#FF
LASTIDX EQU $+1
LD BC,0
INC BC
FNDS INC L
LD A,E
CPIR:SCF:RET PO
INC H:DEC L
LD A,(HL)
DEC H
CP D
JR NZ,FNDS
RET
;- SHARED ROUTINES ---------------------
GETC EXX
OR A
LD A,(HL):INC HL
DJNZ GETC1
SCF
GETC1 EXX
RET
PUTCD LD A,D; PREFIX
PUTC EXX
LD (DE),A:INC DE
EXX
RET
;HL=DICT; A=PREFIX
SETDICT ;;LD HL,CHRTB
LD B,16
SDCT0 LD (HL),L:INC H
LD (HL),A:DEC H
INC L
DJNZ SDCT0
XOR A
SDCT1 LD (HL),A:INC H
LD (HL),A:DEC H
INC L:JR NZ,SDCT1
RET
;- DATA --------------------------------
TEXT DB #0A,#0B,#0C,#0A,#0B,#0C
DB #0A,#0B,#0C,#0A,#0B,#0C
DB #0A,#0B,#0C,#0A,#0B
TEXTSZ EQU 17
=========8<==ОтРеЗаТь=ЗДеСь==8<=============
to be continued...evil Vitalik AKA Dark / X-Trade
From Vitaly Vidmirov → To All 3 November 1998
Hello and prosperity to you, All!
=========8<==Cut=Here==8<=============
- 1 -
Steve Blackstock
EXPLANATION OF LZW AND GIF
I hope this little document helps enlighten
those who want to know a little more about the Lempel-Ziv compression algorithm
Welch and, specifically, about its implementation for the GIF format.
Before we begin, a little about terminology in the world
of this document:
"Symbol": fundamental data element. In regular text
in files this is a separate byte. In raster images,
you are interested in, this is the index that
indicates the color of a given pixel. I will refer to
arbitrary character like "K".
"Character stream": A character stream such as a data file.
"Chain": several consecutive characters. Chain length
can vary from 1 to a very large number of characters. I
I can specify an arbitrary chain as "[...]K".
"Prefix": almost the same as a chain, but it is implied
that the prefix immediately precedes the character, and
the prefix can have zero length. I will refer to
an arbitrary prefix, as in "[...]".
"Root": a single-character string. For most purposes this is simplesymbol, but sometimes it can be different. This is [...]K, where
[...] empty.
"Code": a number defined by a known number of bits that
encodes the chain.
"Code Stream": Output stream of codes such as "raster
data".
"Element": code and its chain.
"Chain table": a list of elements usually, but not necessarily,
unique.
This should be enough to understand the document.
LZW is a data compression method that extracts
benefits for repetitive data chains. Since
raster data usually contains quite a lot of such repetitions,
LZW is a good method for compressing and expanding them.
For now, let's look at regular coding and
decoding using the LZW algorithm. GIF uses variation
this algorithm.
When shrinking and expanding, LZW manipulates three objects:
a stream of symbols, a stream of codes and a table of strings. When compressed
the stream of symbols is the input and the stream of codes is the output. When
expansion, the input is a stream of codes, and the stream of symbols is
on days off. The string table is generated by both compression and
opening, but it is never transferred from compression to opening
and vice versa.
.
- 2 -
The first thing we do with LZW compression isinitializing our character string. To do this, we
you need to select a size code (number of bits) and know how many
possible meanings our symbols can take. Let's put
code size equal to 12 bits, which means it can be memorized
0FFF, or 4096, elements in our string table. Let's also
Let's say we have 32 possible different symbols. (This
corresponds, for example, to a picture with 32 possible colors for
each pixel.) To initialize the table, we will set
matching code #0 to character #0, code #1 to character #1, etc., until
code #31 and symbol #31. In fact, we indicated that each code
0 to 31 is the root. There will be no others in the table
codes that have this property.
Now we will start data compression. Let's first define
something called the "current prefix". This prefix we will
constantly remember and compare with him here and in
further. I will refer to it as "[.c.]". Initially current
the prefix contains nothing. Let's also define "current
chain" which is formed by the current prefix and the next
character in a stream of characters. I will denote the current chain as
"[.c.]K", where K is some character.
Now look at the first character in the character stream. Let's callits P. Let's make [.c.]P the current chain. (At this point it is
of course, the root is P.) Now let's search the string table to
determine whether [.c.]P is included in it. Of course now it is
will happen because our table was initialized
all roots are placed. In this case we do nothing. Now
make the current prefix [.c.]P.
We take the next symbol from the stream by symbol. Let's call him Q.
Let's add the current prefix to form [.c.]Q, i.e. current
chain. We search the chain table to determine
does it include [.c.]Q. In this case, this, of course, will not happen.
Yeah! Now we need to do something. Let's add [.c.]Q
(which in this case is PQ) to the chain table under code #32,
and output the code for [.c.] to the code stream. Now let's start again with
current prefix corresponding to the root P. Continue
adding characters to [.c.] to form [.c.]K, until
until we can find [.c.]K in the string table. Then
print the code for [.c.] and add [.c.]K to the string table. On
In pseudo code, the algorithm will be described approximately like this:
[1] Initialize the string table;
[2] [.c.] <- empty;
[3] K <- next character in the character stream;
[4] Is [.c.]K included in the string table?
(yes: [.c.] <- [.c.]K;
go to [3];
)(none: add [.c.]K to the string table;
output code for [.c.] to code stream;
[.c.] <- K;
go to [3];
)
.
- 3 -
How simple it is! Of course, when we do step [3] and
there are no more characters left in the input stream, you output the code for
[.c.] and leave the table. Everything is done.
Want an example? Let's assume that we have
4-character alphabet: A,B,C,D. The character stream looks like
ABACABA. Let's squeeze it. First we initialize our
chain table: #0=A, #1=B, #2=C, #3=D. The first character is A,
which is included in the string table, therefore [.c.] becomes
equal to A. Next we take AB, which is not included in the table,
hence we output code #0 (for [.c.]), and add AB to
table of chains with code #4. [.c.] becomes equal to B. Next we
take [.c.]A = BA, which is not included in the string table,
therefore we output code #1, and add BA to the table of chains with
code #5. [.c.] becomes equal to A. Next we take AC, which is not
included in the chain table. We output code #0 and add AC to the table
chains with code #6. Now [.c.] is equal to C. Next we take [.c.]A =
CA that is not included in the table. Print #2 for C, and add CA
to the table under code #7. Now [.c.]=A. Next we take AB, whichIS INCLUDED in the string table, therefore [.c.] becomes equal
AB, and we are looking for ABA, which is not in the string table, so we
print the code for AB, which is #4, and add ABA to the table
chains under code #8. [.c.] is equal to A. We can no longer take
characters, so we print code #0 for A and call it a day.
Therefore, the code stream is #0#1#0#2#4#0.
A few words (three) should be said about efficiency:
use a hashing strategy. Searching the string table can
be associated with significant computation and hashing
significantly reduces these costs. Please note that "direct
LZW" compression works with the risk of string table overflow -
the result is a code that cannot be represented by the number of bits,
previously set for codes. There are several ways to
to deal with this problem and GIF implements the most
the simplest one. We will do the same.
An important point to note is
is that at any point during compression is performed
condition: if [...]K is included in the table of strings, then [...] also
enters it. This circumstance leads to an effective method
memorizing chains in a table. Instead of memorizing
table the entire chain, use the fact that any chain canbe represented as a prefix plus the symbol: [...]K. If you deposit
[...]K into the table, you know what [...] is already in it, and
so you can remember the code for [...] plus the trailing character
K.
That's all you need to worry about when compressing. Disclosure,
perhaps more complex conceptually, but software implementation
its simpler.
.
=========8<==Cut=Here==8<=============
evil Vitalik AKA Dark / X-Trade
From Vitaly Vidmirov → To All 3 November 1998
Hello and prosperity to you, All!
=========8<==Cut=Here==8<=============
- 4 -
Let's describe how this is done. We start again with initialization
chain tables. This table is formed based on the knowledge
which we have about the flow that is ultimately generated
symbols, for example, about the possible meanings of symbols. In GIF files
this information is in the header, as the number of possible values
pixels However, the beauty of LZW is that that's all we need
necessary. The compression was done in such a way that we never
we will encounter code in the code stream that we could not convert
in a chain.
We need to define something called "current code"
which we will refer to as "
", and "old code", to
which we will refer to as "". To start unpacking
Let's take the first code. It now becomes . This code will be
initialize the chain table as root. We output
root to character stream. Let's make this code the old code .
(*) Now take the following code and assign it to .
It's possible that this code isn't part of the chaining table, but let's
For now let's assume that it is there. We output the chain,
the corresponding into the character stream. Now let's find the first onecharacter in the chain you just received. Let's call him K.
Let's add it to the prefix [...] generated by ,
to get a new chain [...]K. Let's add this chain to the table
chains and set the old code equal to the current code .
Repeat from the place that I marked with an asterisk and you all
you will do it. Read this paragraph again if you only
"we ran" through it!!!
Now let's consider the possibility that is not
included in the chain table. Let's go back to compression and try
understand what happens if a chain appears in the input stream
type P[...]P[...]PQ. Let us assume that P[...] is already in
table, but P[...]P - no. The encoder will perform grammar
parses P[...], and finds that P[...]P is not in the table. This
will output the code for P[...] and add P[...]P to the table
chains. Then it will take P[...]P for the next chain and
will determine that P[...]P is in the table and produce an output code for
P[...]P, if it turns out that P[...]PQ is missing from the table.
The decoder is always "one step behind"
encoder. When the decoder sees the code for P[...]P, it doesn't
will add this code to his table right away because he needs
initial character P[...]P to be added to the chain for the lastcode P[...] to generate the code for P[...]P. However, when
the decoder will find a code that is not yet known to him, he will always
will be 1 more than the last one added to the table.
Therefore, he can guess that the chain for this code is
must be and, in fact, always will be correct.
If I'm a decoder, and I saw code #124, and my table
chains contains the last code only with #123, I can assume that
the code with #124 should be there, add it to my string table and
output the chain itself. If code #123 generates a chain that
I will refer here as a prefix [...], then code #124 in this special
case there will be [...] plus the first character [...]. So I have to
add the first character [...] to itself. Not so bad.
.
- 5 -
As an example (quite common), let's
Let's assume that we have a raster image in which the first
three pixels have the same color. Those. my character stream
looks like: QQQ.... To be specific, let's say that we
we have 32 colors and Q corresponds to color #12. The encoder will generate
code sequence 12,32,.... (if you don’t understand why,
take a minute to understand.) Recall that code #32 is not included in
initial table, which contains codes #0 to #31.The decoder will see code #12 and translate it as color Q. It will then
will see code #32, the meaning of which he does not yet know. But if he
thinks about it long enough, he will be able to understand that QQ should
be element #32 in the table and QQ must be the next chain
output.
Thus, the pseudo decoding code can be represented
as follows:
[1] Initialize string string;
[2] take the first code: ;
[3] output the string for to a character stream;
[4] = ;
[5] <- next code in the code stream;
[6] does exist in the string table?
(yes: output the string for to a character stream;
[...] <- broadcast for ;
K <- first translation character for ;
add [...]K to the string table;
<- ;
)
(no: [...] <- broadcast for ;
K <- first character [...];
output [...]K to a character stream and append it to
it to the table of chains;
<-
)
[7] go to [5];
Again, if you find in step [5] that there is no more
characters, you must complete. Deriving chains and finding
the initial characters in them pose problems in themselves
effectiveness, but I'm not going to suggest ways tosolutions. Half the fun of programming is
allowing such things!
.
- 6 -
---
And now GIF variations on this theme. In the header part
In a GIF file, there is a field called in the raster data stream
"size code". This is a very confusing name for this field,
but we must come to terms with it. In fact, this is the "root size".
Actual size (in bits) of compression codes in reality
changes during the shrink/expand process and I'll refer to it
here as in "compression size".
The initial table, as usual, contains codes for all roots,
but two special codes are added to the top of it.
Let's say we have a "code size" which is usually a number
bits per pixel. Let's denote it N. If the number of bits per pixel is
1, N must equal 2: the roots occupy cells #0 and #1 in the initial
table and two special codes will occupy cells #4 #5. B
in any other case N is equal to the number of bits per pixel, the roots occupy
cells #0 to #(2**N-1), and special codes are (2**N) and (2**N
+ 1).
The initial compression size will be N+1 bits per code. If you
when encoding, you first output codes of length (N+1) bits and,
if you are decoding, you first select (N+1) bits from
code stream. The special codes used are: orclear code equal to (2**N) and or end of information equal to
(2**N + 1). tells the encoder what to do again
initialize the chain table and reset the compression size
equal to (N+1). means there are no more codes. If you
encoding or decoding, you must start adding
elements into the chain table with + 2. If you enter
encoding, you should output as the very first
code, and then again every time you reach the code
#4095 (hex FFF) because GIF does not allow size
compression greater than 12 bits. If you are conducting a disclosure, you should
reinitialize your chain table as soon as you find
.
Variable compression size doesn't really add much value
hassle. If you are encoding you start with a compression size of
(N+1) bits, and once you output the code (2**(compression size)-1),
you increase the compression size by one bit. Therefore,
the next code in your output will be one bit longer. Remember
that the largest compression size is 12 bits, which corresponds to
code 4095. If you reach this limit, you should output
as the following code and start over. If you are driving
decoding, you should increase your compression size AS SOON AS YOUwrite element #(2**(compression size) - 1) to the string table.
The next code you READ will be one bit longer. Not
make mistakes by waiting until you need to add to the table
code (2**compression size). You already missed a bit from the last code.
Packing codes into a bitstream of raster data is also
is a potential stumbling block for newbies
encoding and decoding. The least significant bit of the code must match
The least significant available bit of the first available byte in the code stream.
For example, if you started with a 5-bit compression code, and your three
the first codes, say, , , , where e, j, and o
bits #0, your code stream will start like:
.
- 7 -
byte#0: hijabcde
byte#1: .klmnofg
So the difference between regular LZW and LZW for GIF
consist in the presence of two additional special codes and
variable compression size. If you understood LZW and you understood these
differences, you understand everything!
As a P.S. You may have noticed that the encoder has
little bit flexibility during compression. I described "greedy"
a method that selects so many characters before printing the code,
as far as possible. In fact, this method is
standard for LZW and results in the best compression ratio.However, there is no rule that prohibits you
stop and print the code for the current prefix, regardless
on whether it is already in the table or not, and add this chain
plus the next character to the string table. There are various
reasons to want to do this, especially if the chain
is too long and causes hashing difficulties. If this is for you
need, do it.
I hope this helps you.
Steve Blackstock
Steve Blackstock was helped to speak Russian by an employee
Institute of Applied Mathematics AH CCCP A. Samotokhin
=========8<==Cut=Here==8<=============
employee of the Institute of Applied Mathematics AH CCCP A. Samotokhin
helped will appear in the echo
sleepy-evil Vitalik AKA Dark / X-Trade