首页 > 解决方案 > png 的字节在 C 中被损坏

问题描述

我想在其他库的帮助下编写自己的 png 阅读器。到目前为止,我可以很好地读取只有一个 IDAT 块的图像。但是,当我需要连接多个块时,文件最终会损坏。这是一个例子:具有多个 IDAT 的文件已损坏

我有很多代码,所以我将尝试将其作为“最小可重现示例”,但这与我不知道具体是什么部分导致它的事实一起,将会使它比我确定你们中的一些人可能喜欢的要长一点,我很抱歉。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zlib.h"

unsigned char IEND_CHUNK[4] = {'I', 'E', 'N', 'D'}; //Indicates End Of File
unsigned char IDAT_CHUNK[4] = {'I', 'D', 'A', 'T'}; //Holds Pixel Information

typedef struct Chunk Chunk;
typedef struct IHDR IHDR;

struct Chunk {
    int length;
    unsigned char type[4];
    unsigned char *data;
    unsigned char crc[4];
};
struct IHDR {
    int width;
    int height;
    int bitd;
    int colort;
    int compm;
    int filterm;
    int interlacem;
    int channels;
    int bytesPerPixel;
};

int bytesToInt(unsigned char a, unsigned char b, unsigned char c, unsigned char d)
{
    return (int) a << 24 | b << 16 | c << 8 | d;
}

int compType(unsigned char *a, unsigned char b[], int len)
{
    int equal = 1;
    for(int i = 0; i < len; i++) equal = a[i] == b[i] ? equal : 0;
    return equal;
}

Chunk* getChunksFromBytes(unsigned char* bytes)
{
    int next_seg = 7;
    int chunks = 0;
    long int size = 1;
    Chunk* temp = (Chunk*) malloc(sizeof(Chunk));

    while(1){
        size += sizeof(Chunk);
        temp = realloc(temp, size);

        temp[chunks].length = bytesToInt(bytes[next_seg+1], bytes[next_seg+2],
                                      bytes[next_seg+3], bytes[next_seg+4]);

        temp[chunks].type[0] = bytes[next_seg+5];
        temp[chunks].type[1] = bytes[next_seg+6];
        temp[chunks].type[2] = bytes[next_seg+7];
        temp[chunks].type[3] = bytes[next_seg+8];

        temp[chunks].data = malloc(temp[chunks].length);
        if(temp[chunks].length > 0){
            memcpy(temp[chunks].data, bytes+next_seg+9, temp[chunks].length);
        }
        else temp[chunks].data = NULL;

        temp[chunks].crc[0] = bytes[next_seg+temp[chunks].length+9];
        temp[chunks].crc[1] = bytes[next_seg+temp[chunks].length+10];
        temp[chunks].crc[2] = bytes[next_seg+temp[chunks].length+11];
        temp[chunks].crc[3] = bytes[next_seg+temp[chunks].length+12];

        if(compType(temp[chunks].type, IEND_CHUNK, 4)) break;
        next_seg+=temp[chunks].length+12;
        chunks++;
    }

    return temp;
}

IHDR getIHDRFromChunks(Chunk* chunks)
{
    IHDR img_info;
    int channels[7] = {1, 0, 3, 1, 2, 0, 4};

    img_info.width = bytesToInt(chunks[0].data[0], chunks[0].data[1],
                                chunks[0].data[2], chunks[0].data[3]);
    img_info.height = bytesToInt(chunks[0].data[4], chunks[0].data[5],
                                 chunks[0].data[6], chunks[0].data[7]);

    img_info.bitd = (int) chunks[0].data[8];
    img_info.colort = (int) chunks[0].data[9];
    img_info.compm = (int) chunks[0].data[10];

    img_info.filterm = (int) chunks[0].data[11];
    img_info.interlacem = (int) chunks[0].data[12];
    img_info.channels = channels[img_info.colort];

    img_info.bytesPerPixel = (int) img_info.channels * (img_info.bitd / 8);
    return img_info;
}

