Приветствую всех читателей!
В этой статье речь пойдет о том, как в Android NDK загрузить PNG и JPEG картинки из файла или из памяти, а также немного полезного кода для скармливания этих картинок OpenGL.
Для загрузки картинок мы будем использовать следующие библиотеки:
Разложим все аккуратно по папкам, например создадим папку modules в корне проекта, там же где находится папка jni. Распакуем:
PROJECT/jni
PROJECT/modules/png
PROJECT/modules/jpeg
PROJECT/modules/zlib
В Вашем C++ коде (у меня это файл loadimage.cpp) делаем следующее:
Тестируем загрузку PNG картинки с карты памяти:
Аналогично делаем с JPEG, учитывая что JPEG всегда без прозрачности
Если нужно применить простое шифрование картинок, Вы можете вставить ф-цию расшифровки прямо в процесс чтения картинки:
Например приложение получило картинку по сети и картинка висит целиком в памяти.
Ничего страшного, что в цикле массив пишет сам в себя т.к. конечный массив всегда меньше чем исходный и после создания OpenGL текстуры он нам уже не нужен.
Уверен кому-то эти наработки пригодятся. Во всяком случае до этого мне приходилось кидать загруженные файлы через JNI яве, там создавать Bitmap, читать пиксели и отдавать обратно в NDK, что было на порядок затратнее и по времени, и по памяти. Кроме того все эти ф-ции можно использовать не только в Android NDK, но и в iOS/MacOS.
На всякий случай вот команды для компиляции libjpeg-turbo (libpng без проблем компилится простым добавлением папки в XCode):
Update:
Библиотеку zlib можно подключить родную из NDK. Для этого в Android.mk надо убрать блок, который касается zlib и прописать:
В этой статье речь пойдет о том, как в Android NDK загрузить PNG и JPEG картинки из файла или из памяти, а также немного полезного кода для скармливания этих картинок OpenGL.
Скачать
Для загрузки картинок мы будем использовать следующие библиотеки:
- zlib — http://www.zlib.net
- pnglib — http://www.libpng.org/pub/png/libpng.html
- libjpeg-turbo — git clone git://git.linaro.org/people/tomgall/libjpeg-turbo/libjpeg-turbo.git -b linaro-android
Распаковать
Разложим все аккуратно по папкам, например создадим папку modules в корне проекта, там же где находится папка jni. Распакуем:
- pnglib в папку modules/png
- libjpeg-turbo в modules/jpeg
- zlib в modules/zlib
PROJECT/jni
PROJECT/modules/png
PROJECT/modules/jpeg
PROJECT/modules/zlib
Настроить
- Копируем PROJECT/modules/png/scripts/pnglibconf.h.prebuilt в PROJECT/modules/png/pnglibconf.h
- Открываем файл modules/jpeg/Android.mk и на 70й строке, после LOCAL_MODULE := libjpeg, добавляем:
ifeq ($(notdir $(MAKECMDGOALS)),libjpeg.a) LOCAL_SRC_FILES += $(libsimd_SOURCES_DIST) include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := dummy endif
- И в 11й строке исправляем перенос строки
ifeq ($(ARCH_ARM_HAVE_NEON),true) LOCAL_CFLAGS += -D__ARM_HAVE_NEON endif
- В терминале идем в папку modules/jpeg и выполняем следующие команды, чтобы скомпилить библиотеки для armv6 и armv7
/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
Правим Android.mk
В Android.mk соединяем все библиотеки вместе
LOCAL_PATH := $(call my-dir)
HEADERS:=
STATICLIBS:=
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)
HEADERS:=
STATICLIBS:=
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;
Чтение картинок с шифрованием
Если нужно применить простое шифрование картинок, Вы можете вставить ф-цию расшифровки прямо в процесс чтения картинки:
- Для PNG надо указать собственную ф-цию для чтения:Скрытый текст
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); ...
- Для JPEG немного сложнее т.к. надо перекомпилить библиотеки. Открываем jdatasrc.c и меняем следующую ф-цию:Скрытый текст
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; }
Чтение картинок из памяти
Например приложение получило картинку по сети и картинка висит целиком в памяти.
- Для PNG мы заменяем ф-цию чтения из файла, ф-цией копирования из массива: Скрытый текст
//структура для чтения из памяти 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, pngLength, pngData }; png_init_io(png_ptr, (FILE*) &png); png_set_read_fn(png_ptr, png_get_io_ptr(png_ptr), userReadData); ...
- Для JPEG замените файл jdatasrc.c на следующий и перекомпильте библиотеки:
Скрытый текстИспользовать так:/* * 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, jpegLength, jpegData }; jpeg_create_decompress(&cinfo); jpeg_stdio_src(&cinfo, (FILE*) &jpeg); ...
OpenGL плюшки
//преобразование прозрачной текстуры 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):
Скрытый текст
cd {source_directory}
autoreconf -fiv
mkdir build
cd build
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"
autoreconf -fiv
mkdir build
cd build
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"
Update:
Библиотеку zlib можно подключить родную из NDK. Для этого в Android.mk надо убрать блок, который касается zlib и прописать:
LOCAL_LDLIBS := -llog -lGLESv1_CM -lz