Test Acc Lmao
Test Acc Lmao

Reputation: 53

Bytes of png become corrupted in C

I want to write my own png reader using little help from other libraries. So far, I can read back images with only one IDAT chunk perfectly well. However, when I need to concatenate multiple chunks, the file ends up corrupted. Here is an example of one: A file with multiple IDAT's having been corrupted

I have a lot of code, so I'm going to try and get it to as 'minimum reproducable example' as I can get, but this along with the fact I do not know specifically what part is causing it, is going to make it a bit longer than I'm sure some of you might like, and I'm sorry.

#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;
}

These are the combinations of funtcions i use to achieve this. I first use a separate function which I know is not the problem and have therefore left out to get the bytes of the png file. I then call getChunksFromBytes() to gather the chunks of the png file, and I separate the IHDR from these chunks into a separate struct. Finally, I call getImgFromChunks() to uncompress the IDAT data, and getPixelsFromImg() to unfilter the uncompressed values.

Upvotes: 0

Views: 348

Answers (1)

Mark Adler
Mark Adler

Reputation: 112304

First off, bytes per pixel is not a thing since pixels can be bits packed into bytes. So your calculation of uncompressed_size in getImgFromChunks() is wrong. It should be:

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

Given that bits per pixel is not a thing, you need to rethink how you are doing getPixelsFromImg(). So there's no point in digging further here until you do that.

There is nothing wrong with your processing of multiple IDAT chunks. You may have misidentified the salient difference of the png file that is giving you issues.

Upvotes: 1

Related Questions