今天在使用OpenGL ES 加载一个 TGA 图片文件的时候,出现了加载失败的问题。
关于什么是TGA文件以及如何打开TGA文件?
可以参考我的博客:【我的OpenGL学习进阶之旅】什么是TGA文件以及如何打开TGA文件?
如下图所示,没有texture加载进来,黑黢黢的页面。
查看日志打印,发现加载tga图片失败,如下所示:
2021-11-25 15:42:31.690 6385-6548/com.oyp.openglesdemo I/NDK_JNI_LOG_TAG: [GLUtils.cpp][loadTgaTexture][240]: Error loading (texture/heightmap.tga) image.
关于日志打印的内容带有文件文件名、方法名、行号 等信息的实现方法
可以参考我的博客 【我的Android进阶之旅】NDK开发之在C++代码中使用Android Log打印日志,打印内容带有文件文件名、方法名、行号 等信息,方便定位日志输出的地方
// // Load texture from disk // GLuint GLUtils::loadTgaTexture (const char *fileName ) { int width, height; char *buffer = esLoadTGA (fileName, &width, &height ); GLuint texId; if ( buffer == nullptr ) { LOGI ( "Error loading (%s) image.\n", fileName ) return 0; } glGenTextures ( 1, &texId ); glBindTexture ( GL_TEXTURE_2D, texId ); glTexImage2D ( GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, buffer ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); free ( buffer ); return texId; }
可以发现,因为loadTgaTexture
函数调用esLoadTGA
函数返回的buffer
为nullptr
所以报错了。
esLoadTGA 函数代码如下:
// // esLoadTGA() // // Loads a 8-bit, 24-bit or 32-bit TGA image from a file // char * esLoadTGA (const char *fileName, int *width, int *height ) { char *buffer; esFile *fp; TGA_HEADER Header; int bytesRead; // Open the file for reading fp = esFileOpen (fileName ); if ( fp == nullptr ) { // Log error as 'error in opening the input file from apk' LOGE ( "esLoadTGA FAILED to load : { %s }\n", fileName ) return nullptr; } LOGD ( "sizeof ( TGA_HEADER ) : { %d }\n", sizeof ( TGA_HEADER ) ) bytesRead = esFileRead ( fp, sizeof ( TGA_HEADER ), &Header ); *width = Header.Width; *height = Header.Height; if ( Header.ColorDepth == 8 || Header.ColorDepth == 24 || Header.ColorDepth == 32 ) { int bytesToRead = sizeof ( char ) * ( *width ) * ( *height ) * Header.ColorDepth / 8; // Allocate the image data buffer buffer = ( char * ) malloc ( bytesToRead ); if ( buffer ) { bytesRead = esFileRead ( fp, bytesToRead, buffer ); esFileClose ( fp ); return ( buffer ); } } return ( nullptr ); }
我们在这个esLoadTGA
函数打断点,调试一下看看:
定位到问题,因为Header.ColorDepth
为87 'W'
,既不等于8,也不等于24,还不等于32,所以最终返回了nullptr
。
原来产生这个问题的原因和C/C++内存对齐
有关。这里,读者可以参考下面的文章了解。
我在esLoadTGA
函数里面有两句代码,如下所示,在读取TGA
文件之前,打印了TGA_HEADER
的sizeof
LOGD ( "sizeof ( TGA_HEADER ) : { %d }\n", sizeof ( TGA_HEADER ) ) bytesRead = esFileRead ( fp, sizeof ( TGA_HEADER ), &Header );
打印出来的sizeof 大小为20,所以导致了无法加载TGA。
2021-11-25 15:42:31.688 6385-6548/com.oyp.openglesdemo D/NDK_JNI_LOG_TAG: [GLUtils.cpp][esLoadTGA][203]: sizeof ( TGA_HEADER ) : { 20 } 2021-11-25 15:42:31.690 6385-6548/com.oyp.openglesdemo I/NDK_JNI_LOG_TAG: [GLUtils.cpp][loadTgaTexture][240]: Error loading (texture/heightmap.tga) image.
typedef struct { unsigned char IdSize, MapType, ImageType; unsigned short PaletteStart, PaletteSize; unsigned char PaletteEntryDepth; unsigned short X, Y, Width, Height; unsigned char ColorDepth, Descriptor; } TGA_HEADER;
__attribute__ ( ( packed ) )
参考下面博客:
__attribute__ ( ( packed ) )
修改结构体如下所示:
typedef struct // C语言__attribute__的使用 https://blog.csdn.net/qlexcel/article/details/92656797 // 使用该属性对struct 或者union 类型进行定义,设定其类型的每一个变量的内存约束。 // 就是告诉编译器取消结构在编译过程中的优化对齐(使用1字节对齐),按照实际占用字节数进行对齐,是GCC特有的语法。 // 这个功能是跟操作系统没关系,跟编译器有关,gcc编译器不是紧凑模式的 __attribute__ ( ( packed ) ) { unsigned char IdSize, MapType, ImageType; unsigned short PaletteStart, PaletteSize; unsigned char PaletteEntryDepth; unsigned short X, Y, Width, Height; unsigned char ColorDepth, Descriptor; } TGA_HEADER;
改完之后,重新执行,正常运行。
断点调试,发现 ColorDepth
为 8
,所以正常加载了 TGA 图片
实现的效果如下所示:
打印出来的 sizeof ( TGA_HEADER ) 为 18,所以加载TGA文件成功。
2021-11-25 16:03:30.881 7080-7188/com.oyp.openglesdemo D/NDK_JNI_LOG_TAG: [GLUtils.cpp][esLoadTGA][203]: sizeof ( TGA_HEADER ) : { 18 }
#pragma pack(1)
参考下面两篇博客:
#pragma pack(1)
修改代码// 注意点:保证内存是连续的,不然读取错误 使用 #pragma pack(1) 或者 __attribute__ ( ( packed ) ) 都可以 // C/C++内存对齐详解 https://zhuanlan.zhihu.com/p/30007037 // #pragma的常用方法讲解 https://blog.csdn.net/weixin_39640298/article/details/84503428 #pragma pack(push,x1) // Byte alignment (8-bit) #pragma pack(1) // 如果前面加上#pragma pack(1),那么此时有效对齐值为1字节 typedef struct { unsigned char IdSize, MapType, ImageType; unsigned short PaletteStart, PaletteSize; unsigned char PaletteEntryDepth; unsigned short X, Y, Width, Height; unsigned char ColorDepth, Descriptor; } TGA_HEADER; #pragma pack(pop,x1)
效果和 使用 __attribute__ ( ( packed ) )
修改结构体 的一样,这里不再重复描述
打印出来的 sizeof ( TGA_HEADER ) 为 18,所以加载TGA文件成功。
2021-11-25 16:11:12.936 7408-7542/com.oyp.openglesdemo D/NDK_JNI_LOG_TAG: [GLUtils.cpp][esLoadTGA][198]: sizeof ( TGA_HEADER ) : { 18 }
参考下面博客: