字体压缩! RLE或霍夫曼还是什么都没有?

时间:2013-10-01 14:35:45

标签: c embedded compression

我正在开发一个嵌入式项目(ARM7)。在我的应用程序中,我必须存储大量的中文字符,这些中文字符浪费了大约300k的闪存。当前的字体编码是Unicode,每个字符包含22个字节,因为每个字形都是12 * 12加上左侧和底侧的一个行空间,这使得它成为169像素(22个字节)(参见示例)。例如,这个汉字的Unicode enter image description here

如下:           / * unicode5939 * /       0x40,0x44,0x4c,0x54,0x64,0xff,0xff,0x44,       0x54,0x4c,0x44,0x40,0x0,0x8,0x11,0x11,       0x0,0x8,0x82,0x20,0x4,0x0。

当前的Unicode是这样的:字形的上8行完全等于Unicode的前13个字节(基于列而不是基于行,从右上方)。其余的9个字节代表字形的5个底部行(从左侧看,右侧逐列,在字节中放置0和1,直到字节变满,依此类推)。

如果我们对此Unicode(按位)执行RLE压缩,则结果需要超过22个字节来存储每次运行的重复次数(就我手工完成而言)。所以我想做另一种压缩。任何线索?

3 个答案:

答案 0 :(得分:2)

通过不为每个字形存储空行,您将获得近20%的改善。

12x12而不是13x13 = 18字节而不是22。

答案 1 :(得分:1)

(不是实际答案,但我需要更多空间而不是评论)

嗯,至少13 * 13适合22个字节(它是169位,因此是21 1/8字节)。当按字节排列时,它看起来像这样:

01000000   01000    00010
01000100   01000    00010
01001100   00100    00100
01010100   00010    01000
01100100   00001    10000
11111111   00000    00000 Reordered by groups of five bits,
11111111   00000 <- 00000 <--------------------------+
01000100   00001    10000 flipped again             |
01010100   00010    01000                           |
01001100   00100    00100                           |
01000100   01000    00010                           |
01000000   01000    00010                           |
00000000   00000    00000                           |
00001000 -> 00010000 <- Bottom 9 bytes reversed: \  |
00010001 -> 10001000                             |  |
00010001 -> 10001000                             |  |
00000000 -> 00000000                             |  |
00001000 -> 00010000                             +--+
10000010 -> 01000001                             |
00100000 -> 00000100                             |
00000100 -> 00100000                             |
00000000 -> 0        (only one useful bit)       /

至少前13个字节肯定看起来像角色的前8行(右上方)。对于底部9个字节,一旦反转,它们可以由5位组布局,看起来像底部。

或者更可读的是,它们都是这样编码的: Glyph storage diagram

现在回答实际问题,我确信尝试单独压缩字形是一种灾难。由于缺乏存储未压缩数据的空间,因此无法在一个块中压缩所有块。因此,压缩需要在X字形块中完成,并使用缓存系统进行解压缩。

答案 2 :(得分:0)

甚至可以使用例如per / glyph来处理字体压缩。比较说4位邻域的算术编码:

[ 4 ] [ 3 ] [ 2 ]
[ 1 ] ( x )

像素p1,p2,p3,p4的每个邻域编码x == 0对x == 1的概率,其在处理比特'x'之后被更新;与霍夫曼编码器不同,算术编码器能够以比单个比特更小的单位压缩符号,这是香农信息理论给出的限制。

Context,    count               bits per symbol  
Zeros[ 0] = 45   Ones[ 0] = 4   19.987
Zeros[ 1] = 7    Ones[ 1] = 4   10.402
Zeros[ 2] = 6    Ones[ 2] = 0   0.000
Zeros[ 3] = 2    Ones[ 3] = 2   4.000
Zeros[ 4] = 6    Ones[ 4] = 5   10.934
Zeros[ 5] = 0    Ones[ 5] = 1   0.000
Zeros[ 6] = 2    Ones[ 6] = 0   0.000
Zeros[ 7] = 9    Ones[ 7] = 4   11.576
Zeros[ 8] = 5    Ones[ 8] = 13  15.343
Zeros[ 9] = 1    Ones[ 9] = 3   3.245
Zeros[10] = 4    Ones[10] = 0   0.000
Zeros[11] = 1    Ones[11] = 3   3.245
Zeros[12] = 2    Ones[12] = 2   4.000
Zeros[13] = 1    Ones[13] = 0   0.000
Zeros[14] = 1    Ones[14] = 5   3.900
Zeros[15] = 3    Ones[15] = 3   6.000
Total 92.634 bits = 12 bytes
vs. no context, 
Zeros = 95           Ones = 49      133.212 bits = 17 bytes

显而易见的问题是如何初始化数组:

1)使用固定的,即频率的静态模型
  2)使用完全自适应模型,假设开始时有50:50的机会   3)使用一组(2-256?)固定模型,最好地表征字形
  4)从预先计算的集合中开始一些模型并更新

一个完整的模型需要32个字节来编码0..169的值,因此除非非常强烈(并且巧妙地)压缩,否则不能用字符传递。

修改 如果使用单个(或非常少的全局静态表),也可以将表放大到5,6,7像素的邻域,或者将像素位置的信息嵌入到表中(这种方法无法编码条件'x是在底线'):

[ B ] [ C ] [ D ],          where A,B,C,D = 00 for 'white'
[ A ]  (x)                                  11 for 'black'
                                            01/10 for pixel outside canvas 

or
[ 1 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ]       + [ x is next to border? ]
[ 0 ] [ 2 ]  (x)

进一步阅读:
  - Fax Compression / JBIG
  - Arithmetic Coding