/* -----------------------------------------------------------------
Name: sflcomp.c
Title: Compression functions
Package: Standard Function Library (SFL)
Written: 1991/05/20 iMatix SFL project team
Revised: 1997/09/08
Copyright: Copyright (c) 1996-2000 iMatix Corporation
License: This is free software; you can redistribute it and/or modify
it under the terms of the SFL License Agreement as provided
in the file LICENSE.TXT. This software is distributed in
the hope that it will be useful, but without any warranty.
-------------------------------------------------------------------*/
#include "sflcomp.h" /* Function prototypes */
#include
/* Constants */
#define FLAG_COPY 0x80 /* For LZ/RLE compression */
#define FLAG_COMPRESS 0x40
/* Local function prototypes */
static byte get_match (const byte *source, word ptr, word source_size,
short *hash, word *size, short *pos);
/* ---------------------------------------------------------------------[<]-
Function: compress_block
Synopsis: Takes up to 64Kb of uncompressed data in Source, compresses
it using a fast LZ/RLE algorithm and places the result in Dest. The
compression technique is comparable to that used by Zip and such tools,
but less agressive. It is, however, fast enough to use in realtime.
Returns the size of the compressed data. To decompress the data, use
the expand_block() function.
---------------------------------------------------------------------[>]-*/
word
compress_block (
const byte *src,
byte *dst,
word src_size)
{
static short
Hash [4096];
short SymbolAddress;
word Key;
word Size;
byte Bit = 0;
word Command = 0;
word src_index = 0;
word dst_size = 3;
word HeaderIndex = 1;
dst [0] = FLAG_COMPRESS;
for (Key = 0; Key < 4096; Key++)
Hash [Key] = -1;
while ((src_index < src_size) && (dst_size <= src_size))
{
if (Bit > 15)
{
dst [HeaderIndex] = (byte) ((Command >> 8) & 0x00ff);
dst [HeaderIndex + 1] = (byte) ( Command & 0x00ff);
HeaderIndex = dst_size;
dst_size += 2;
Bit = 0;
}
for (Size = 1;; Size++)
if ((word) (src_index + Size) >= src_size
|| (src [src_index] != src [src_index + Size])
|| (Size >= 0x0fff))
break;
if (Size >= 16)
{
dst [dst_size++] = 0;
dst [dst_size++] = (byte) (((word) (Size - 16) >> 8) & 0x00ff);
dst [dst_size++] = (byte) ((Size - 16) & 0x00ff);
dst [dst_size++] = src [src_index];
src_index += Size;
Command = (Command << 1) + 1;
}
else
if (get_match (src, src_index, src_size,
Hash, &Size, &SymbolAddress) != 0)
{
Key = ((src_index - SymbolAddress) << 4) + (Size - 3);
dst [dst_size++] = (byte) ((Key >> 8) & 0x00ff);
dst [dst_size++] = (byte) (Key & 0x00ff);
src_index += Size;
Command = (Command << 1) + 1;
}
else
{
dst [dst_size++] = src [src_index++];
Command = (Command << 1);
}
Bit++;
}
Command <<= (16 - Bit);
dst [HeaderIndex] = (byte) ((Command >> 8) & 0x00ff);
dst [HeaderIndex + 1] = (byte) ( Command & 0x00ff);
if (dst_size > src_size)
{
for (dst_size = 0; dst_size < src_size; dst_size++)
dst [dst_size + 1] = src [dst_size];
dst [0] = FLAG_COPY;
return (src_size + 1);
}
return (dst_size);
}
/* ---------------------------------------------------------------------[<]-
Function: expand_block
Synopsis: Expands a block of data previously compressed using the
compress_block() function. The compressed block is passed in src;
the expanded result in dst. dst must be large enough to accomodate
the largest possible decompressed block. Returns the size of the
uncompressed data.
---------------------------------------------------------------------[>]-*/
word
expand_block (
const byte *src,
byte *dst,
word src_size)
{
word SymbolAddress;
word ChunkSize;
word Counter;
word Command = 0;
word src_index = 1;
word dst_size = 0;
byte Bit = 0;
if (src [0] == FLAG_COPY)
{
for (dst_size = 1; dst_size < src_size; dst_size++)
dst [dst_size - 1] = src [dst_size];
return (src_size - 1);
}
while (src_index < src_size)
{
if (Bit == 0)
{
Command = src [src_index++] << 8;
Command += src [src_index++];
Bit = 16;
}
if (Command & 0x8000)
{
SymbolAddress = (word) (src [src_index++] << 4);
SymbolAddress += (word) (src [src_index] >> 4);
if (SymbolAddress)
{
ChunkSize = (word) (src [src_index++] & 0x0f) + 3;
SymbolAddress = dst_size - SymbolAddress;
for (Counter = 0; Counter < ChunkSize; Counter++)
dst [dst_size++] = dst [SymbolAddress++];
}
else
{
ChunkSize = (word) (src [src_index++] << 8);
ChunkSize += (word) (src [src_index++] + 16);
for (Counter = 0; Counter < ChunkSize; Counter++)
dst [dst_size++] = src [src_index];
src_index++;
}
}
else
dst [dst_size++] = src [src_index++];
Command <<= 1;
Bit--;
}
return (dst_size);
}
/* -------------------------------------------------------------------------
* get_match -- local
*
* Finds a match for the bytes at the specified offset.
*/
static byte get_match (const byte *source, word ptr, word source_size,
short *hash, word *size, short *pos)
{
word hash_value;
hash_value = (word) (40543L * (long) ((((source [ptr] << 4) ^
source [ptr + 1]) << 4) ^
source [ptr + 2]) >> 4) & 0xfff;
*pos = hash [hash_value];
hash [hash_value] = ptr;
if ((word) ((*pos != -1) && ((ptr - *pos)) < 4096U))
{
for (*size = 0;; (*size)++)
if ((*size >= 18)
|| ((word) (ptr + *size) >= source_size)
|| (source [ptr + *size] != source [*pos + *size]))
break;
return (byte) (*size >= 3);
}
return (FALSE);
}
/* ---------------------------------------------------------------------[<]-
Function: compress_rle
Synopsis: Takes a block of uncompressed data in src, compresses
it using a RLE algorithm and places the result in dst. To decompress
the data, use the expand_rle () function. Returns the size of the
compressed data. The dst buffer should be 10% larger than the src
buffer. The src buffer must be at least src_size + 1 bytes long. It
may be modified. The compressed data contains these strings:
[01-7F][data...] String of uncompressed data, 1 to 127 bytes.
[83-FF][byte] Run of 3 to 127 identical bytes.
[80][len][byte] Run of 128 to 255 identical bytes.
[81][lo][hi][byte] Run of 256 to 2^16 identical bytes.
[82][len] Run of 3 to 255 spaces.
[00][len] Run of 3 to 255 binary zeroes.
---------------------------------------------------------------------[>]-*/
word
compress_rle (
byte *src,
byte *dst,
word src_size)
{
word
dst_size, /* Size of compressed data */
src_scan, /* Scan through source data */
run_end, /* Points to end of run of bytes */
length = 0; /* Size of the run or string */
byte
cur_byte, /* Next byte to process */
*header; /* Header of unpacked string */
Bool
have_run; /* TRUE when we have a run */
src_scan = 0; /* Start at beginning of source */
dst_size = 0; /* No output yet */
header = NULL; /* No open unpacked string */
while (src_scan < src_size)
{
cur_byte = src [src_scan++];
have_run = FALSE; /* Unless we find a run */
/* Three identical bytes signals the start of a run */
if (cur_byte == src [src_scan]
&& cur_byte == src [src_scan + 1]
&& (src_scan + 1 < src_size))
{
/* Stick-in a sentinel character to ensure that the run ends */
src [src_size] = !cur_byte;
run_end = src_scan; /* src_scan <= src_size */
while (src [run_end] == cur_byte)
run_end++;
have_run = TRUE;
if (header) /* If we have a previous unpacked */
{ /* string, close it */
*header = (byte) length;
header = NULL;
}
length = run_end - src_scan + 1;
src_scan = run_end;
}
if (have_run)
{
/* We compress short runs of spaces and nulls separately */
if (length < 256 && cur_byte == 0)
{
dst [dst_size++] = 0x00;
dst [dst_size++] = (byte) length;
}
else
if (length < 256 && cur_byte == ' ')
{
dst [dst_size++] = 0x82;
dst [dst_size++] = (byte) length;
}
else
if (length < 128)
{
dst [dst_size++] = (byte) length | 0x80;
dst [dst_size++] = cur_byte;
}
else
if (length < 256) /* Short run 128-255 bytes */
{
dst [dst_size++] = 0x80;
dst [dst_size++] = (byte) length;
dst [dst_size++] = cur_byte;
}
else /* Long run 256-2^16 bytes */
{
dst [dst_size++] = 0x81;
dst [dst_size++] = (byte) (length & 0xff);
dst [dst_size++] = (byte) (length >> 8);
dst [dst_size++] = cur_byte;
}
}
else
{
if (!header) /* Start new unpacked string if */
{ /* necessary */
header = &dst [dst_size++];
length = 0;
}
dst [dst_size++] = cur_byte;
if (++length == 127) /* Each string can be up to 127 */
{ /* bytes long (high bit cleared) */
*header = (byte) length;
header = NULL;
}
}
}
if (header) /* If we have a previous unpacked */
{ /* string, close it */
*header = (byte) length;
header = NULL;
}
return (dst_size); /* Return compressed data size */
}
/* ---------------------------------------------------------------------[<]-
Function: expand_rle
Synopsis: Expands a block of data previously compressed using the
compress_rle() function. The compressed block is passed in src; the
expanded result in dst. Dst must be large enough to accomodate the
largest possible decompressed block. Returns the size of the expanded
data.
---------------------------------------------------------------------[>]-*/
word
expand_rle (
const byte *src,
byte *dst,
word src_size)
{
word
dst_size, /* Size of expanded data */
src_scan, /* Scan through source data */
length; /* Size of the run or string */
byte
cur_byte; /* Next byte to process */
src_scan = 0;
dst_size = 0;
while (src_scan < src_size)
{
cur_byte = src [src_scan++];
/* 1 to 127 is uncompressed string of 1 to 127 bytes */
if (cur_byte > 0 && cur_byte < 128)
{
length = (word) cur_byte;
memcpy (dst + dst_size, src + src_scan, length);
src_scan += length;
dst_size += length;
}
else /* Run of 3 or more bytes */
{
switch (cur_byte)
{
case 0x00: /* Run of 3-255 zeroes */
length = src [src_scan++];
cur_byte = 0;
break;
case 0x82: /* Run of 3-255 spaces */
length = src [src_scan++];
cur_byte = ' ';
break;
case 0x80: /* Short run 128-255 bytes */
length = src [src_scan++];
cur_byte = src [src_scan++];
break;
case 0x81: /* Long run 256-2^16 bytes */
length = src [src_scan++];
length += src [src_scan++] << 8;
cur_byte = src [src_scan++];
break;
default: /* Run of 3 to 127 bytes */
length = cur_byte & 127;
cur_byte = src [src_scan++];
}
memset (dst + dst_size, cur_byte, length);
dst_size += length;
}
}
return (dst_size); /* Return expanded data size */
}
/* ---------------------------------------------------------------------[<]-
Function: compress_nulls
Synopsis: Similar to compress_rle(), but optimised towards compression
of binary zeroes. Use this when you are certain that the sparse areas
are set to binary zeroes. You must use expand_nulls () to decompress
a block compressed with this function. Returns the size of the
compressed data. The dst buffer should be 10% larger than the src
buffer. The src buffer must be at least src_size + 1 bytes long. It
may be modified. The compressed data contains these strings:
[01-7F][data...] String of uncompressed data, 1 to 127 bytes.
[82-FF] Run of 2 to 127 binary zeroes.
[81][80-FF] Run of 128 to 255 binary zeroes.
[80][lo][hi] Run of 256 to 2^16 binary zeroes.
[00][len][byte] Run of 4 to 255 identical bytes.
[00][00][lo][hi][byte] Run of 256 to 2^16 identical bytes.
---------------------------------------------------------------------[>]-*/
word
compress_nulls (
byte *src,
byte *dst,
word src_size)
{
word
dst_size, /* Size of compressed data */
src_scan, /* Scan through source data */
run_end, /* Points to end of run of bytes */
length = 0; /* Size of the run or string */
byte
cur_byte, /* Next byte to process */
*header; /* Header of unpacked string */
Bool
have_run; /* TRUE when we have a run */
src_scan = 0; /* Start at beginning of source */
dst_size = 0; /* No output yet */
header = NULL; /* No open unpacked string */
while (src_scan < src_size)
{
cur_byte = src [src_scan++];
have_run = FALSE; /* Unless we find a run */
/* Two identical bytes may signal the start of a run */
if (cur_byte == src [src_scan]
&& src_scan < src_size)
{
/* Stick-in a sentinel character to ensure that the run ends */
src [src_size] = !cur_byte;
run_end = src_scan; /* src_scan <= src_size */
while (src [run_end] == cur_byte)
run_end++;
/* A run is 4+ identical bytes or 2+ nulls */
if ((run_end - src_scan > 2) || cur_byte == 0)
{
have_run = TRUE;
if (header) /* If we have a previous unpacked */
{ /* string, close it */
*header = (byte) length;
header = NULL;
}
length = run_end - src_scan + 1;
src_scan = run_end;
}
}
if (have_run)
{
if (cur_byte == 0)
{
if (length < 128) /* 2-127 binary zeroes */
dst [dst_size++] = (byte) (length | 0x80);
else
if (length < 256) /* 128-256 binary zeroes */
{
dst [dst_size++] = 0x81;
dst [dst_size++] = (byte) length;
}
else /* 256-2^15 binary zeroes */
{
dst [dst_size++] = 0x80;
dst [dst_size++] = (byte) (length & 0xff);
dst [dst_size++] = (byte) (length >> 8);
}
}
else
if (length < 256) /* Short run 4-255 bytes */
{
dst [dst_size++] = 0x00;
dst [dst_size++] = (byte) length;
dst [dst_size++] = cur_byte;
}
else /* Long run 256-2^16 bytes */
{
dst [dst_size++] = 0x00;
dst [dst_size++] = 0x00;
dst [dst_size++] = (byte) (length & 0xff);
dst [dst_size++] = (byte) (length >> 8);
dst [dst_size++] = cur_byte;
}
}
else
{
if (!header) /* Start new unpacked string if */
{ /* necessary */
header = &dst [dst_size++];
length = 0;
}
dst [dst_size++] = cur_byte;
if (++length == 127) /* Each string can be up to 127 */
{ /* bytes long (high bit cleared) */
*header = (byte) length;
header = NULL;
}
}
}
if (header) /* If we have a previous unpacked */
{ /* string, close it */
*header = (byte) length;
header = NULL;
}
return (dst_size); /* Return compressed data size */
}
/* ---------------------------------------------------------------------[<]-
Function: expand_nulls
Synopsis: Expands a block of data previously compressed using the
compress_nulls() function. The compressed block is passed in src; the
expanded result in dst. Dst must be large enough to accomodate the
largest possible decompressed block. Returns the size of the expanded
data.
---------------------------------------------------------------------[>]-*/
word
expand_nulls (
const byte *src,
byte *dst,
word src_size)
{
word
dst_size, /* Size of expanded data */
src_scan, /* Scan through source data */
length; /* Size of the run or string */
byte
cur_byte; /* Next byte to process */
src_scan = 0;
dst_size = 0;
while (src_scan < src_size)
{
cur_byte = src [src_scan++];
/* 1 to 127 is uncompressed string of 1 to 127 bytes */
if (cur_byte > 0 && cur_byte < 128)
{
length = (word) cur_byte;
memcpy (dst + dst_size, src + src_scan, length);
src_scan += length;
dst_size += length;
}
else /* Run of 2 or more bytes */
{
switch (cur_byte)
{
case 0x00: /* Run of non-zero bytes */
length = src [src_scan++];
if (length == 0) /* Stored as double-byte */
{
length = src [src_scan++];
length += src [src_scan++] << 8;
}
cur_byte = src [src_scan++];
break;
case 0x80: /* 256-2^16 zeroes */
length = src [src_scan++];
length += src [src_scan++] << 8;
cur_byte = 0;
break;
case 0x81: /* 128 to 255 zeroes */
length = src [src_scan++];
cur_byte = 0;
break;
default: /* 2 to 127 zeroes */
length = cur_byte & 127;
cur_byte = 0;
}
memset (dst + dst_size, cur_byte, length);
dst_size += length;
}
}
return (dst_size); /* Return expanded data size */
}
/* ---------------------------------------------------------------------[<]-
Function: compress_bits
Synopsis: Similar to compress_rle(), but optimised towards compression
of sparse bitmaps. Use this when you are playing with large, sparse
bitmaps. You must use expand_bits () to decompress a block compressed
with this function. Returns the size of the compressed data. The dst
buffer should be 10% larger than the src buffer for worst cases. The
src buffer must be at least src_size + 1 bytes long. It may be
modified. The compressed data contains these strings:
[00-07] Single byte containing a bit in position 0 to 7.
[08-7F][data...] String of uncompressed data, 1 to 120 bytes.
[82-FF] Run of 1 to 126 binary zeroes.
[81][00-FD] Run of 127 to 380 binary zeroes.
[81][FE][len][byte] Run of 4 to 255 identical bytes.
[81][FF][lo][hi][byte] Run of 256 to 2^16 identical bytes.
[80][lo][hi] Run of 381 to 2^16 binary zeroes.
---------------------------------------------------------------------[>]-*/
word
compress_bits (
byte *src,
byte *dst,
word src_size)
{
word
dst_size, /* Size of compressed data */
src_scan, /* Scan through source data */
run_end, /* Points to end of run of bytes */
length = 0; /* Size of the run or string */
byte
cur_byte, /* Next byte to process */
*header; /* Header of unpacked string */
static byte
single_bits [256]; /* Bytes with one bit set */
static Bool
initialised = FALSE; /* First time flag */
/* The single_bits table provides a fast lookup for bytes with */
/* one bit set. The 'interesting' bytes are non-zero in the table */
/* where their value is the output code value (0-7) + 1. */
if (!initialised) /* First time? Initialise */
{
memset (single_bits, 0, 256);
single_bits [1] = 1;
single_bits [2] = 2;
single_bits [4] = 3;
single_bits [8] = 4;
single_bits [16] = 5;
single_bits [32] = 6;
single_bits [64] = 7;
single_bits [128] = 8;
}
src_scan = 0; /* Start at beginning of source */
dst_size = 0; /* No output yet */
header = NULL; /* No open unpacked string */
while (src_scan < src_size)
{
cur_byte = src [src_scan++];
/*- Look for 1 or more binary zeroes, and compress into a run -------*/
if (cur_byte == 0)
{
src [src_size] = 0xff; /* Stop with a sentinel */
run_end = src_scan; /* src_scan <= src_size */
while (src [run_end] == 0)
run_end++;
if (header) /* If we have a previous unpacked */
{ /* string, close it */
*header = (byte) length + 7;
header = NULL;
}
length = run_end - src_scan + 1;
src_scan = run_end;
if (length < 127) /* 1-126 binary zeroes */
dst [dst_size++] = (byte) (++length | 0x80);
else
if (length < 381) /* 127-380 binary zeroes */
{
dst [dst_size++] = 0x81;
dst [dst_size++] = (byte) length - 127;
}
else /* 381-2^16 binary zeroes */
{
dst [dst_size++] = 0x80;
dst [dst_size++] = (byte) (length & 0xff);
dst [dst_size++] = (byte) (length >> 8);
}
}
else
/*- Next, look for bytes with 1 bit set; we encode these as 1 byte --*/
if (single_bits [cur_byte]) /* Single bit value? */
{
dst [dst_size++] = single_bits [cur_byte] - 1;
if (header) /* If we have a previous unpacked */
{ /* string, close it */
*header = (byte) length + 7;
header = NULL;
}
}
else
/*- Next, look for a run of 4 or more identical (non-zero) bytes ----*/
if (cur_byte == src [src_scan]
&& cur_byte == src [src_scan + 1]
&& cur_byte == src [src_scan + 2]
&& (src_scan < src_size - 2))
{
src [src_size] = !cur_byte; /* Stick in a sentinel byte */
run_end = src_scan; /* src_scan <= src_size */
while (src [run_end] == cur_byte)
run_end++;
if (header) /* If we have a previous unpacked */
{ /* string, close it */
*header = (byte) length + 7;
header = NULL;
}
length = run_end - src_scan + 1;
src_scan = run_end;
if (length < 256) /* Short run 4-255 bytes */
{
dst [dst_size++] = 0x81;
dst [dst_size++] = 0xFE;
dst [dst_size++] = (byte) length;
dst [dst_size++] = cur_byte;
}
else /* Long run 256-2^16 bytes */
{
dst [dst_size++] = 0x81;
dst [dst_size++] = 0xFF;
dst [dst_size++] = (byte) (length & 0xff);
dst [dst_size++] = (byte) (length >> 8);
dst [dst_size++] = cur_byte;
}
}
else
/*- Lastly, compress unpackable strings into chunks of 120 bytes ----*/
{
if (!header) /* Start new unpacked string if */
{ /* necessary */
header = &dst [dst_size++];
length = 0;
}
dst [dst_size++] = cur_byte;
if (++length == 120) /* Each string can be up to 120 */
{ /* bytes long (high bit cleared) */
*header = (byte) length + 7;
header = NULL;
}
}
}
if (header) /* If we have a previous unpacked */
{ /* string, close it */
*header = (byte) length + 7;
header = NULL;
}
return (dst_size); /* Return compressed data size */
}
/* ---------------------------------------------------------------------[<]-
Function: expand_bits
Synopsis: Expands a block of data previously compressed using the
compress_bits() function. The compressed block is passed in src; the
expanded result in dst. Dst must be large enough to accomodate the
largest possible decompressed block. Returns the size of the expanded
data.
---------------------------------------------------------------------[>]-*/
word
expand_bits (
const byte *src,
byte *dst,
word src_size)
{
word
dst_size, /* Size of expanded data */
src_scan, /* Scan through source data */
length; /* Size of the run or string */
byte
cur_byte; /* Next byte to process */
src_scan = 0;
dst_size = 0;
while (src_scan < src_size)
{
cur_byte = src [src_scan++];
if (cur_byte < 8) /* Single bit in position 0 to 7 */
dst [dst_size++] = 1 << cur_byte;
else
if (cur_byte < 128) /* String of 1 to 120 bytes */
{
length = (word) cur_byte - 7;
memcpy (dst + dst_size, src + src_scan, length);
src_scan += length;
dst_size += length;
}
else /* Run of 1 or more bytes */
{
switch (cur_byte)
{
case 0x80: /* 381-2^16 binary zeroes */
length = src [src_scan++];
length += src [src_scan++] << 8;
cur_byte = 0;
break;
case 0x81:
length = src [src_scan++];
if (length == 0xFE) /* 4-255 non-zero bytes */
{
length = src [src_scan++];
cur_byte = src [src_scan++];
}
else
if (length == 0xFF) /* Run of 256-2^15 non-zero bytes */
{
length = src [src_scan++];
length += src [src_scan++] << 8;
cur_byte = src [src_scan++];
}
else
{
length += 127;
cur_byte = 0; /* 127 to 380 zeroes */
}
break;
default: /* 1 to 126 zeroes */
length = (cur_byte - 1) & 127;
cur_byte = 0;
}
memset (dst + dst_size, cur_byte, length);
dst_size += length;
}
}
return (dst_size); /* Return expanded data size */
}