- PVSM.RU - https://www.pvsm.ru -
Приветствую всех читателей!
В этой статье речь пойдет о том, как в Android NDK загрузить PNG и JPEG картинки из файла или из памяти, а также немного полезного кода для скармливания этих картинок OpenGL.
Для загрузки картинок мы будем использовать следующие библиотеки:
Разложим все аккуратно по папкам, например создадим папку modules в корне проекта, там же где находится папка jni. Распакуем:
т.о. получится такая структура
PROJECT/jni
PROJECT/modules/png
PROJECT/modules/jpeg
PROJECT/modules/zlib
ifeq ($(notdir $(MAKECMDGOALS)),libjpeg.a)
LOCAL_SRC_FILES += $(libsimd_SOURCES_DIST)
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := dummy
endif
ifeq ($(ARCH_ARM_HAVE_NEON),true)
LOCAL_CFLAGS += -D__ARM_HAVE_NEON
endif
/PATH_TO_NDK/android-ndk-r8/ndk-build NDK_PROJECT_PATH=. LOCAL_ARM_MODE=arm APP_BUILD_SCRIPT=./Android.mk obj/local/armeabi/libjpeg.a
/PATH_TO_NDK/android-ndk-r8/ndk-build NDK_PROJECT_PATH=. LOCAL_ARM_MODE=arm APP_BUILD_SCRIPT=./Android.mk APP_ABI=armeabi-v7a obj/local/armeabi-v7a/libjpeg.a
include $(CLEAR_VARS)
LOCAL_MODULE := png
FILE_LIST := $(wildcard $(LOCAL_PATH)/../modules/png/*.c*)
LOCAL_SRC_FILES :=$(FILE_LIST:$(LOCAL_PATH)/%=%)
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../modules/png
HEADERS += $(LOCAL_EXPORT_C_INCLUDES)
STATICLIBS += $(LOCAL_MODULE)
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := zlib
FILE_LIST := $(wildcard $(LOCAL_PATH)/../modules/zlib/*.c*)
LOCAL_SRC_FILES :=$(FILE_LIST:$(LOCAL_PATH)/%=%)
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../modules/zlib
HEADERS += $(LOCAL_EXPORT_C_INCLUDES)
STATICLIBS += $(LOCAL_MODULE)
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := jpeg
LOCAL_SRC_FILES := ../modules/jpeg/obj/local/$(TARGET_ARCH_ABI)/libjpeg.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../modules/jpeg
STATICLIBS += $(LOCAL_MODULE)
HEADERS += $(LOCAL_EXPORT_C_INCLUDES)
include $(PREBUILT_STATIC_LIBRARY)
#----------------------------------------------------------
include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_MODULE := LoadImage
LOCAL_SRC_FILES := loadimage.cpp
LOCAL_CFLAGS := -Werror -DGL_GLEXT_PROTOTYPES=1 -fsigned-char -Wno-write-strings -Wno-psabi
LOCAL_LDLIBS := -llog -lGLESv1_CM
LOCAL_STATIC_LIBRARIES := $(STATICLIBS)
LOCAL_C_INCLUDES = $(HEADERS)
include $(BUILD_SHARED_LIBRARY)
В Вашем C++ коде (у меня это файл loadimage.cpp) делаем следующее:
#include <jni.h>
#include <android/log.h>
#include <GLES/gl.h>
#include <GLES/glext.h>
//подключаем заголовки
extern "C" {
#include "png.h"
#include <setjmp.h>
#include "jpeglib.h"
}
#define LOG(...) __android_log_print(ANDROID_LOG_VERBOSE, "NDK",__VA_ARGS__)
//структура для картинки
struct image {
png_uint_32 imWidth, imHeight; //реальный размер картинки
png_uint_32 glWidth, glHeight; //размер который подойдет для OpenGL
int bit_depth, color_type;
char* data; //данные RGB/RGBA
};
//ф-ция определяющая размер картинки который подойдет для OpenGL
static int reNpot(int w) {
//поддерживает ли OpenGL текстуры размера не кратным двум
//эту переменную конечно надо определять один раз при старте проги с помощью
//String s = gl.glGetString(GL10.GL_EXTENSIONS);
//NON_POWER_OF_TWO_SUPPORTED = s.contains("texture_2D_limited_npot") || s.contains("texture_npot") || s.contains("texture_non_power_of_two");
bool NON_POWER_OF_TWO_SUPPORTED = false;
if (NON_POWER_OF_TWO_SUPPORTED) {
if (w % 2) w++;
} else {
if (w <= 4) w = 4;
else if (w <= 8) w = 8;
else if (w <= 16) w = 16;
else if (w <= 32) w = 32;
else if (w <= 64) w = 64;
else if (w <= 128) w = 128;
else if (w <= 256) w = 256;
else if (w <= 512) w = 512;
else if (w <= 1024) w = 1024;
else if (w <= 2048) w = 2048;
else if (w <= 4096) w = 4096;
}
return w;
}
//ф-ция чтения PNG файла
static image readPng(const char* fileName) {
image im;
FILE* file = fopen(fileName, "rb");
//пропускаем заголовок, хотя именно сюда можно добавить проверку PNG это или JPEG, чтобы ф-ция сама определяла как грузить картинку
fseek(file, 8, SEEK_CUR);
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop info_ptr = png_create_info_struct(png_ptr);
png_init_io(png_ptr, file);
png_set_sig_bytes(png_ptr, 8);
png_read_info(png_ptr, info_ptr);
//читаем данные о картинке
png_get_IHDR(png_ptr, info_ptr, &im.imWidth, &im.imHeight, &im.bit_depth, &im.color_type, NULL, NULL, NULL);
//определяем размер картинки подходящий для OpenGL
im.glWidth = reNpot(im.imWidth);
im.glHeight = reNpot(im.imHeight);
//если картинка содержит прозрачность то на каждый пиксель 4 байта (RGBA), иначе 3 (RGB)
int row = im.glWidth * (im.color_type == PNG_COLOR_TYPE_RGBA ? 4 : 3);
im.data = new char[row * im.glHeight];
//в этом массиве содержатся указатели на начало каждой строки
png_bytep * row_pointers = new png_bytep[im.imHeight];
for(int i = 0; i < im.imHeight; ++i)
row_pointers[i] = (png_bytep) (im.data + i * row);
//читаем картинку
png_read_image(png_ptr, row_pointers);
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
delete[] row_pointers;
return im;
}
//необходимые структуры для libjpeg-turbo
struct my_error_mgr {
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
};
typedef struct my_error_mgr * my_error_ptr;
METHODDEF(void) my_error_exit(j_common_ptr cinfo) {
my_error_ptr myerr = (my_error_ptr) cinfo->err;
(*cinfo->err->output_message)(cinfo);
longjmp(myerr->setjmp_buffer, 1);
}
//ф-ция чтения JPEG файла
static image readJpeg(const char* fileName) {
image im;
FILE* file = fopen(fileName, "rb");
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
if (setjmp(jerr.setjmp_buffer)) {
jpeg_destroy_decompress(&cinfo);
return im;
}
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, file);
//получаем данные о картинке
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
im.imWidth = cinfo.image_width;
im.imHeight = cinfo.image_height;
im.glWidth = reNpot(im.imWidth);
im.glHeight = reNpot(im.imHeight);
//JPEG не имеет прозрачности так что картинка всегда 3х-байтная (RGB)
int row = im.glWidth * 3;
im.data = new char[row * im.glHeight];
//построчно читаем данные
unsigned char* line = (unsigned char*) (im.data);
while (cinfo.output_scanline < cinfo.output_height) {
jpeg_read_scanlines(&cinfo, &line, 1);
line += row;
}
//подчищаем
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return im;
}
Тестируем загрузку PNG картинки с карты памяти:
//создаем OpenGL текстуру
GLuint texture1;
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//читаем PNG картинку
image im = readPng("/mnt/sdcard/scrrihs.png");
LOG("PNG: %dx%d (%dx%d) bit:%d type:%d", im.imWidth, im.imHeight, im.glWidth, im.glHeight, im.bit_depth, im.color_type);
//в зависимости от прозрачности загружаем текстуру в OpenGL
if (im.color_type == PNG_COLOR_TYPE_RGBA) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, im.glWidth, im.glHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, im.data);
} else {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, im.glWidth, im.glHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, im.data);
}
delete[] im.data;
Аналогично делаем с JPEG, учитывая что JPEG всегда без прозрачности
GLuint texture2;
glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
image imJpeg = readJpeg("/mnt/sdcard/test.jpg");
LOG("JPEG: %dx%d (%dx%d)", imJpeg.imWidth, imJpeg.imHeight, imJpeg.glWidth, imJpeg.glHeight);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imJpeg.glWidth, imJpeg.glHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, imJpeg.data);
delete[] imJpeg.data;
Если нужно применить простое шифрование картинок, Вы можете вставить ф-цию расшифровки прямо в процесс чтения картинки:
static void userReadData(png_structp png_ptr, png_bytep data, png_size_t length) {
//получаем ссылку но объект который скормили png_init_io(png_ptr, file); Сюда можно передавать и собственную структуру для более сложных алгоритмов
FILE* file=(FILE*)png_get_io_ptr(png_ptr);
//читаем данные
fread(data, 1, length, file);
//незамысловато расшифровываем данные, например так
for(int i = 0; i < length; i++)
data[i] ^= 73;
}
...
png_init_io(png_ptr, file);
png_set_read_fn(png_ptr, png_get_io_ptr(png_ptr), userReadData);
...
METHODDEF (boolean)
fill_input_buffer (j_decompress_ptr cinfo)
{
my_src_ptr src = (my_src_ptr) cinfo->src;
size_t nbytes;
nbytes = JFREAD(src->infile, src->buffer, INPUT_BUF_SIZE);
if (nbytes <= 0) {
if (src->start_of_file) /* Treat empty input file as fatal error */
ERREXIT(cinfo, JERR_INPUT_EMPTY);
WARNMS(cinfo, JWRN_JPEG_EOF);
/* Insert a fake EOI marker */
src->buffer[0] = (JOCTET) 0xFF;
src->buffer[1] = (JOCTET) JPEG_EOI;
nbytes = 2;
} else {
//а вот и наша расшифорвка
int i;
for(i = 0; i < nbytes; i++)
src->buffer[i] ^= 73;
}
src->pub.next_input_byte = src->buffer;
src->pub.bytes_in_buffer = nbytes;
src->start_of_file = FALSE;
return TRUE;
}
Например приложение получило картинку по сети и картинка висит целиком в памяти.
//структура для чтения из памяти
struct mypng {
unsigned int pos;//текущая позиция в массиве
unsigned int length;//длинна массива
const char* data;//массив содержащий сжатую картинку
};
static void userReadData(png_structp png_ptr, png_bytep data, png_size_t length) {
//получаем ссылку на структуру
mypng* png = (mypng*) png_get_io_ptr(png_ptr);
//смотрим чтобы не вылезти за края массива
if (png->pos + length > png->length) length += png->pos-png->length;
if (length > 0) {
//копируем данные из массива
memcpy(data, png->data + png->pos, length);
//двигаем текущую позицию
png->pos += length;
}
}
...
mypng png = { 8, pngData, pngLength };
png_init_io(png_ptr, (FILE*) &png);
png_set_read_fn(png_ptr, png_get_io_ptr(png_ptr), userReadData);
...
/*
* jdatasrc.c
*
* Copyright (C) 1994-1996, Thomas G. Lane.
* Modified 2009-2010 by Guido Vollbeding.
* This file is part of the Independent JPEG Group's software.
* For conditions of distribution and use, see the accompanying README file.
*
* This file contains decompression data source routines for the case of
* reading JPEG data from memory or from a file (or any stdio stream).
* While these routines are sufficient for most applications,
* some will want to use a different source manager.
* IMPORTANT: we assume that fread() will correctly transcribe an array of
* JOCTETs from 8-bit-wide elements on external storage. If char is wider
* than 8 bits on your machine, you may need to do some tweaking.
*/
/* this is not a core library module, so it doesn't define JPEG_INTERNALS */
#include "jinclude.h"
#include "jpeglib.h"
#include "jerror.h"
/* Expanded data source object for stdio input */
typedef struct {
unsigned int pos;
unsigned int length;
const char* data;
} pngrd;
typedef struct {
struct jpeg_source_mgr pub; /* public fields */
pngrd* infile; /* source stream */
JOCTET * buffer; /* start of buffer */
boolean start_of_file; /* have we gotten any data yet? */
} my_source_mgr;
typedef my_source_mgr * my_src_ptr;
#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */
/*
* Initialize source --- called by jpeg_read_header
* before any data is actually read.
*/
METHODDEF(void)
init_source (j_decompress_ptr cinfo)
{
}
#if JPEG_LIB_VERSION >= 80
METHODDEF(void)
init_mem_source (j_decompress_ptr cinfo)
{
/* no work necessary here */
}
#endif
/*
* Fill the input buffer --- called whenever buffer is emptied.
*
* In typical applications, this should read fresh data into the buffer
* (ignoring the current state of next_input_byte & bytes_in_buffer),
* reset the pointer & count to the start of the buffer, and return TRUE
* indicating that the buffer has been reloaded. It is not necessary to
* fill the buffer entirely, only to obtain at least one more byte.
*
* There is no such thing as an EOF return. If the end of the file has been
* reached, the routine has a choice of ERREXIT() or inserting fake data into
* the buffer. In most cases, generating a warning message and inserting a
* fake EOI marker is the best course of action --- this will allow the
* decompressor to output however much of the image is there. However,
* the resulting error message is misleading if the real problem is an empty
* input file, so we handle that case specially.
*
* In applications that need to be able to suspend compression due to input
* not being available yet, a FALSE return indicates that no more data can be
* obtained right now, but more may be forthcoming later. In this situation,
* the decompressor will return to its caller (with an indication of the
* number of scanlines it has read, if any). The application should resume
* decompression after it has loaded more data into the input buffer. Note
* that there are substantial restrictions on the use of suspension --- see
* the documentation.
*
* When suspending, the decompressor will back up to a convenient restart point
* (typically the start of the current MCU). next_input_byte & bytes_in_buffer
* indicate where the restart point will be if the current call returns FALSE.
* Data beyond this point must be rescanned after resumption, so move it to
* the front of the buffer rather than discarding it.
*/
METHODDEF(boolean)
fill_input_buffer (j_decompress_ptr cinfo)
{
static JOCTET mybuffer[4];
/* The whole JPEG data is expected to reside in the supplied memory
* buffer, so any request for more data beyond the given buffer size
* is treated as an error.
*/
WARNMS(cinfo, JWRN_JPEG_EOF);
/* Insert a fake EOI marker */
mybuffer[0] = (JOCTET) 0xFF;
mybuffer[1] = (JOCTET) JPEG_EOI;
cinfo->src->next_input_byte = mybuffer;
cinfo->src->bytes_in_buffer = 2;
return TRUE;
}
#if JPEG_LIB_VERSION >= 80
METHODDEF(boolean)
fill_mem_input_buffer (j_decompress_ptr cinfo)
{
static JOCTET mybuffer[4];
/* The whole JPEG data is expected to reside in the supplied memory
* buffer, so any request for more data beyond the given buffer size
* is treated as an error.
*/
WARNMS(cinfo, JWRN_JPEG_EOF);
/* Insert a fake EOI marker */
mybuffer[0] = (JOCTET) 0xFF;
mybuffer[1] = (JOCTET) JPEG_EOI;
cinfo->src->next_input_byte = mybuffer;
cinfo->src->bytes_in_buffer = 2;
return TRUE;
}
#endif
/*
* Skip data --- used to skip over a potentially large amount of
* uninteresting data (such as an APPn marker).
*
* Writers of suspendable-input applications must note that skip_input_data
* is not granted the right to give a suspension return. If the skip extends
* beyond the data currently in the buffer, the buffer can be marked empty so
* that the next read will cause a fill_input_buffer call that can suspend.
* Arranging for additional bytes to be discarded before reloading the input
* buffer is the application writer's problem.
*/
METHODDEF(void)
skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
struct jpeg_source_mgr * src = cinfo->src;
/* Just a dumb implementation for now. Could use fseek() except
* it doesn't work on pipes. Not clear that being smart is worth
* any trouble anyway --- large skips are infrequent.
*/
if (num_bytes > 0) {
while (num_bytes > (long) src->bytes_in_buffer) {
num_bytes -= (long) src->bytes_in_buffer;
(void) (*src->fill_input_buffer) (cinfo);
/* note we assume that fill_input_buffer will never return FALSE,
* so suspension need not be handled.
*/
}
src->next_input_byte += (size_t) num_bytes;
src->bytes_in_buffer -= (size_t) num_bytes;
}
}
/*
* An additional method that can be provided by data source modules is the
* resync_to_restart method for error recovery in the presence of RST markers.
* For the moment, this source module just uses the default resync method
* provided by the JPEG library. That method assumes that no backtracking
* is possible.
*/
/*
* Terminate source --- called by jpeg_finish_decompress
* after all data has been read. Often a no-op.
*
* NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
* application must deal with any cleanup that should happen even
* for error exit.
*/
METHODDEF(void)
term_source (j_decompress_ptr cinfo)
{
/* no work necessary here */
}
/*
* Prepare for input from a stdio stream.
* The caller must have already opened the stream, and is responsible
* for closing it after finishing decompression.
*/
GLOBAL(void)
jpeg_stdio_src (j_decompress_ptr cinfo, FILE * infile)
{
struct jpeg_source_mgr * src;
/* The source object is made permanent so that a series of JPEG images
* can be read from the same buffer by calling jpeg_mem_src only before
* the first one.
*/
if (cinfo->src == NULL) { /* first time for this JPEG object? */
cinfo->src = (struct jpeg_source_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
SIZEOF(struct jpeg_source_mgr));
}
src = cinfo->src;
src->init_source = init_source;
src->fill_input_buffer = fill_input_buffer;
src->skip_input_data = skip_input_data;
src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
src->term_source = term_source;
src->bytes_in_buffer = (size_t) ((pngrd*)infile)->length;
src->next_input_byte = (JOCTET *) ((pngrd*)infile)->data;
}
#if JPEG_LIB_VERSION >= 80
/*
* Prepare for input from a supplied memory buffer.
* The buffer must contain the whole JPEG data.
*/
GLOBAL(void)
jpeg_mem_src (j_decompress_ptr cinfo,
unsigned char * inbuffer, unsigned long insize)
{
struct jpeg_source_mgr * src;
if (inbuffer == NULL || insize == 0) /* Treat empty input as fatal error */
ERREXIT(cinfo, JERR_INPUT_EMPTY);
/* The source object is made permanent so that a series of JPEG images
* can be read from the same buffer by calling jpeg_mem_src only before
* the first one.
*/
if (cinfo->src == NULL) { /* first time for this JPEG object? */
cinfo->src = (struct jpeg_source_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
SIZEOF(struct jpeg_source_mgr));
}
src = cinfo->src;
src->init_source = init_mem_source;
src->fill_input_buffer = fill_mem_input_buffer;
src->skip_input_data = skip_input_data;
src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
src->term_source = term_source;
src->bytes_in_buffer = (size_t) insize;
src->next_input_byte = (JOCTET *) inbuffer;
}
#endif
Использовать так:
...
mypng jpeg = { 0, jpegData, jpegLength };
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, (FILE*) &jpeg);
...
//преобразование прозрачной текстуры RGBA в RGBA4444
int len = im.glWidth * im.glHeight;
unsigned short* tmp = (unsigned short*) im.data;
for(int i = 0; i < len; i++)
tmp[i] = ((im.data[i * 4] >> 4) << 12) | ((im.data[i * 4 + 1] >> 4) << 8) | ((im.data[i * 4 + 2] >> 4) << 4) | (im.data[i * 4 + 3] >> 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, im.glWidth, im.glHeight, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, im.data);
//преобразование RGB в текстуру RGB565
int len = im.glWidth * im.glHeight;
unsigned short* tmp = (unsigned short*) im.data;
for(int i = 0; i < len; i++)
tmp[i] = ((im.data[i * row] >> 3) << 11) | ((im.data[i * row + 1] >> 2) << 5) | (im.data[i * row + 2] >> 3);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, im.glWidth, im.glHeight, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, im.data);
//из монохромной RGB/RGBA делаем одноканальную GL_LUMINANCE или GL_ALPHA
int row = HAS_ALPHA?4:3;
int len = im.glWidth * im.glHeight * row;
for(int i = 0, a = 0; i < len; i += row, a++)
im.data[a] = im.data[i];
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, im.glWidth, im.glHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, im.data);
Ничего страшного, что в цикле массив пишет сам в себя т.к. конечный массив всегда меньше чем исходный и после создания OpenGL текстуры он нам уже не нужен.
Уверен кому-то эти наработки пригодятся. Во всяком случае до этого мне приходилось кидать загруженные файлы через JNI яве, там создавать Bitmap, читать пиксели и отдавать обратно в NDK, что было на порядок затратнее и по времени, и по памяти. Кроме того все эти ф-ции можно использовать не только в Android NDK, но и в iOS/MacOS.
На всякий случай вот команды для компиляции libjpeg-turbo (libpng без проблем компилится простым добавлением папки в XCode):
MacOS
sh ../configure --host i686-apple-darwin CFLAGS='-O3 -m32' LDFLAGS=-m32
iOS ARM v7 only
sh ../configure --host arm-apple-darwin10 --enable-static --disable-shared CC="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/arm-apple-darwin10-llvm-gcc-4.2" LD="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/arm-apple-darwin10-llvm-gcc-4.2" CFLAGS="-mfloat-abi=softfp -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk -O3 -march=armv7 -mcpu=cortex-a8 -mtune=cortex-a8 -mfpu=neon" LDFLAGS="-mfloat-abi=softfp -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk -march=armv7 -mcpu=cortex-a8 -mtune=cortex-a8 -mfpu=neon"
Автор: Apetrus
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android/30421
Ссылки в тексте:
[1] http://www.zlib.net: http://www.zlib.net/
[2] http://www.libpng.org/pub/png/libpng.html: http://www.libpng.org/pub/png/libpng.html
[3] Источник: http://habrahabr.ru/post/173303/
Нажмите здесь для печати.