libpng
libpng is the official PNG reference library. It supports almost all PNG features, is extensible, and has been extensively tested for over 28 years. The home site for development versions (i.e., may be buggy or subject to change or include experimental features) is https://libpng.sourceforge.io/, and the place to go for questions about the library is the png-mng-implement mailing list. libpng is available as ANSI C (C89) source code and requires zlib 1.0.4 or later (1.2.13 or later recommended for performance and security reasons). The current public release, libpng 1.6.40, is a maintenance release with various fixes and improvements to the build
scripts, docs, and handlers for three of the chunk types (eXIf, pCAL,tRNS).
从上面的信息中可以看出libpng是一个历史悠久的处理png的库,需要zlib依赖。
Android版本
Android版本的libpng。
https://github.com/julienr/libpng-android
将代码clone下来就可以进行编译了。
编译
静态库
使用下面的命令可以直接编译为静态库。
ndk-build NDK_PROJECT_PATH=./
动态库
修改Android.mk中的,编译动态库
include $(BUILD_SHARED_LIBRARY)
# include $(BUILD_STATIC_LIBRARY)
使用上面的命令编译报错,从错误中推测应该需要zlib依赖,还需要下载zlib,由于Android自带zlib库,遂使用cmake进行。
只需要在CmakeList.txt中加入下面这句话就可以了。ADD_SUBDIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/libpng-android-master)
。这个路径是clone下来的libpng中CmakeLists.txt所在的目录。
cmake_minimum_required(VERSION 3.22.1)
# Declares and names the project.
project("myapplication")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
ADD_SUBDIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/libpng-android-master)
# set(png-lib ${CMAKE_SOURCE_DIR}/../../../libs)
#将第三方库作为动态库引用
add_library( # Sets the name of the library.
myapplication
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
修改libpng中CmakeLists.txt中的add_library,修改为动态共享库。
find_package( zlib REQUIRED )
# If you want a shared library instead of static, specify the BUILD_SHARED_LIBS
# variable (which is a built-in CMake option).
add_library( ${project_name} SHARED ${sources} ${headers} )
target_link_libraries( ${project_name} PUBLIC ZLIB::ZLIB )
target_include_directories( ${project_name} PUBLIC
$<BUILD_INTERFACE:${source_dir}>
$<INSTALL_INTERFACE:include>
)
然后就可以通过Build Apks进行编译了。
使用
将编译出来的so文件放在libs文件夹下面。也就是下面代码中的${CMAKE_SOURCE_DIR}/../../../libs
。将相应的.h问价拷贝到应用程序代码能够引用到的位置即可。
CMakeLists.txt内容如下:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.22.1)
set(my_lib_path ${CMAKE_SOURCE_DIR}/../../../libs)
# Declares and names the project.
project("myapplication")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
# ADD_SUBDIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/libpng-android-master)
set(png-lib ${CMAKE_SOURCE_DIR}/../../../libs)
#将第三方库作为动态库引用
add_library(png-lib
SHARED
IMPORTED)
#指定第三方库的绝对路径
set_target_properties(png-lib
PROPERTIES IMPORTED_LOCATION
${my_lib_path}/${ANDROID_ABI}/libpng.so)
add_library( # Sets the name of the library.
myapplication
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
myapplication
png-lib
# Links the target library to the log library
# included in the NDK.
${log-lib}
)
官方的例子
“android_png.h”
#ifndef _ANDROID_PNG_H_
#define _ANDROID_PNG_H_
#include "common.h"
class AAssetManager;
struct ImageData {
int img_width, img_height;
uint8_t* pixels;
virtual ~ImageData();
};
inline
bool IsPowerOfTwo(int n) {
// http://www.exploringbinary.com/ten-ways-to-check-if-an-integer-is-a-power-of-two-in-c/
return (n & (n - 1)) == 0;
}
inline
int NextPowerOfTwo(int n) {
// Explanation : http://stackoverflow.com/questions/1322510/given-an-integer-how-do-i-find-the-next-largest-power-of-two-using-bit-twiddlin
if (n < 0) {
return 0;
}
n--;
n |= n >> 1; // Divide by 2^k for consecutive doublings of k up to 32,
n |= n >> 2; // and then or the results.
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n++; // The result is a number of 1 bits equal to the number
// of bits in the original number, plus 1. That's the
// next highest power of 2.
return n;
}
// Loads a PNG image from the asset directory
// Note that this REQUIRES that fname has a power-of-two size
ImageData* FromAssetPNGFile(AAssetManager* mgr, const string& fname);
#endif
#include "android_png.h"
#include <android/asset_manager.h>
#include "png.h"
struct PNGImageData : public ImageData {
PNGImageData(png_byte* pixels, int width, int height) {
this->img_width = width;
this->img_height = height;
this->pixels = (uint8_t*)pixels;
}
virtual ~PNGImageData() {
delete[] pixels;
}
};
static AAsset* asset;
void png_asset_read(png_structp png_ptr, png_bytep data, png_size_t length) {
CHECK_GT(AAsset_read(asset, data, length), 0) << "Reading past end-of-file";
}
// Adapted from http://en.wikibooks.org/wiki/OpenGL_Programming/Intermediate/Textures
ImageData* FromAssetPNGFile(AAssetManager* mgr, const string& fname) {
asset = AAssetManager_open(mgr, fname.c_str(), AASSET_MODE_STREAMING);
if (!asset) {
LOG(ERROR) << "Error opening " << fname;
return NULL;
}
//header for testing if it is a png
png_byte header[8];
//read the header
AAsset_read(asset, header, 8);
//test if png
int is_png = !png_sig_cmp(header, 0, 8);
if (!is_png) {
AAsset_close(asset);
LOG(ERROR) << "Not a png file : " << fname;
return NULL;
}
//create png struct
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
NULL, NULL);
if (!png_ptr) {
AAsset_close(asset);
LOG(ERROR) << "Unable to create png struct : " << fname;
return NULL;
}
//create png info struct
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL);
LOG(ERROR) << "Unable to create png info : " << fname;
AAsset_close(asset);
return NULL;
}
//create png end info struct
png_infop end_info = png_create_info_struct(png_ptr);
if (!end_info) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
LOG(ERROR) << "Unable to create png end info : " << fname;
AAsset_close(asset);
return NULL;
}
//png error stuff, not sure libpng man suggests this.
if (setjmp(png_jmpbuf(png_ptr))) {
AAsset_close(asset);
LOG(ERROR) << "Error during setjmp : " << fname;
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
return NULL;
}
//init png reading
//png_init_io(png_ptr, fp);
png_set_read_fn(png_ptr, NULL, png_asset_read);
//let libpng know you already read the first 8 bytes
png_set_sig_bytes(png_ptr, 8);
// read all the info up to the image data
png_read_info(png_ptr, info_ptr);
//variables to pass to get info
int bit_depth, color_type;
png_uint_32 width, height;
// get info about png
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
NULL, NULL, NULL);
if (!IsPowerOfTwo(width) || !IsPowerOfTwo(height)) {
LOG(ERROR) << "Non-power of two (" << width << "x" << height
<< ") texture are not supported : " << fname;
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
AAsset_close(asset);
return NULL;
}
// Update the png info struct.
png_read_update_info(png_ptr, info_ptr);
// Row size in bytes.
int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
// Allocate the image_data as a big block
png_byte *image_data = new png_byte[rowbytes * height];
if (!image_data) {
//clean up memory and close stuff
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
LOG(ERROR) << "Unable to allocate image_data while loading " << fname;
AAsset_close(asset);
return NULL;
}
//row_pointers is for pointing to image_data for reading the png with libpng
png_bytep *row_pointers = new png_bytep[height];
if (!row_pointers) {
//clean up memory and close stuff
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
delete[] image_data;
LOG(ERROR) << "Unable to allocate row_pointer while loading " << fname;
AAsset_close(asset);
return NULL;
}
// set the individual row_pointers to point at the correct offsets of image_data
for (int i = 0; i < height; ++i) {
//row_pointers[height - 1 - i] = image_data + i * rowbytes;
row_pointers[i] = image_data + i * rowbytes;
}
//read the png into image_data through row_pointers
png_read_image(png_ptr, row_pointers);
ImageData* img = new PNGImageData(image_data, width, height);
//clean up memory and close stuff
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
delete[] row_pointers;
AAsset_close(asset);
return img;
}
目录结构
公众号
更多内容,欢迎关注我的微信公众号: 半夏之夜的无情剑客。