基于OpenGL手动生成PNG时IDAT块数据异常问题及修复
OpenGL读取像素手动生成PNG显示异常排查
出于学习目的手动基于OpenGL实现PNG文件生成,目前IHDR、pHY、IEND等其余数据块均已验证正常,但生成的PNG文件显示异常,相同像素读取逻辑生成BMP文件可正常工作。
初始实现代码
OpenGL屏幕像素读取逻辑
int s_width = glutGet(GLUT_SCREEN_WIDTH), s_height = glutGet(GLUT_SCREEN_HEIGHT); int pixelArraySize = s_width*s_height*_glColorChannels; unsigned char *pixelsArrayInfo = (unsigned char*)malloc(pixelArraySize); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadBuffer(GL_FRONT); glReadPixels(0, 0, (unsigned short)s_width, (unsigned short)s_height, GL_RGB, GL_UNSIGNED_BYTE, pixelsArrayInfo);
初始扫描线生成、压缩相关逻辑
初始逻辑为每条扫描线对应屏幕单行RGB值数组,前缀添加过滤类型标识0,相关实现如下:
unsigned char *generateScanlines(unsigned char *pixels, int s_width, int s_height) { int eachScanlineLength = 1 + s_width*3; unsigned char *finalOutput = (unsigned char*)malloc(s_height*eachScanlineLength); for(int i=0; i<s_height; i++){ finalOutput[i*eachScanlineLength] = 0; copyElement(finalOutput, pixels, i*eachScanlineLength, (i+1)*eachScanlineLength, i*eachScanlineLength+1); } return finalOutput; } void copyElement(unsigned char *dest, unsigned char *src, int src_debut, int src_fin, int dest_debut) { for(int i=src_debut, j=dest_debut; i<src_fin; i++, j++){ dest[j] = src[i]; } } unsigned char *deflateDatas(unsigned char *pixels, int s_width, int s_height, int *deflatedDataLength) { unsigned char *deflated = (unsigned char*)malloc(compressBound(s_height*(1 + s_width*3))); unsigned char *scanlines = invertArray(generateScanlines(pixels, s_width, s_height), s_height*(1 + s_width*3)); z_stream defstream; defstream.zalloc = Z_NULL; defstream.zfree = Z_NULL; defstream.opaque = Z_NULL; defstream.avail_in = (uInt)(s_height*(1 + s_width*3)); defstream.next_in = (Bytef *)scanlines; defstream.avail_out = (uInt)(compressBound(s_height*(1 + s_width*3))); defstream.next_out = (Bytef *)deflated; deflateInit(&defstream, 0); deflate(&defstream, Z_FINISH); deflateEnd(&defstream); *deflatedDataLength = compressBound(s_height*(1 + s_width*3)); return deflated; }
原数组反转函数
原本尝试通过全数组反转解决坐标原点不匹配问题,实现如下:
unsigned char *invertArray(unsigned char *myArray, int arrayEnd) { unsigned char *invertedtableau = (unsigned char*)malloc(arrayEnd*sizeof(unsigned char)); for(int i=0 ; i<=arrayEnd ; i++) { invertedtableau[i] = myArray[arrayEnd-i]; } return invertedtableau; }
问题根因
经排查共定位到三类错误:
- 扫描线生成的内存拷贝逻辑存在偏移错误
- 坐标规则不匹配:OpenGL像素坐标原点为左下角,PNG像素坐标原点为左上角,原数组反转逻辑存在越界访问问题,且逐字节反转无法正确实现像素行的垂直翻转
- zlib deflate压缩调用流程不规范,且错误将压缩长度上界作为压缩后数据的实际长度写入IDAT块,导致PNG解析异常
修正后实现代码
像素缓冲区垂直翻转
需在生成扫描线前调用该函数,将OpenGL输出的左下角原点像素数据转换为PNG要求的左上角原点格式:
void flipPixels(unsigned char *pixelsArray, int s_width, int s_heigth, int colorChannel) { int totalLength = s_width * s_heigth * colorChannel; int oneLineLength = s_width * colorChannel; unsigned char *tmp = (unsigned char *)malloc(totalLength * sizeof(unsigned char)); memcpy(tmp, pixelsArray, totalLength); for (int i = 0; i < s_heigth; i++) memcpy(pixelsArray + oneLineLength * i, tmp + totalLength - oneLineLength * (i + 1), oneLineLength); free(tmp); }
修正后的扫描线生成函数
// 生成PNG要求的带过滤前缀的扫描线数据 unsigned char *generateScanlines(unsigned char *pixels, int s_width, int s_height, int colorChannel) { int eachScanlineLength = 1 + s_width * colorChannel, i = 1, j = 0; unsigned char *scanlines = (unsigned char *)malloc(s_height * eachScanlineLength); memset(scanlines, 0, s_height * eachScanlineLength * sizeof(char)); // 逐行拷贝像素数据,每行首字节保留为0(无过滤) for (i = 1, j = 0; i < s_height && j < s_height; i++, j++) memcpy(scanlines + 1 + (i - 1) * eachScanlineLength, pixels + j * (eachScanlineLength - 1), eachScanlineLength - 1); memcpy(scanlines + 1 + (i - 1) * eachScanlineLength, pixels + j * (eachScanlineLength - 1), eachScanlineLength - 1); return scanlines; }
修正后的IDAT块deflate压缩逻辑
// 对扫描线数据做deflate压缩,返回压缩后数据,通过出参返回实际压缩长度 unsigned char *deflateDatas(unsigned char *pixels, int s_width, int s_height, int colorChannel, int *deflatedLen) { unsigned long inLen = s_height * (1 + s_width * colorChannel), tmpLen = 0; unsigned char *scanlines = generateScanlines(pixels, s_width, s_height, colorChannel); unsigned char *deflatedDatas = NULL; int result = 0; z_stream defstream; defstream.zalloc = Z_NULL; defstream.zfree = Z_NULL; defstream.opaque = Z_NULL; defstream.avail_in = inLen; defstream.next_in = (Bytef *)scanlines; defstream.avail_out = 0; defstream.next_out = (Bytef *)deflatedDatas; if ((result = deflateInit(&defstream, Z_DEFAULT_COMPRESSION)) == Z_OK) { unsigned long estimateLen = deflateBound(&defstream, inLen); deflatedDatas = (unsigned char *)malloc(estimateLen); if (deflatedDatas != NULL) { defstream.avail_out = (uInt)estimateLen; defstream.next_out = (Bytef *)deflatedDatas; deflate(&defstream, Z_FINISH); // 计算压缩后数据的实际长度 tmpLen = (unsigned char *)defstream.next_out - deflatedDatas; } } deflateEnd(&defstream); *deflatedLen = tmpLen; free(scanlines); return deflatedDatas; }
内容的提问来源于stack exchange,提问作者Igor Mogou
相关产品推荐
相关产品推荐