unsigned char* getImgFromChunks(Chunk* chunks)
{
    int count = 0;
    IHDR ihdr_data = getIHDRFromChunks(chunks);
    uLongf compressed_size = 0;
    uLongf uncompressed_size = (ihdr_data.width*ihdr_data.height*ihdr_data.bytesPerPixel) + ihdr_data.height + 1;
    unsigned char* compressed_idat = (unsigned char*) malloc(sizeof(unsigned char)*4);
    unsigned char* uncompressed_idat = (unsigned char*) malloc(sizeof(unsigned char)*uncompressed_size);

    while(1){
        if(compType(chunks[count].type, IEND_CHUNK, 4)) break;
        else if (compType(chunks[count].type, IDAT_CHUNK, 4)){
            compressed_size += sizeof(unsigned char)*chunks[count].length;
            compressed_idat = realloc(compressed_idat, compressed_size);

            memcpy(compressed_idat + compressed_size - chunks[count].length, chunks[count].data, chunks[count].length);
        }
        count++;
    }

    int ret = uncompress(uncompressed_idat, &uncompressed_size, compressed_idat, compressed_size);
    free(compressed_idat);

    return ret == 0 ? uncompressed_idat : '\0';
}

int PaethPredictor(int a, int b, int c)
{
    int pa = abs(b - c);
    int pb = abs(a - c);
    int pc = abs(a + b - (2*c));

    if(pa <= pb && pa <= pc) return a;
    else if(pb <= pc) return b;
    return c;

}

int* getPixelsFromImg(unsigned char* img, IHDR ihdr_info)
{
    int* pixels = (int*) malloc(sizeof(int)*ihdr_info.width*ihdr_info.height*ihdr_info.bytesPerPixel);
    int filter_type;

    int i = 0;
    int current_pixel = 0;
    int stride = ihdr_info.width * ihdr_info.bytesPerPixel;

    int filt_a;
    int filt_b;
    int filt_c;


    for(int r = 0; r < ihdr_info.height; r++){
        filter_type = img[i];
        i++;
        for(int c = 0; c < stride; c++){

            filt_a = c >= ihdr_info.bytesPerPixel ? pixels[r * stride + c - ihdr_info.bytesPerPixel] : 0;
            filt_b = r > 0 ? pixels[(r - 1) * stride + c] : 0;
            filt_c = r > 0 && c >= ihdr_info.bytesPerPixel ? pixels[(r - 1) * stride + c - ihdr_info.bytesPerPixel] : 0;

            switch(filter_type){
                case 0:
                    pixels[current_pixel] = img[i] & 0xff;
                    break;
                case 1:
                    pixels[current_pixel] = (img[i] + filt_a) & 0xff;
                    break;
                case 2:
                    pixels[current_pixel] = (img[i] + filt_b) & 0xff;
                    break;
                case 3:
                    pixels[current_pixel] = (img[i] + filt_a + filt_c) & 0xff;
                    break;
                case 4:
                    pixels[current_pixel] = (img[i] + PaethPredictor(filt_a, filt_b, filt_c)) & 0xff;
                    break;
            }
            current_pixel++;
            i++;
        }
    }

    return pixels;
}

这些是我用来实现这一目标的功能组合。我首先使用一个单独的函数,我知道这不是问题,因此省略了获取 png 文件的字节。然后我调用getChunksFromBytes()来收集 png 文件的块,并将 IHDR 从这些块中分离到一个单独的结构中。最后,我调用getImgFromChunks()解压缩 IDAT 数据,并取消getPixelsFromImg()过滤未压缩的值。

标签: cpngzlib

解决方案


首先,每个像素的字节数不是问题,因为像素可以被打包成字节。所以你对uncompressed_sizein的计算getImgFromChunks()是错误的。它应该是:

uLongf uncompressed_size = ihdr_data.height *
    (1 + ((ihdr_data.bitd * ihdr_data.channels * (uLongf)ihdr_data.width + 7) >> 3));

鉴于每像素位数不是问题,您需要重新考虑您的工作方式getPixelsFromImg()。所以在你这样做之前,在这里进一步挖掘是没有意义的。

您处理多个 IDAT 块没有任何问题。您可能错误地识别了给您带来问题的 png 文件的显着差异。


推荐阅读