add native android lib for loading png and gif files. Does not premultiply. Uses stb_image (http://www.nothings.org/stb_image.c)
This commit is contained in:
parent
b94a20db14
commit
ee8e42f921
2
.gitignore
vendored
2
.gitignore
vendored
@ -68,3 +68,5 @@
|
||||
/sdk/JME3TestsTemplateAndroid/src/jme3test/
|
||||
/sdk/JME3TestsTemplate/src/jme3test/
|
||||
/jme3-ios/build/
|
||||
/jme3-android-native/openal-soft/
|
||||
/jme3-android-native/OpenALSoft.zip
|
@ -49,5 +49,5 @@ ext {
|
||||
|
||||
// add each native lib build file
|
||||
apply from: file('openalsoft.gradle')
|
||||
//apply from: file('stb_image.gradle')
|
||||
apply from: file('stb_image.gradle')
|
||||
|
||||
|
13
jme3-android-native/src/native/jme_stbi/Android.mk
Normal file
13
jme3-android-native/src/native/jme_stbi/Android.mk
Normal file
@ -0,0 +1,13 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := stbijme
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)
|
||||
LOCAL_CFLAGS += -O2
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
|
||||
LOCAL_SRC_FILES := $(subst $(LOCAL_PATH)/,, $(wildcard $(LOCAL_PATH)/*.c))
|
||||
|
||||
#adds zlib
|
||||
LOCAL_LDLIBS += -lz -llog
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
3
jme3-android-native/src/native/jme_stbi/Application.mk
Normal file
3
jme3-android-native/src/native/jme_stbi/Application.mk
Normal file
@ -0,0 +1,3 @@
|
||||
APP_OPTIM := release
|
||||
APP_ABI := all
|
||||
#APP_ABI := armeabi-v7a
|
@ -0,0 +1,125 @@
|
||||
#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>
|
||||
#define STBI_HEADER_FILE_ONLY
|
||||
#include "stb_image.c"
|
||||
|
||||
typedef unsigned int uint32;
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
stbi_uc* pInBuffer = (stbi_uc*) (*env)->GetDirectBufferAddress(env, inBuffer);
|
||||
stbi_uc* pOutBuffer = (stbi_uc*) (*env)->GetDirectBufferAddress(env, outBuffer);
|
||||
uint32 width, height, comp;
|
||||
|
||||
uint32 result = stbi_info_from_memory(pInBuffer, bufSize, &width, &height, &comp);
|
||||
if (result == 1) {
|
||||
uint32 numBytes = (width) * (height) * (comp);
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, "NativeImageLoader", "getImageInfo width: %d", width);
|
||||
__android_log_print(ANDROID_LOG_INFO, "NativeImageLoader", "getImageInfo height: %d", height);
|
||||
__android_log_print(ANDROID_LOG_INFO, "NativeImageLoader", "getImageInfo comp: %d", comp);
|
||||
__android_log_print(ANDROID_LOG_INFO, "NativeImageLoader", "getImageInfo data size: %d", numBytes);
|
||||
|
||||
// each element is a 4 byte int
|
||||
if (outSize != 12) {
|
||||
return 2;
|
||||
}
|
||||
memcpy(pOutBuffer+0, &width, 4);
|
||||
memcpy(pOutBuffer+4, &height, 4);
|
||||
memcpy(pOutBuffer+8, &comp, 4);
|
||||
|
||||
return (jint) 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_com_jme3_texture_plugins_AndroidNativeImageLoader_decodeBuffer
|
||||
(JNIEnv * env, jclass clazz, jobject inBuffer, jint inSize, jboolean flipY, jobject outBuffer, jint outSize)
|
||||
{
|
||||
stbi_uc* pInBuffer = (stbi_uc*) (*env)->GetDirectBufferAddress(env, inBuffer);
|
||||
stbi_uc* pOutBuffer = (stbi_uc*) (*env)->GetDirectBufferAddress(env, outBuffer);
|
||||
uint32 width, height, comp;
|
||||
uint32 req_comp = 0;
|
||||
|
||||
stbi_uc* pData = stbi_load_from_memory(pInBuffer, inSize, &width, &height, &comp, req_comp);
|
||||
if(pData == NULL) {
|
||||
return 1;
|
||||
}
|
||||
uint32 numBytes = width * height * comp;
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, "NativeImageLoader", "decodeBuffer width: %d", width);
|
||||
__android_log_print(ANDROID_LOG_INFO, "NativeImageLoader", "decodeBuffer height: %d", height);
|
||||
__android_log_print(ANDROID_LOG_INFO, "NativeImageLoader", "decodeBuffer comp: %d", comp);
|
||||
__android_log_print(ANDROID_LOG_INFO, "NativeImageLoader", "decodeBuffer data size: %d", numBytes);
|
||||
|
||||
if (numBytes != outSize) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
int i;
|
||||
// for (i=0; i<outSize; i+=4) {
|
||||
// __android_log_print(ANDROID_LOG_INFO, "NativeImageLoader",
|
||||
// "pData byte[%d] r: %02x, g: %02x, b: %02x, a: %02x",
|
||||
// i, *(pData+i), *(pData+i+1), *(pData+i+2), *(pData+i+3));
|
||||
// }
|
||||
|
||||
if (!flipY) {
|
||||
memcpy(pOutBuffer, pData, outSize);
|
||||
stbi_image_free(pData);
|
||||
return 0;
|
||||
} else {
|
||||
uint32 yNew = 0;
|
||||
uint32 yOrig = 0;
|
||||
// stb_image always outputs in bpp = 8
|
||||
uint32 bytesPerLine = (width * comp);
|
||||
stbi_uc* newData = (stbi_uc *) malloc(bytesPerLine * height);
|
||||
if (newData == NULL) {
|
||||
stbi_image_free(pData);
|
||||
return 3;
|
||||
}
|
||||
|
||||
for (yOrig = 0; yOrig < height; yOrig++){
|
||||
yNew = height - yOrig - 1;
|
||||
// __android_log_print(ANDROID_LOG_INFO, "NativeImageLoader", "yOrig: %d, yNew: %d, bytes: %d", yOrig, yNew, bytesPerLine);
|
||||
memcpy(newData + (yNew * bytesPerLine), pData + (yOrig * bytesPerLine), bytesPerLine);
|
||||
}
|
||||
|
||||
// for (i=0; i<outSize; i+=4) {
|
||||
// __android_log_print(ANDROID_LOG_INFO, "NativeImageLoader",
|
||||
// "newData byte[%d] r: %02x, g: %02x, b: %02x, a: %02x",
|
||||
// i, *(newData+i), *(newData+i+1), *(newData+i+2), *(newData+i+3));
|
||||
// }
|
||||
|
||||
memcpy(pOutBuffer, newData, outSize);
|
||||
|
||||
// for (i=0; i<outSize; i+=4) {
|
||||
// __android_log_print(ANDROID_LOG_INFO, "NativeImageLoader",
|
||||
// "pOutBuffer byte[%d] r: %02x, g: %02x, b: %02x, a: %02x",
|
||||
// i, *(pOutBuffer+i), *(pOutBuffer+i+1), *(pOutBuffer+i+2), *(pOutBuffer+i+3));
|
||||
// }
|
||||
|
||||
stbi_image_free(pData);
|
||||
free(newData);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
// stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
|
||||
}
|
66
jme3-android-native/stb_image.gradle
Normal file
66
jme3-android-native/stb_image.gradle
Normal file
@ -0,0 +1,66 @@
|
||||
// stb_image is not downloaded. The single source file is included in the repo
|
||||
String stbiFolder = 'stb_image'
|
||||
|
||||
//Working directory for the ndk build.
|
||||
//Must be the parent directory of the jni directory
|
||||
//Libs directory (output of ndk) will be created in this directory as well
|
||||
String stbiBuildDir = "${buildDir}" + File.separator + 'stb_image'
|
||||
|
||||
// jME Android Native source files path
|
||||
String stbiJmeAndroidPath = 'src/native/jme_stbi'
|
||||
|
||||
// Copy stb_image files to jni directory
|
||||
task copyStbiFiles(type: Copy) {
|
||||
def sourceDir = file(stbiFolder)
|
||||
def outputDir = file(stbiBuildDir + File.separator + 'jni')
|
||||
// println "copyStbiFiles sourceDir: " + sourceDir
|
||||
// println "copyStbiFiles outputDir: " + outputDir
|
||||
|
||||
from sourceDir
|
||||
into outputDir
|
||||
}
|
||||
|
||||
// Copy jME Android native files to jni directory
|
||||
task copyStbiJmeFiles(type: Copy, dependsOn:copyStbiFiles) {
|
||||
def sourceDir = file(stbiJmeAndroidPath)
|
||||
def outputDir = file(stbiBuildDir + File.separator + 'jni')
|
||||
// println "copyStbiJmeFiles sourceDir: " + sourceDir
|
||||
// println "copyStbiJmeFiles outputDir: " + outputDir
|
||||
|
||||
from sourceDir
|
||||
into outputDir
|
||||
}
|
||||
|
||||
jar.into("lib") { from stbiBuildDir + File.separator + 'libs' }
|
||||
|
||||
task generateStbiHeaders(dependsOn:copyStbiJmeFiles) {
|
||||
String destDir = stbiBuildDir + File.separator + 'jni'
|
||||
String classes = ""
|
||||
.concat("com.jme3.texture.plugins.AndroidNativeImageLoader, ")
|
||||
|
||||
// println "stb_image classes = " + classes
|
||||
// println "stb_image destDir = " + destDir
|
||||
// println "stb_image classpath = " + project.projectClassPath
|
||||
|
||||
ant.javah(
|
||||
classpath: project.projectClassPath,
|
||||
destdir: destDir,
|
||||
class: classes
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
task buildStbiNativeLib(type: Exec, dependsOn: generateStbiHeaders) {
|
||||
// println "stb_image build dir: " + buildLibDir
|
||||
// println "ndkCommandPath: " + project.ndkCommandPath
|
||||
args 'TARGET_PLATFORM=android-9'
|
||||
workingDir stbiBuildDir
|
||||
executable project.ndkCommandPath
|
||||
}
|
||||
|
||||
compileJava.dependsOn {
|
||||
// ndkPath is defined in the root project gradle.properties file
|
||||
if (ndkCommandPath != null && new File(ndkCommandPath).exists()) {
|
||||
buildStbiNativeLib
|
||||
}
|
||||
}
|
4673
jme3-android-native/stb_image/stb_image.c
Normal file
4673
jme3-android-native/stb_image/stb_image.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,163 @@
|
||||
package com.jme3.texture.plugins;
|
||||
|
||||
import com.jme3.asset.AssetInfo;
|
||||
import com.jme3.asset.AssetLoadException;
|
||||
import com.jme3.asset.AssetLoader;
|
||||
import com.jme3.asset.TextureKey;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Native image loader to deal with filetypes that support alpha channels.
|
||||
* The Android Bitmap class premultiplies the channels by the alpha when
|
||||
* loading. This loader does not.
|
||||
*
|
||||
* @author iwgeric
|
||||
*/
|
||||
public class AndroidNativeImageLoader implements AssetLoader {
|
||||
private static final Logger logger = Logger.getLogger(AndroidNativeImageLoader.class.getName());
|
||||
|
||||
public Image load(InputStream in, boolean flipY) throws IOException{
|
||||
int result;
|
||||
byte[] bytes = getBytes(in);
|
||||
int origSize = bytes.length;
|
||||
// logger.log(Level.INFO, "png file length: {0}", size);
|
||||
|
||||
ByteBuffer origDataBuffer = BufferUtils.createByteBuffer(origSize);
|
||||
origDataBuffer.clear();
|
||||
origDataBuffer.put(bytes, 0, origSize);
|
||||
origDataBuffer.flip();
|
||||
|
||||
int headerSize = 12;
|
||||
ByteBuffer headerDataBuffer = BufferUtils.createByteBuffer(headerSize);
|
||||
headerDataBuffer.asIntBuffer();
|
||||
headerDataBuffer.clear();
|
||||
|
||||
result = getImageInfo(origDataBuffer, origSize, headerDataBuffer, headerSize);
|
||||
if (result != 0) {
|
||||
logger.log(Level.SEVERE, "Image header could not be read: {0}", getFailureReason());
|
||||
return null;
|
||||
}
|
||||
headerDataBuffer.rewind();
|
||||
|
||||
// logger.log(Level.INFO, "image header size: {0}", headerDataBuffer.capacity());
|
||||
// int position = 0;
|
||||
// while (headerDataBuffer.position() < headerDataBuffer.capacity()) {
|
||||
// int value = headerDataBuffer.getInt();
|
||||
// logger.log(Level.INFO, "position: {0}, value: {1}",
|
||||
// new Object[]{position, value});
|
||||
// position++;
|
||||
// }
|
||||
// headerDataBuffer.rewind();
|
||||
|
||||
|
||||
int width = headerDataBuffer.getInt();
|
||||
int height = headerDataBuffer.getInt();
|
||||
int numComponents = headerDataBuffer.getInt();
|
||||
int imageDataSize = width * height * numComponents;
|
||||
// logger.log(Level.INFO, "width: {0}, height: {1}, numComponents: {2}, imageDataSize: {3}",
|
||||
// new Object[]{width, height, numComponents, imageDataSize});
|
||||
|
||||
ByteBuffer imageDataBuffer = BufferUtils.createByteBuffer(imageDataSize);
|
||||
imageDataBuffer.clear();
|
||||
|
||||
result = decodeBuffer(origDataBuffer, origSize, flipY, imageDataBuffer, imageDataSize);
|
||||
if (result != 0) {
|
||||
logger.log(Level.SEVERE, "Image could not be decoded: {0}", getFailureReason());
|
||||
return null;
|
||||
}
|
||||
imageDataBuffer.rewind();
|
||||
|
||||
// logger.log(Level.INFO, "png outSize: {0}", imageDataBuffer.capacity());
|
||||
// int pixelNum = 0;
|
||||
// while (imageDataBuffer.position() < imageDataBuffer.capacity()) {
|
||||
// short r = (short) (imageDataBuffer.get() & 0xFF);
|
||||
// short g = (short) (imageDataBuffer.get() & 0xFF);
|
||||
// short b = (short) (imageDataBuffer.get() & 0xFF);
|
||||
// short a = (short) (imageDataBuffer.get() & 0xFF);
|
||||
// logger.log(Level.INFO, "pixel: {0}, r: {1}, g: {2}, b: {3}, a: {4}",
|
||||
// new Object[]{pixelNum, r, g, b, a});
|
||||
// pixelNum++;
|
||||
// }
|
||||
// imageDataBuffer.rewind();
|
||||
|
||||
BufferUtils.destroyDirectBuffer(origDataBuffer);
|
||||
BufferUtils.destroyDirectBuffer(headerDataBuffer);
|
||||
|
||||
Image img = new Image(getImageFormat(numComponents), width, height, imageDataBuffer);
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
public Image load(AssetInfo info) throws IOException {
|
||||
// logger.log(Level.INFO, "Loading texture: {0}", ((TextureKey)info.getKey()).toString());
|
||||
boolean flip = ((TextureKey) info.getKey()).isFlipY();
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = info.openStream();
|
||||
Image img = load(in, flip);
|
||||
if (img == null){
|
||||
throw new AssetLoadException("The given image cannot be loaded " + info.getKey());
|
||||
}
|
||||
return img;
|
||||
} finally {
|
||||
if (in != null){
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Image.Format getImageFormat(int stbiNumComponents) {
|
||||
// stb_image always returns 8 bit components
|
||||
// N=#comp components
|
||||
// 1 grey
|
||||
// 2 grey, alpha
|
||||
// 3 red, green, blue
|
||||
// 4 red, green, blue, alpha
|
||||
Image.Format format = null;
|
||||
|
||||
if (stbiNumComponents == 1) {
|
||||
format = Image.Format.Luminance8;
|
||||
} else if (stbiNumComponents == 2) {
|
||||
format = Image.Format.Luminance8Alpha8;
|
||||
} else if (stbiNumComponents == 3) {
|
||||
format = Image.Format.RGB8;
|
||||
} else if (stbiNumComponents == 4) {
|
||||
format = Image.Format.RGBA8;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Format returned by stbi is not valid. Returned value: " + stbiNumComponents);
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
public static byte[] getBytes(InputStream input) throws IOException {
|
||||
byte[] buffer = new byte[32768];
|
||||
int bytesRead;
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
while ((bytesRead = input.read(buffer)) != -1)
|
||||
{
|
||||
os.write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
byte[] output = os.toByteArray();
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Load jni .so on initialization */
|
||||
static {
|
||||
System.loadLibrary("stbijme");
|
||||
}
|
||||
|
||||
private static native int getImageInfo(ByteBuffer inBuffer, int inSize, ByteBuffer outBuffer, int outSize);
|
||||
private static native int decodeBuffer(ByteBuffer inBuffer, int inSize, boolean flipY, ByteBuffer outBuffer, int outSize);
|
||||
private static native String getFailureReason();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user