Libpng读取调色板图片时出现索引越界问题排查
问题排查:Libpng读取调色板图片时索引越界问题
问题背景
使用Libpng库开发PNG相关软件,读取部分调色板图片后,发现生成的二维数组存在大量调色板索引越界情况,无法定位原因。使用调试代码测试时,出现异常输出:
Index = 96 Pixel at (319, 96): Palette index out of range
调试代码
read_and_write_png函数
void read_and_write_png(const char* input_filename, const char* output_filename) { FILE* input_file = fopen(input_filename, "rb"); if(!input_file) { printf("Can't open file %s for reading\n", input_filename); return; } // Initialize the reading structures. png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); png_infop read_info_ptr = png_create_info_struct(read_ptr); png_init_io(read_ptr, input_file); png_read_info(read_ptr, read_info_ptr); png_uint_32 height = png_get_image_height(read_ptr, read_info_ptr); png_bytepp row_pointers = (png_bytepp)malloc(sizeof(png_bytep) * height); for (png_uint_32 i = 0; i < height; i++) { row_pointers[i] = (png_bytep)malloc(png_get_rowbytes(read_ptr, read_info_ptr)); } png_read_image(read_ptr, row_pointers); // Now that we've read the image, let's write it to another file. FILE* output_file = fopen(output_filename, "wb"); if(!output_file) { printf("Can't open file %s for writing\n", output_filename); return; } // Initialize the writing structures. png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); png_infop write_info_ptr = png_create_info_struct(write_ptr); // Set up the output. png_init_io(write_ptr, output_file); // Copy the image data from the read structures to the write structures. png_set_IHDR(write_ptr, write_info_ptr, png_get_image_width(read_ptr, read_info_ptr), png_get_image_height(read_ptr, read_info_ptr), png_get_bit_depth(read_ptr, read_info_ptr), png_get_color_type(read_ptr, read_info_ptr), png_get_interlace_type(read_ptr, read_info_ptr), png_get_compression_type(read_ptr, read_info_ptr), png_get_filter_type(read_ptr, read_info_ptr)); png_colorp palette; int num_palette; if (png_get_PLTE(read_ptr, read_info_ptr, &palette, &num_palette)) { png_set_PLTE(write_ptr, write_info_ptr, palette, num_palette); } png_bytep trans_alpha = NULL; int num_trans = 0; png_color_16p trans_color = NULL; if (png_get_tRNS(read_ptr, read_info_ptr, &trans_alpha, &num_trans, &trans_color)) { png_set_tRNS(write_ptr, write_info_ptr, trans_alpha, num_trans, trans_color); } printf("Reading image now. . \n"); sleep(5); output_pixel_values(read_ptr, read_info_ptr, row_pointers); printf("Done!!"); // Set up the data in the write structure. png_set_rows(write_ptr, write_info_ptr, row_pointers); // Finally, write the image to the file. png_write_png(write_ptr, write_info_ptr, PNG_TRANSFORM_IDENTITY, NULL); // Clean up. fclose(input_file); fclose(output_file); png_destroy_read_struct(&read_ptr, &read_info_ptr, NULL); png_destroy_write_struct(&write_ptr, &write_info_ptr); // Free the memory associated with row_pointers for (png_uint_32 i = 0; i < height; i++) { free(row_pointers[i]); } free(row_pointers); }
output_pixel_values函数
void output_pixel_values(png_structp png_ptr, png_infop info_ptr, png_bytep *row_pointers) { int width = png_get_image_width(png_ptr, info_ptr); int height = png_get_image_height(png_ptr, info_ptr); int color_type = png_get_color_type(png_ptr, info_ptr); int bit_depth = png_get_bit_depth(png_ptr, info_ptr); png_colorp palette = NULL; int num_palette = 0; if (color_type == PNG_COLOR_TYPE_PALETTE) { png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); } for (int y = 0; y < height; y++) { png_bytep row = row_pointers[y]; for (int x = 0; x < width; x++) { if (color_type == PNG_COLOR_TYPE_RGB) { png_bytep px = &(row[x * 3]); printf("Pixel at (%d, %d): R=%d, G=%d, B=%d\n", x, y, px[0], px[1], px[2]); } else if (color_type == PNG_COLOR_TYPE_RGBA) { png_bytep px = &(row[x * 4]); printf("Pixel at (%d, %d): R=%d, G=%d, B=%d, A=%d\n", x, y, px[0], px[1], px[2], px[3]); } else if (color_type == PNG_COLOR_TYPE_GRAY) { int num_bytes = bit_depth == 8 ? 1 : 2; png_bytep px = &(row[x * num_bytes]); int gray_value = num_bytes == 1 ? px[0] : (px[0] << 8) + px[1]; printf("Pixel at (%d, %d): Gray=%d\n", x, y, gray_value); } else if (color_type == PNG_COLOR_TYPE_PALETTE) { int index = row[x]; if (index < num_palette) { png_color palette_color = palette[index]; printf("Pixel at (%d, %d): Palette Index=%d, R=%d, G=%d, B=%d\n", x, y, index, palette_color.red, palette_color.green, palette_color.blue); } else { printf("Index = %d\n", index); printf("Pixel at (%d, %d): Palette index out of range\n", x, y); row[x] = 0; } } else { printf("Index = %d", row[x]); printf("Pixel at (%d, %d): Color type not supported\n", x, y); } } } }
问题根源
代码在处理PNG_COLOR_TYPE_PALETTE类型图片时,默认认为每个像素占用1个字节(8位深度),但PNG调色板图片支持1/2/4/8位多种位深度:
- 当位深度小于8时,多个像素的索引会被打包到同一个字节中,直接通过
row[x]获取索引会读取到错误的字节值,导致索引越界。 - 例如4位深度的图片,每个字节存储2个像素索引,
row[x]取到的是包含两个索引的完整字节,而非当前x位置的单个索引。
解决方案
有两种可选修复方式:
方式1:让Libpng自动扩展位深度
在读取图片信息后,调用png_set_expand让Libpng自动将低位深度的调色板图片转换为8位,这样每个像素占用1个字节,无需手动处理打包逻辑:
// 在png_read_info之后添加 png_set_expand(read_ptr); png_read_update_info(read_ptr, read_info_ptr);
添加后,原代码中row[x]获取索引的逻辑即可正常工作。
方式2:手动处理低位深度的像素索引解析
如果需要保留原始位深度,需根据位深度计算每个像素的实际索引:
// 替换output_pixel_values中调色板处理的代码块 else if (color_type == PNG_COLOR_TYPE_PALETTE) { int pixels_per_byte = 8 / bit_depth; int byte_idx = x / pixels_per_byte; int bit_shift = (8 - bit_depth) - (x % pixels_per_byte) * bit_depth; int index = (row[byte_idx] >> bit_shift) & ((1 << bit_depth) - 1); if (index < num_palette) { png_color palette_color = palette[index]; printf("Pixel at (%d, %d): Palette Index=%d, R=%d, G=%d, B=%d\n", x, y, index, palette_color.red, palette_color.green, palette_color.blue); } else { printf("Index = %d\n", index); printf("Pixel at (%d, %d): Palette index out of range\n", x, y); // 若需修正索引,需按位深度重新计算字节并修改对应位 // 此处示例仅输出错误,如需修改可自行实现位操作逻辑 } }
内容的提问来源于stack exchange,提问作者Lordmeme0123
相关产品推荐
相关产品推荐

