libpng的详细使用方法在于它的官方文档libpng-manual.txt,下载文件夹下含有。使用openGL生成纹理的时候需要图片的像素数据。使用libpng可以帮助我们解析PNG标准格式的结构,获得pixel数据。
在NDK中读取assets文件夹内容的方法在头文件#include <android/asset_manager.h>中定义。直接看代码:
/**
* Read png pixel data from file, caller must be free it
*/
static void* readPng(const char* filePath, Texture* texture) {
void* pixelData = NULL;
AAsset* asset = NULL;
do
{
off_t size;
asset = getAAsset(filePath, &size);
if(!asset) {
break;
}
unsigned char head[8];
AAsset_read(asset, head, 8);
if(png_sig_cmp(head, 0, 8)) {
LOGE("File %s, is not PNG", filePath);
break;
}
/* Create and initialize the png_struct with the desired error handler
* functions. If you want to use the default stderr and longjump method,
* you can supply NULL for the last three parameters. We also supply the
* the compiler header file version, so that we know if the application
* was compiled with a compatible version of the library. REQUIRED
*/
png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!pngPtr) {
LOGE("Unable to create PNG structure: %s", filePath);
break;
}
// Allocate/initialize the memory for image information. REQUIRED
png_infop infoPtr = png_create_info_struct(pngPtr);
if (!infoPtr) {
png_destroy_read_struct(&pngPtr, NULL, NULL);
LOGE("Unable to create PNG info : %s", filePath);
break;
}
png_infop endInfo = png_create_info_struct(pngPtr);
if (!endInfo) {
png_destroy_read_struct(&pngPtr, &infoPtr, NULL);
LOGE("Unable to create PNG end info : %s", filePath);
break;
}
// Set error handling if you are using the setjmp/longjmp method (this is
// the normal method of doing things with libpng). REQUIRED unless you
// set up your own error handlers in the png_create_read_struct() earlier.
if (setjmp(png_jmpbuf(pngPtr))) {
// Free all of the memory associated with the png_ptr and info_ptr
png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
LOGE("Error during setjmp : %s", filePath);
break;
}
/* If you are using replacement read functions, instead of calling
* png_init_io() here you would call:
* where user_io_ptr is a structure you want available to the callbacks
*/
png_set_read_fn(pngPtr, (void*)asset, readPngData);
// If we have already read some of the signature
png_set_sig_bytes(pngPtr, 8);
/* The call to png_read_info() gives us all of the information from the
* PNG file before the first IDAT (image data chunk). REQUIRED
*/
png_read_info(pngPtr, infoPtr);
int bitDepth;
int colorType;
int interlaceype;
png_uint_32 width;
png_uint_32 height;
png_get_IHDR(pngPtr, infoPtr,
&width,
&height,
&bitDepth,&colorType, &interlaceype, NULL, NULL);
texture->width = (float) width;
texture->height = (float) height;
LOGD("PNG width = %f, height = %f", texture->width, texture->height);
// Update the png info struct.
png_read_update_info(pngPtr, infoPtr);
// Allocate the memory to hold the image using the fields of info_ptr
unsigned int rowBytes = png_get_rowbytes(pngPtr, infoPtr);
LOGD("Row size: %d bytes", rowBytes);
// Allocate the pixel data as a big block, to be given to openGL
pixelData = png_malloc(pngPtr, rowBytes * height);
if(!pixelData) {
png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
LOGE("Unable to allocate PNG pixel data while loading %s", filePath);
break;
}
/* Turn on interlace handling. REQUIRED if you are not using
* png_read_image(). To see how to handle interlacing passes,
* see the png_read_row() method below:
*/
int numberPasses = png_set_interlace_handling(pngPtr);
LOGD("interlacing passes = %d", numberPasses);
for (int pass = 0; pass < numberPasses; pass++) {
for (int row = 0; row < height; row++) {
png_read_row(pngPtr, ((unsigned char*)pixelData + (row * rowBytes)), NULL);
}
}
// Read rest of file, and get additional chunks in info_ptr - REQUIRED
png_read_end(pngPtr, infoPtr);
// At this point you have read the entire image
// Clean up after the read, and free any memory allocated - REQUIRE
png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
} while(0);
AAsset_close(asset);
return pixelData;
}
这里我参考了libpng官方教程,使用最简单和最直接的方法获得PNG的像素数据。libpng的API可以对PNG进行复杂的操作和设置,我一概没有使用,直接定位到pixel数据,读取到内存。
这里需要解释两个地方。第一,libpng使用了FILE对象的文件操作体系。当然在NDK中无法直接使用,我们需要使用assets来代替。libpng提供了设置读取数据的回调函数。
/**
* Callback for libpng read data
*/
static void readPngData(png_structp pngPtr, png_bytep data, png_size_t length) {
AAsset* asset = (AAsset*)png_get_io_ptr(pngPtr);
AAsset_read(asset, data, length);
}
最后一点, libpng读取pixel数据的时候,有2中方法。一种是一次性读取数据到一个指针数组,一个指针持有一行数据。但是openGL需要的是一维数组。所以,通常做法会构建一个一维数组,以后构建一个指针数组,以后把一维数组折叠到指针数组。意思就是,指针数组的每个指针,指向一维数组特定的位置,表示一行数据。
我这里使用了png_read_row方法,每次读取一行,直接就放到一维数组里面去了。省去了构建指针数组的空间,也更简洁直观。
获得assets的方法是NDK提供的函数: