幻想森林

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 5150|回复: 17

[通用编程] 12bit LZW压缩算法的实现问题

[复制链接]

23

主题

218

帖子

2470

积分

⑥精研

积分
2470
发表于 2007-12-30 20:31:54 | 显示全部楼层 |阅读模式
自己根据Wikipedia上的算法框架东拼西凑,外加参考了某些游戏引擎写了个实现,勉强能运行不过速度实在是!@$#%……请问有没有现成的实现可以参考?或者能对我的算法给点意见吗?

压缩算法:
  1. int *build_next_table(const unsigned char *s, int n, int *t)
  2. {
  3.     int i = 2;
  4.     int j = 0;
  5.     t[0] = -1;
  6.     t[1] = 0;
  7.     while (i < n)
  8.     {
  9.         if (s[i-1] == s[j])
  10.             t[i++] = ++j;
  11.         else if (j > 0)
  12.             j = t[j];
  13.         else
  14.             t[i++] = 0;
  15.     }
  16.     return t;
  17. }
  18. int kmp_match_mask(const unsigned char *s, int ls, int *t, const unsigned char *dic, int dic_start, int dic_pos, size_t dic_mask, int *match_len)
  19. {
  20.     int m = dic_start;
  21.     int i = 0;
  22.     int max_l = 0;
  23.     int max_ptr = -1;
  24.     build_next_table(s, ls, t);
  25.     do
  26.     {
  27.         if (s[i] == dic[(m+i) & dic_mask] && ((m+i) & dic_mask) != dic_pos)
  28.         {
  29.             i++;
  30.             if (i > max_l)
  31.             {
  32.                 max_l = i;
  33.                 max_ptr = m;
  34.                 if (max_l == ls)
  35.                     break;
  36.             }
  37.         }
  38.         else
  39.         {
  40.             m = (m + i - t[i]) & dic_mask;
  41.             if (i>0) i = t[i];
  42.         }
  43.     } while (((m+i) & dic_mask) != dic_start);
  44.     *match_len = max_l;
  45.     return max_ptr;
  46. }
  47. size_t LZW_Compress(const unsigned char *src, unsigned char *dest, size_t size)
  48. {
  49.     size_t src_off = 0;
  50.     size_t dest_off = 0;
  51.     size_t dic_pos = 0xFEE;
  52.     int dic_size = 18;
  53.     unsigned char dic[0x1000];
  54.     unsigned char *out_buf;
  55.     int out_pos, s_len;
  56.     int i,j;
  57.     int next_table[18];
  58.     int match_len, dic_ref;
  59.     memset(dic, 0, 0x1000);
  60.     while (src_off < size)
  61.     {
  62.         out_buf = dest+dest_off;
  63.         out_buf[0] = 0;
  64.         out_pos = 1;
  65.         for (i=0; i<8; i++)
  66.         {
  67.             s_len = size-src_off;
  68.             if (s_len <= 0) break;
  69.             if (s_len > 18) s_len = 18;
  70.             dic_ref = kmp_match_mask(src+src_off, s_len, next_table, dic, 0xFED, dic_pos, 0xFFF, &match_len);
  71.             
  72.             if (match_len < 3)
  73.             {
  74.                 out_buf[0] |= (1 << i);
  75.                 out_buf[out_pos++] = dic[dic_pos++] = src[src_off++];
  76.                 dic_pos &= 0xFFF;
  77.                 if (dic_size < 0x1000) dic_size++;
  78.             }
  79.             else
  80.             {
  81.                 out_buf[out_pos++] = dic_ref & 0xFF;
  82.                 out_buf[out_pos++] = ((dic_ref & 0xF00) >> 4) | ((match_len - 3) & 0xF);
  83.                 for (j=0; j<match_len; j++)
  84.                 {
  85.                     dic[dic_pos++] = src[src_off++];
  86.                     dic_pos &= 0xFFF;
  87.                     if (dic_size < 0x1000) dic_size++;
  88.                 }
  89.             }   
  90.         }
  91.         dest_off += out_pos;
  92.     }
  93.     return dest_off;
  94. }
复制代码
以上算法是根据Wikipedia上面的框架自己写的,字符串匹配算法用了KMP。


解压算法:
  1. size_t LZW_Decompress(const unsigned char *src, unsigned char *dest, size_t src_size, size_t dest_size)
  2. {
  3.     size_t src_off = 0;
  4.     size_t dest_off = 0;
  5.     size_t dic_pos = 0xFEE;
  6.     unsigned char dic[0x1000];
  7.     size_t var_1010 = 0;
  8.     size_t var_1008;
  9.    
  10.     memset(dic, 0, 0x1000);
  11.    
  12.     while (src_off < src_size)
  13.     {
  14.         var_1010 >>=1;
  15.         if ((var_1010 & 0x0100) == 0)
  16.         {
  17.             var_1010 = src[src_off++] | 0xFF00;
  18.         }
  19.         if ((var_1010 & 0x0001) != 0)
  20.         {
  21.             if (dest_off >= dest_size) return dest_off;
  22.             dest[dest_off++] = dic[dic_pos++] = src[src_off++];
  23.             dic_pos &= 0xFFF;
  24.         }
  25.         else
  26.         {
  27.             size_t eax;
  28.             size_t i;
  29.             eax = src[src_off] | ((src[src_off+1] & 0xF0) << 4);
  30.             src_off++;
  31.             var_1008 = (src[src_off++] & 0x0F) + 2;
  32.             for (i=0; i<=var_1008; i++)
  33.             {
  34.                 if (dest_off >= dest_size) return dest_size;
  35.                 dest[dest_off++] = dic[dic_pos++] = dic[(eax+i)&0xFFF];
  36.                 dic_pos &= 0xFFF;
  37.             }
  38.         }
  39.     }
  40.     return dest_off;
  41. }
复制代码
以上是参考(可以算是抄了)某引擎的,这个应该没啥问题……
ONScripter for PSP/Windows中文版 http://blog.163.com/john_he_
回复

使用道具 举报

8

主题

215

帖子

2223

积分

⑥精研

积分
2223
发表于 2007-12-31 05:19:53 | 显示全部楼层
好神奇...参考LZW的框架实现了LZSS?
不行,还是觉得好神奇.LZ77/LZSS与LZ78/LZW唯一的相似点就是它们都是字典系的压缩算法,除此之外它们对在滑动窗口(字典)里保存什么和如何构造字典的方式上都很不同...

真正的12bit LZW是Welch的论文里提到的一种.用12位的代码(code word)来指向字典中的项,每项一个字节(8位).字典也就相应的有4096项(2^12).字典中的前256项依次被初始化为0-255,后面的3840项用来记录新的前缀.

而囧大这里碰到的是LZSS.使用16位的代码来指定一个长度为4096的滑动窗口(字典)中的字串.这个16位代码的构成是12位的指向字典的指针+4位的要复制的长度.编码后,每8个单元数据前都会有1字节的标志,用于记录后面的单元到底是一个8位的原数据(raw byte)还是一个16位的代码.字典一般以0x00填充作为初始化,也有用别的更常见的数据初始化的可能(但字典里每个字节的初始值都一样)

静态观察数据时,我一般是靠0xFF标记来辨认LZSS的,因为如果有连续的8字节都是原数据的话,前面就会有一个0xFF的标志,相当好辨认.如果是分析程序代码,我一般会找找0xFEE,如果是用了4096长度的字典LZSS的话,这0xFEE基本上少不了.

LZSS的代码发在CK那帖里,这边不重复发了
回复 支持 反对

使用道具 举报

50

主题

742

帖子

402

积分

版主

自定义头衔

Rank: 7Rank: 7Rank: 7

积分
402
发表于 2007-12-31 08:22:07 | 显示全部楼层
究竟怎样是压缩的,偶只知道用HUFFMAN建树,然后保留到各个复现概率高的高度值-以上
Style-C
回复 支持 反对

使用道具 举报

136

主题

1751

帖子

548

积分

版主

Rank: 7Rank: 7Rank: 7

积分
548
发表于 2007-12-31 11:13:16 | 显示全部楼层
lzss也应该是比较快的吧?

ps: john原来可以谐音成囧 [s:25]
え~え~お!!!
回复 支持 反对

使用道具 举报

8

主题

215

帖子

2223

积分

⑥精研

积分
2223
发表于 2007-12-31 14:32:37 | 显示全部楼层
LZSS里的滑动窗口(字典)被视为一个循环数组。它的任务就是把最近出现过的在最小匹配长度以上,最大匹配长度以下的所有字串给记录下来(就像是在输入流上的“滑动窗口”一般)。在经典的LZSS里,使用16位的代码来记录到底要从字典的什么位置(12位指针)复制多大长度(4位数字)的内容到输出流,这样最小的匹配长度就是>2,而最大匹配长度是(2^4+2=18)。因为指针有12位,所以字典里有4096(=2^12)项,每项一个字节。

具体的算法随便找找都有,这里就不多说了。代码我发在了CK那帖:
http://bbs.sumisora.com/read.php?tid=10875708
没注册的可能得先注册登录才进得去 OTL
回复 支持 反对

使用道具 举报

136

主题

1751

帖子

548

积分

版主

Rank: 7Rank: 7Rank: 7

积分
548
发表于 2007-12-31 14:56:02 | 显示全部楼层
可以直接看到,虽然N年在那注册过,现在连注册名是什么都想不起来了。
果然贴的那个很经典的C源文件。89年的...

lzss,lzw虽然是无失真&高速,可也有个压缩率不是很高的问题。
一般来说,还有什么比较简单且高速的算法来配合一下?(无损的)

现在我的作法是把BMP的文件头改造了一下,后面的具体数据只是简单的用了下zlib。
[s:5]
え~え~お!!!
回复 支持 反对

使用道具 举报

23

主题

218

帖子

2470

积分

⑥精研

积分
2470
 楼主| 发表于 2007-12-31 15:26:47 | 显示全部楼层
