c - png 的字节在 C 中被损坏
问题描述
我想在其他库的帮助下编写自己的 png 阅读器。到目前为止,我可以很好地读取只有一个 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()
过滤未压缩的值。
解决方案
首先,每个像素的字节数不是问题,因为像素可以被打包成字节。所以你对uncompressed_size
in的计算getImgFromChunks()
是错误的。它应该是:
uLongf uncompressed_size = ihdr_data.height *
(1 + ((ihdr_data.bitd * ihdr_data.channels * (uLongf)ihdr_data.width + 7) >> 3));
鉴于每像素位数不是问题,您需要重新考虑您的工作方式getPixelsFromImg()
。所以在你这样做之前,在这里进一步挖掘是没有意义的。
您处理多个 IDAT 块没有任何问题。您可能错误地识别了给您带来问题的 png 文件的显着差异。
推荐阅读
- fork - fork() 移动到 main() 的开头
- sql - 在触发器中引发错误以返回插入行列
- visual-studio-code - 我可以扩展 vscode 检测到的默认 URI 方案吗?
- django - 在 Django 中向用户添加组
- javascript - javascript,如何更改数组中元素的时间戳?
- windows - 用于操作系统内部请求的类似提琴手的程序
- kubernetes - Kubernetes 负载均衡器类型未响应外部 IP 地址
- python - 当应该影响 X 的变量发生变化时,变量 X 不更新
- javascript - 发布请求后重新加载 HTML
- formula - NetSuite 保存的搜索条件公式 {activity.assigned} = 特定员工