我正在寻找建议我可以用来压缩以下数据的方法:
65|018 03C 066 066 066 07E 066 066 066 000 000 000 000 000 000 000
66|07C 066 066 066 07C 066 066 066 07C 000 000 000 000 000 000 000
67|03C 066 066 060 060 060 066 066 03C 000 000 000 000 000 000 000
...
,
格式化为
<ASCII_CODE>|<FIRST ROW OF PIXELS> ... <16TH ROW OF PIXELS>
。
其中一行pizel可以是3个十六进制数字。 并且要求阅读要求不高。我已经尝试过Run-Length编码(因为有很多冗余数据),但是效果不好。 它还需要足够简单才能解压缩。
答案 0 :(得分:0)
最简单的方法是使用现有的解决方案,例如zlib,并在解析之前解压缩到内存。但这是一个无聊的答案,所以让我们看看我们自己能够取得的成就。
以文本形式表示数据效率很低。在当前格式中,我们需要68个字节(假设行终止符为CRLF)来表示一条记录。
记录本身由ASCII码(1个字节)和16行像素组成。每行由3位十六进制数表示,即12位。这总共25个字节,如果我们将2行打包成3个字节,或者如果我们只为每行使用16位整数则总共33个字节。这是原始空间要求的37%或49%,并且编码/解码它或多或少是微不足道的。
ASCII代码是连续的,但不是从0开始,并且不会一直运行到255.您可以按代码对记录进行排序,存储第一个和最后一个的索引,以及此范围的位图,指示缺少的符号。那么你就不必为每条记录存储代码。
似乎绝大多数符号左边是空列,底部是空行(顶部的空格不太常见)。
您可以存储此类空列和行的数量,然后避免保存这些位。由于有12列,4位就足够了。跳过12列或更多列意味着缺少字形(因此我们不需要前面提到的位图)。另外4位表示在底部跳过的行(因为跳过所有列就像跳过所有行一样,我们不需要能够跳过16行)。
您可以从Lempel-Ziv算法中获取一些灵感来压缩字形。
让我们定义以下操作码:
注意:空行可以表示为带0偏移的复制。
现在每个字形可以表示为那些操作码及其参数的序列。
字形图像中0和1的概率分布可能是偏斜的。类似的情况将与类似LZ的方案中的操作码和偏移相关。对它们进行熵编码可能是一个不错的选择(使用可以编码接近熵的算术编码器)。您还可以考虑使模型上下文敏感。
最后,您可以混合和匹配所提到的方法(如果LZ扩展数据,则回退到原始表示)。
包含许多变体的完整源代码在pastebin上,由于大小I,我将包含有趣的位。
样本输入的不同变体(0未经修改)的效率比较如下:
Variant 0: 6547 bytes
Variant 1: 3194 bytes
Variant 2: 2434 bytes
Variant 3: 1493 bytes
Variant 4: 1483 bytes
Variant 5: 1296 bytes
Variant 6: 1152 bytes
Variant 7: 1011 bytes
Variant 8: 839 bytes
Variant 9: 789 bytes
Variant 10: 669 bytes
表示内存中字体的简单结构:
struct glyph
{
uint8_t code;
std::array<uint16_t, 16> rows;
};
struct font
{
std::string header;
std::vector<glyph> glyphs;
void sort()
{
std::sort(glyphs.begin(), glyphs.end()
, [](glyph const& l, glyph const& r) -> bool {
return l.code < r.code;
});
}
};
一个简单的输出比特流实现:
struct simple_bitstream
{
simple_bitstream(std::vector<uint8_t>& buffer)
: buf_(buffer)
, temp_(0)
, temp_size_(0)
{
}
void write_bits(uint32_t v, uint8_t bits)
{
if (bits) {
write_bits(v >> 1, bits - 1);
write_bit(v & 1);
}
}
void write_bit(uint8_t v)
{
temp_ = (temp_ << 1) | (v & 1);
++temp_size_;
if (temp_size_ == 8) {
buf_.push_back(temp_);
temp_size_ = 0;
temp_ = 0;
}
}
void flush()
{
for (; temp_size_;) {
write_bit(0);
}
}
std::vector<uint8_t>& buf_;
uint8_t temp_;
uint8_t temp_size_;
};
Variant 6 - Lempel-Ziv启发的算法,包含4个操作码。
// Find nearest identical preceding row in this glyph
uint8_t find_nearest_copy(glyph const& g, uint32_t i)
{
uint8_t offset(0);
uint16_t row(g.rows[i]);
for (uint8_t j(1); j < i; ++j) {
if (row == g.rows[i - j]) {
offset = j;
break;
}
}
return offset;
}
uint32_t find_end_row(glyph const& g)
{
uint32_t end_row(16);
for (uint32_t i(0); i < 16; ++i) {
if (g.rows[15 - i] > 0) {
break;
}
--end_row;
}
return end_row;
}
void encode_v6(font const& f, std::vector<uint8_t>& buffer)
{
uint32_t OP_VERBATIM(0), OP_COPY(1), OP_EMPTY(2), OP_END(3);
encode_header(f, buffer);
simple_bitstream b(buffer);
for (glyph const& g : f.glyphs) {
// Code using 1 byte
b.write_bits(g.code, 8);
uint32_t end_row(find_end_row(g));
for (uint32_t i(0); i < end_row; ++i) {
uint16_t row(g.rows[i]);
if (row == 0) {
// Empty row
b.write_bits(OP_EMPTY, 2);
continue;
}
// Find nearest identical preceding row in this glyph
uint8_t offset(find_nearest_copy(g, i));
if (offset) {
// Copy with non-zero offset
b.write_bits(OP_COPY, 2);
b.write_bits(offset - 1, 4);
} else {
// Verbatim row
b.write_bits(OP_VERBATIM, 2);
b.write_bits(row, 12);
}
}
if (end_row < 16) {
// End the glyph (any remaining rows are empty)
b.write_bits(OP_END, 2);
}
}
}
变体8-10使用FastAC(算术编解码器)进行熵编码,以及一些额外的简单建模。