You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用霍夫曼编码压缩PNG图像出现黑块问题的排查与修复

问题根源与修复方案

你的代码核心问题在于压缩逻辑完全错误,同时缺少关键的编码字典存储、文件读写模式错误等问题,导致解压后数据全黑。以下是具体修复步骤:


1. 修复压缩函数的核心逻辑

当前compressImage函数完全搞反了霍夫曼编码的流程:它没有根据符号查找对应的霍夫曼编码并写入比特流,而是用符号本身的二进制位去遍历霍夫曼树,这根本不是压缩。

解决步骤:

  • 首先构建字典,存储每个符号对应的霍夫曼编码(比特序列和长度)
  • 压缩时遍历每个符号,查字典得到编码,将编码的比特依次写入压缩数据

第一步:添加编码字典结构

在代码开头添加:

typedef struct {
    unsigned int code;  // 存储霍夫曼编码(整数保存,高位在前)
    int length;         // 编码的比特长度
} HuffmanCode;

HuffmanCode codeDict[256];  // 编码字典

第二步:修改createCodeDictionary函数,填充编码字典

void createCodeDictionary(Node* root, unsigned int code, int depth) {
    if (root->left == NULL && root->right == NULL) {
        codeDict[root->symbol].code = code;
        codeDict[root->symbol].length = depth;
        // 调试用打印
        printf("Symbol: 0x%02X, Code: ", root->symbol);
        for (int i = depth-1; i >= 0; i--) {
            printf("%d", (code >> i) & 1);
        }
        printf("\n");
        return;
    }
    createCodeDictionary(root->left, code << 1, depth + 1);
    createCodeDictionary(root->right, (code << 1) | 1, depth + 1);
}

调用时改为:createCodeDictionary(root, 0, 0);

第三步:重写compressImage函数

void compressImage(const unsigned char* symbol_sequence, size_t total_symbols, HuffmanCode* codeDict, unsigned char* compressed_data, size_t* compressed_size) {
    unsigned char* output = compressed_data;
    unsigned char current_byte = 0;
    int bit_pos = 0;  // 当前字节的比特位置(0为最高位)

    for (size_t i = 0; i < total_symbols; i++) {
        unsigned char symbol = symbol_sequence[i];
        HuffmanCode code = codeDict[symbol];
        unsigned int huff_code = code.code;
        int code_len = code.length;

        // 将编码的每一位写入当前字节
        for (int j = code_len - 1; j >= 0; j--) {
            int bit = (huff_code >> j) & 1;
            current_byte |= (bit << (7 - bit_pos));
            bit_pos++;

            // 字节满8位,写入输出
            if (bit_pos == 8) {
                *output = current_byte;
                output++;
                current_byte = 0;
                bit_pos = 0;
            }
        }
    }

    // 处理剩余未填满的字节
    if (bit_pos > 0) {
        *output = current_byte;
        output++;
    }

    *compressed_size = output - compressed_data;
}

调用时改为:compressImage(symbol_sequence, pixel_count * 3, codeDict, compressed_data, &compressed_data_size);


2. 修复文件读写的二进制模式

压缩数据是二进制流,不能用文本模式读写,否则会损坏数据(如Windows下0x0A会被转换为0x0D0A):

// 保存压缩数据用二进制写模式
FILE* file = fopen("../images/compressed_data.bin", "wb");

// 读取压缩数据用二进制读模式
file = fopen("../images/compressed_data.bin", "rb");

3. 修复符号序列的类型错误

symbol_sequence用char*存储unsigned char数据会导致符号扩展问题,改为:

unsigned char *symbol_sequence = malloc(pixel_count * 3);

4. 修复频率打印函数的逻辑

原printRGBFrequency遍历方式错误,改为正确的频率打印:

void printFrequency(const int *frequency) {
    printf("Symbol frequencies:\n");
    for (int i = 0; i < 256; i++) {
        if (frequency[i] > 0) {
            printf("0x%02X: %d\n", i, frequency[i]);
        }
    }
}

5. 补充压缩数据的必要元信息

当前代码未保存霍夫曼树/编码字典到压缩文件,实际解压时需要这些信息。可以将频率数组写入文件开头:

// 保存时先写入频率数组
fwrite(frequency, sizeof(int), 256, file);
// 再写入压缩数据
fwrite(compressed_data, 1, compressed_data_size, file);

解压时先读取频率数组重构霍夫曼树:

int read_frequency[256];
fread(read_frequency, sizeof(int), 256, file);
Node* root = buildHuffmanTree(read_frequency);

完成以上修改后,程序应该能正确压缩和解压图像,不会再出现全黑的情况。

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 11:59:54