好神奇...参考LZW的框架实现了LZSS?
不行,还是觉得好神奇.LZ77/LZSS与LZ78/LZW唯一的相似点就是它们都是字典系的压缩算法,除此之外它们对在滑动窗口(字典)里保存什么和如何构造字典的方式上都很不同...
好丢人……因为我是算法小白……

我这个解压算法是跟着DK4跟出来的,然后看雪上有位仁兄说破过ELF的花与蛇,说这是LZW,我看过代码也差不多,就认为是LZW了= =|||(诅咒那家伙~~~~)

而压缩算法是看了Wikipedia LZW那页之后写的,说实在Wikipedia上那页根本就没详细说明,于是剩下的就靠在下脑内YY,根据压缩算法简单逆推过来。在最关键的查字典上面是用KMP的,我想这个就是慢的原因,因为实在没有研究到用树……

至于算法辨认,连算法都不认识就不要说辨认了……俺分析程序唯一能说出名字就是SAYA的算法,不过不是看算法,是看到了调用zlib的初始化函数(逃~~~~)

不知道对于压缩算法方面有什么资料可以参考?俺觉得是时候补习补习了|||OTL……

再次感谢FX大人~~~~~
ONScripter for PSP/Windows中文版 http://blog.163.com/john_he_
回复 支持 反对

使用道具 举报

23

主题

218

帖子

2470

积分

⑥精研

积分
2470
 楼主| 发表于 2007-12-31 15:47:21 | 显示全部楼层
引用第5楼shawind于2007-12-31 14:56发表的  :
可以直接看到,虽然N年在那注册过,现在连注册名是什么都想不起来了。
果然贴的那个很经典的C源文件。89年的...

lzss,lzw虽然是无失真&高速,可也有个压缩率不是很高的问题。
一般来说,还有什么比较简单且高速的算法来配合一下?(无损的)
.......

我觉得以现在机器的速度,用Deflate是没有问题的,NScripter甚至用了bzip2(具体情况不清楚,貌似比Deflate更费时)感觉上都没有什么延迟。其实我觉得只要引擎设计得好,尽量把资源加载集中在LOADING时,数据压缩并不是影响速度的主要因素(一般解压缩都是很快的)。
ONScripter for PSP/Windows中文版 http://blog.163.com/john_he_
回复 支持 反对

使用道具 举报

8

主题

215

帖子

2223

积分

⑥精研

积分
2223
发表于 2007-12-31 16:33:33 | 显示全部楼层
引用第5楼shawind于2007-12-31 14:56发表的  :
lzss,lzw虽然是无失真&高速,可也有个压缩率不是很高的问题。
一般来说,还有什么比较简单且高速的算法来配合一下?(无损的)

现在我的作法是把BMP的文件头改造了一下,后面的具体数据只是简单的用了下zlib。
LZSS的压缩率还是相当不错的啦,特别是结合它的解压速度和复杂度来看的话能达到这个水平就相当不错了。
不过一般在字典压缩之后(注意一定是先字典压缩)接一个算术编码或者Huffman之类的熵减算法会是个不错的选择。其实zlib的deflate算法也是LZ77改进型+Huffman而已……并不复杂。

压缩图像用无损压缩能得到的收效始终还是太小。BMP的话要是在做deflate之前先做一次差分的话能得到更好的压缩率,也不会带来太多overhead(图不大的话嗯)。呵呵不过再多弄几步就变成PNG了。
引用第6楼john_he于2007-12-31 15:26发表的  :

不知道对于压缩算法方面有什么资料可以参考?俺觉得是时候补习补习了|||OTL……
LZSS编码算法的具体执行步骤如下:

1、把编码位置置于输入数据流的开始位置。
2、在前向缓冲存储器中查找与窗口中最长的匹配串
① Pointer :=匹配串指针。
② Length :=匹配串长度。
3、判断匹配串长度Length是否大于等于最小匹配串长度(Length ³ MIN_LENGTH) ,
如果“是”:输出指针,然后把编码位置向前移动Length个字符。
如果“否”:输出前向缓冲存储器中的第1个字符,然后把编码位置向前移动一个字符。
4、如果前向缓冲存储器不是空的,就返回到步骤2。
我有本书专门讲多媒体应用中的各种压缩算法……so繁复 T T
话说之前我在翻译的一个逆向工程相关的文章里也有讲压缩算法。可惜压缩算法那部分我坑了一年一直没翻完。主要是中间做了大概65%的时候那翻译稿突然在U盘里浮云了,让我没想法了……
回复 支持 反对

使用道具 举报

50

主题

742

帖子

402

积分

版主

自定义头衔

Rank: 7Rank: 7Rank: 7

积分
402
发表于 2007-12-31 16:38:40 | 显示全部楼层
哈,等到要用的时候偶就找一个来自己写试试XD~
YY最高啊,偶也想先编写再说
Style-C
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|幻想森林

GMT+8, 2024-4-30 14:24 , Processed in 0.044428 second(s), 20 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表