Загрузка 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, 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"

    Update:
    Библиотеку zlib можно подключить родную из NDK. Для этого в Android.mk надо убрать блок, который касается zlib и прописать:
    LOCAL_LDLIBS    := -llog -lGLESv1_CM -lz
    

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 10

      0
      C 2.3 уже есть AAssetManager, так что zlib уже ни к чему.
        0
        В данном случае zlib нужен для успешной компиляции libpng
          0
          libpng независимая библиотека, zlib для компиляции не нужен.
            +1
            На главной странице написано, что нужен. Без zlib будет ругаться на png.c:114: error: undefined reference to 'crc32'
              +1
              Я сначала подумал, что вы имели ввиду libzip.
              С другой же стороны, zlib уже присутствует в ndk, зачем вы её сами компилите, не понимаю)
                0
                И вправду незачем :) Что-то я забыл об этом, можно вместо zlib просто добавить
                LOCAL_LDLIBS += -lz
        0
        Я понимаю, что прошло почти 2 года, но как можно скомпилять вариант либы для android-x86?
          0
          Тогда надо добавлять в код исходники libpng и libjpeg и компилить под x86 как часть вашего кода, но, на сколько я помню, с первого раза скомпилить не получится. Сейчас я настоятельно рекомендую использовать формат webp и соответственно либу libwebp, которая легко компилится под android/ios/x86 etc включая 32/64bit, к тому же конечный размер файлов меньше, а настроек сжатия больше.
            0
            Дело в том, что я использую libjpeg-turbo не для OpenGL, а для обработки фотографий. И команды типа этой:
            /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
            нормально компиляют для armeabi-v7a и armeabi, а для x86 не получается :(
              0
              Быстрый поиск выдал мне эту страничку, на которой есть заметки по x86, надеюсь это подойдет.

        Only users with full accounts can post comments. Log in, please.