Загрузка PNG и JPEG картинок в Android NDK

в 11:00, , рубрики: android, game development, jpeg, PNG, Разработка под android, метки: , , ,

Загрузка PNG и JPEG картинок в Android NDKПриветствую всех читателей!
В этой статье речь пойдет о том, как в Android NDK загрузить PNG и JPEG картинки из файла или из памяти, а также немного полезного кода для скармливания этих картинок OpenGL.

Скачать

Для загрузки картинок мы будем использовать следующие библиотеки:

Распаковать

Разложим все аккуратно по папкам, например создадим папку 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)

Чтение картинок

В Вашем 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, pngData, pngLength };
    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, jpegData, jpegLength };
    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"

Автор: Apetrus

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js