@ -1,24 +1,331 @@
# include "com_jme3_texture_plugins_AndroidNativeImageLoader.h"
// for __android_log_print(ANDROID_LOG_INFO, "YourApp", "formatted message");
# include <android/log.h>
# include <stddef.h>
# include <stdio.h>
# include <assert.h>
# include <string.h>
# include <time.h>
# ifdef DEBUG
# include <android/log.h>
# define LOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, \
" NativeImageLoader " , fmt , # # __VA_ARGS__ ) ;
# else
# define LOGI(fmt, ...)
# endif
# define STB_IMAGE_IMPLEMENTATION
# define STBI_NO_STDIO
# define STBI_NO_HDR
# include "stb_image.h"
typedef unsigned int uint32 ;
typedef struct
{
JNIEnv * env ;
jbyteArray tmp ;
int tmpSize ;
jobject isObject ;
jmethodID isReadMethod ;
jmethodID isSkipMethod ;
int isEOF ;
char * errorMsg ;
}
JavaInputStreamWrapper ;
static void throwIOException ( JNIEnv * env , const char * message )
{
jclass ioExClazz = ( * env ) - > FindClass ( env , " java/io/IOException " ) ;
( * env ) - > ThrowNew ( env , ioExClazz , message ) ;
}
static int InputStream_read ( void * user , char * nativeData , int nativeSize ) {
JavaInputStreamWrapper * wrapper = ( JavaInputStreamWrapper * ) user ;
JNIEnv * env = wrapper - > env ;
if ( nativeSize < = 0 )
{
wrapper - > isEOF = 1 ;
wrapper - > errorMsg = " read() requested negative or zero size " ;
return 0 ;
}
jbyteArray tmp = wrapper - > tmp ;
jint tmpSize = wrapper - > tmpSize ;
jint remaining = nativeSize ;
jint offset = 0 ;
while ( offset < nativeSize )
{
// Read data into Java array.
jint toRead = tmpSize < remaining ? tmpSize : remaining ;
jint read = ( * env ) - > CallIntMethod ( env , wrapper - > isObject ,
wrapper - > isReadMethod ,
tmp , ( jint ) 0 , ( jint ) toRead ) ;
// Check IOException
if ( ( * env ) - > ExceptionCheck ( env ) )
{
wrapper - > isEOF = 1 ;
wrapper - > errorMsg = NULL ;
return 0 ;
}
LOGI ( " InputStream->read(tmp, 0, %d) = %d " , toRead , read ) ;
// Read -1 bytes = EOF.
if ( read < 0 )
{
wrapper - > isEOF = 1 ;
wrapper - > errorMsg = NULL ;
break ;
}
else if ( read = = 0 )
{
// Read 0 bytes, give it another try.
continue ;
}
// Read 1 byte or more.
LOGI ( " memcpy(native[%d], java, %d) " , offset , read ) ;
// Copy contents of Java array to native array.
jbyte * nativeTmp = ( * env ) - > GetPrimitiveArrayCritical ( env , tmp , 0 ) ;
if ( nativeTmp = = NULL )
{
wrapper - > isEOF = 1 ;
wrapper - > errorMsg = " Failed to acquire Java array contents " ;
return 0 ;
}
memcpy ( & nativeData [ offset ] , nativeTmp , read ) ;
( * env ) - > ReleasePrimitiveArrayCritical ( env , tmp , nativeTmp , 0 ) ;
offset + = read ;
remaining - = read ;
assert ( remaining > = 0 ) ;
assert ( offset < = nativeSize ) ;
}
return offset ;
}
static void InputStream_skip ( void * user , int n ) {
JavaInputStreamWrapper * wrapper = ( JavaInputStreamWrapper * ) user ;
JNIEnv * env = wrapper - > env ;
if ( n < 0 )
{
wrapper - > isEOF = 1 ;
wrapper - > errorMsg = " Negative seek attempt detected " ;
return ;
}
else if ( n = = 0 )
{
return ;
}
// InputStream.skip(n);
jlong result = ( * env ) - > CallLongMethod ( env , wrapper - > isObject ,
wrapper - > isSkipMethod , ( jlong ) n ) ;
LOGI ( " InputStream->skip(%lld) = %lld " , ( jlong ) n , result ) ;
// IOException
if ( ( * env ) - > ExceptionCheck ( env ) )
{
wrapper - > isEOF = 1 ;
wrapper - > errorMsg = NULL ;
}
else if ( ( int ) result ! = n )
{
wrapper - > isEOF = 1 ;
wrapper - > errorMsg = " Could not skip requested number of bytes " ;
}
}
static int InputStream_eof ( void * user ) {
JavaInputStreamWrapper * wrapper = ( JavaInputStreamWrapper * ) user ;
LOGI ( " InputStream->eof() = %s " , wrapper - > isEOF ? " true " : " false " ) ;
return wrapper - > isEOF ;
}
static stbi_io_callbacks JavaInputStreamCallbacks = {
InputStream_read ,
InputStream_skip ,
InputStream_eof ,
} ;
static JavaInputStreamWrapper createInputStreamWrapper ( JNIEnv * env , jobject is , jbyteArray tmpArray )
{
JavaInputStreamWrapper wrapper ;
jclass inputStreamClass = ( * env ) - > FindClass ( env , " java/io/InputStream " ) ;
wrapper . env = env ;
wrapper . isObject = is ;
wrapper . isEOF = 0 ;
wrapper . errorMsg = NULL ;
wrapper . isReadMethod = ( * env ) - > GetMethodID ( env , inputStreamClass , " read " , " ([BII)I " ) ;
wrapper . isSkipMethod = ( * env ) - > GetMethodID ( env , inputStreamClass , " skip " , " (J)J " ) ;
wrapper . tmp = ( jbyteArray ) tmpArray ;
wrapper . tmpSize = ( * env ) - > GetArrayLength ( env , tmpArray ) ;
return wrapper ;
}
static jobject createJmeImage ( JNIEnv * env , int width , int height , int comps , char * data )
{
// Convert # of components to jME format.
jclass formatClass = ( * env ) - > FindClass ( env , " com/jme3/texture/Image$Format " ) ;
jfieldID formatFieldID ;
switch ( comps )
{
case 1 :
formatFieldID = ( * env ) - > GetStaticFieldID ( env , formatClass ,
" Luminance8 " , " Lcom/jme3/texture/Image$Format; " ) ;
break ;
case 2 :
formatFieldID = ( * env ) - > GetStaticFieldID ( env , formatClass ,
" Luminance8Alpha8 " , " Lcom/jme3/texture/Image$Format; " ) ;
break ;
case 3 :
formatFieldID = ( * env ) - > GetStaticFieldID ( env , formatClass ,
" RGB8 " , " Lcom/jme3/texture/Image$Format; " ) ;
break ;
case 4 :
formatFieldID = ( * env ) - > GetStaticFieldID ( env , formatClass ,
" RGBA8 " , " Lcom/jme3/texture/Image$Format; " ) ;
break ;
default :
throwIOException ( env , " Unrecognized number of components " ) ;
return NULL ;
}
jobject formatVal = ( * env ) - > GetStaticObjectField ( env , formatClass , formatFieldID ) ;
// Get colorspace sRGB
jclass colorSpaceClass = ( * env ) - > FindClass ( env , " com/jme3/texture/image/ColorSpace " ) ;
jfieldID sRGBFieldID = ( * env ) - > GetStaticFieldID ( env , colorSpaceClass ,
" sRGB " , " Lcom/jme3/texture/image/ColorSpace; " ) ;
jobject sRGBVal = ( * env ) - > GetStaticObjectField ( env , colorSpaceClass , sRGBFieldID ) ;
int size = width * height * comps ;
// Stick it in a ByteBuffer
jobject directBuffer = ( * env ) - > NewDirectByteBuffer ( env , data , size ) ;
if ( directBuffer = = NULL )
{
throwIOException ( env , " Failed to allocate ByteBuffer " ) ;
return NULL ;
}
// Create JME image.
jclass jmeImageClass = ( * env ) - > FindClass ( env , " com/jme3/texture/Image " ) ;
// Image(Format format, int width, int height, ByteBuffer data, ColorSpace colorSpace)
jmethodID newImageMethod = ( * env ) - > GetMethodID ( env , jmeImageClass , " <init> " ,
" (Lcom/jme3/texture/Image$Format;IILjava/nio/ByteBuffer;Lcom/jme3/texture/image/ColorSpace;)V " ) ;
jobject jmeImage = ( * env ) - > NewObject ( env , jmeImageClass , newImageMethod ,
formatVal , ( jint ) width , ( jint ) height ,
directBuffer , sRGBVal ) ;
return jmeImage ;
}
static void flipImage ( int scanline , int height , char * data )
{
char tmp [ scanline ] ;
for ( int y = 0 ; y < height / 2 ; y + + )
{
int oppY = height - y - 1 ;
int yOff = y * scanline ;
int oyOff = oppY * scanline ;
// Copy scanline at Y to tmp
memcpy ( tmp , & data [ yOff ] , scanline ) ;
// Copy data at opposite Y to Y
memcpy ( & data [ yOff ] , & data [ oyOff ] , scanline ) ;
// Copy tmp to opposite Y
memcpy ( & data [ oyOff ] , tmp , scanline ) ;
}
}
JNIEXPORT jobject JNICALL Java_com_jme3_texture_plugins_AndroidNativeImageLoader_load
( JNIEnv * env , jobject thisObj , jobject inputStream , jboolean flipY , jbyteArray tmpArray )
{
JavaInputStreamWrapper wrapper = createInputStreamWrapper ( env , inputStream , tmpArray ) ;
stbi_uc * imageData ;
int width , height , comps ;
LOGI ( " stbi_load_from_callbacks " ) ;
imageData = stbi_load_from_callbacks ( & JavaInputStreamCallbacks , & wrapper , & width , & height , & comps , STBI_default ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
{
// IOException
goto problems ;
}
else if ( wrapper . errorMsg ! = NULL )
{
// Misc error
throwIOException ( env , wrapper . errorMsg ) ;
goto problems ;
}
else if ( imageData = = NULL )
{
// STBI error
throwIOException ( env , stbi_failure_reason ( ) ) ;
goto problems ;
}
// No IOExceptions or errors encountered. We have image data!
// Maybe we need to flip it.
LOGI ( " Flipping image " ) ;
if ( flipY )
{
flipImage ( width * comps , height , imageData ) ;
}
// Create the jME3 image.
LOGI ( " Creating jME3 image " ) ;
return createJmeImage ( env , width , height , comps , imageData ) ;
problems :
if ( imageData ! = NULL )
{
stbi_image_free ( imageData ) ;
}
return NULL ;
}
JNIEXPORT jobject JNICALL Java_com_jme3_texture_plugins_AndroidNativeImageLoader_getFailureReason
( JNIEnv * env , jclass clazz )
{
return stbi_failure_reason ( ) ;
return NULL ;
}
JNIEXPORT jint JNICALL Java_com_jme3_texture_plugins_AndroidNativeImageLoader_getImageInfo
( JNIEnv * env , jclass clazz , jobject inBuffer , jint bufSize , jobject outBuffer , jint outSize )
{
return 0 ;
}
JNIEXPORT jint JNICALL Java_com_jme3_texture_plugins_AndroidNativeImageLoader_decodeBuffer
( JNIEnv * env , jclass clazz , jobject inBuffer , jint inSize , jboolean flipY , jobject outBuffer , jint outSize )
{
return 0 ;
}
/*
JNIEXPORT jobject JNICALL Java_com_jme3_texture_plugins_AndroidNativeImageLoader_getFailureReason
( JNIEnv * env , jclass clazz )
{
return stbi_failure_reason ( ) ;
}
JNIEXPORT jint JNICALL Java_com_jme3_texture_plugins_AndroidNativeImageLoader_getImageInfo
( JNIEnv * env , jclass clazz , jobject inBuffer , jint bufSize , jobject outBuffer , jint outSize )
@ -124,3 +431,4 @@ JNIEXPORT jint JNICALL Java_com_jme3_texture_plugins_AndroidNativeImageLoader_de
// stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
}
*/