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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:24:40