Commit 2f53b53f authored by Gaëtan Renaudeau's avatar Gaëtan Renaudeau

using Fresco for image management

parent 3430bf61
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":rngl" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/../Examples/Simple/android" external.system.id="GRADLE" external.system.module.group="Simple" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":rngl" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<afterSyncTasks>
<task>generateDebugAndroidTestSources</task>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
<option name="LIBRARY_PROJECT" value="true" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/annotations" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="okhttp-ws-2.4.0" level="project" />
<orderEntry type="library" exported="" name="library-2.4.0" level="project" />
<orderEntry type="library" exported="" name="okhttp-2.4.0" level="project" />
<orderEntry type="library" exported="" name="jsr305-3.0.0" level="project" />
<orderEntry type="library" exported="" name="jackson-core-2.2.3" level="project" />
<orderEntry type="library" exported="" name="fbcore-0.6.1" level="project" />
<orderEntry type="library" exported="" name="react-native-0.14.0" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-23.0.1" level="project" />
<orderEntry type="library" exported="" name="imagepipeline-okhttp-0.6.1" level="project" />
<orderEntry type="library" exported="" name="android-jsc-r174650" level="project" />
<orderEntry type="library" exported="" name="imagepipeline-0.6.1" level="project" />
<orderEntry type="library" exported="" name="fresco-0.6.1" level="project" />
<orderEntry type="library" exported="" name="bolts-android-1.1.4" level="project" />
<orderEntry type="library" exported="" name="support-v4-23.0.1" level="project" />
<orderEntry type="library" exported="" name="okio-1.5.0" level="project" />
<orderEntry type="library" exported="" name="drawee-0.6.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.0.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-23.0.1" level="project" />
</component>
</module>
\ No newline at end of file
......@@ -9,6 +9,7 @@ import android.opengl.GLSurfaceView;
import android.util.DisplayMetrics;
import android.view.ViewGroup;
import com.facebook.imagepipeline.core.ExecutorSupplier;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
......@@ -28,13 +29,12 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Executor;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class GLCanvas
extends GLSurfaceView
implements GLSurfaceView.Renderer, RunInGLThread {
public class GLCanvas extends GLSurfaceView implements GLSurfaceView.Renderer, Executor {
private ReactContext reactContext;
private RNGLContext rnglContext;
......@@ -55,10 +55,13 @@ public class GLCanvas
private Map<Integer, GLShader> shaders;
private Map<Integer, GLFBO> fbos;
private ExecutorSupplier executorSupplier;
private final Queue<Runnable> mRunOnDraw = new LinkedList<>();
public GLCanvas(ThemedReactContext context) {
public GLCanvas(ThemedReactContext context, ExecutorSupplier executorSupplier) {
super(context);
reactContext = context;
this.executorSupplier = executorSupplier;
rnglContext = context.getNativeModule(RNGLContext.class);
setEGLContextClientVersion(2);
......@@ -110,7 +113,7 @@ public class GLCanvas
if (contentTextures.size() != this.nbContentTextures)
resizeUniformContentTextures(nbContentTextures);
syncEventsThrough(); // FIXME, really need to do this ?
syncEventsThrough(); // FIXME: need to do this here?
if (!preloadingDone) {
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
......@@ -219,8 +222,8 @@ public class GLCanvas
// Sync methods
private final Queue<Runnable> mRunOnDraw = new LinkedList<>();
public void runInGLThread (final Runnable runnable) {
@Override
public void execute (final Runnable runnable) {
synchronized (mRunOnDraw) {
mRunOnDraw.add(runnable);
requestRender();
......@@ -235,7 +238,7 @@ public class GLCanvas
}
public void requestSyncData () {
runInGLThread(new Runnable() {
execute(new Runnable() {
public void run() {
if (ensureCompiledShader(data))
syncData();
......@@ -343,7 +346,7 @@ public class GLCanvas
return resolveSrc(src);
}
public GLRenderData recSyncData (GLData data, HashMap<Uri, GLImage> images) {
private GLRenderData recSyncData (GLData data, HashMap<Uri, GLImage> images) {
Map<Uri, GLImage> prevImages = this.images;
GLShader shader = getShader(data.shader);
......@@ -411,7 +414,7 @@ public class GLCanvas
images.put(src, image);
}
if (image == null) {
image = new GLImage(reactContext.getApplicationContext(), this, new Runnable() {
image = new GLImage(this, executorSupplier.forDecode(), new Runnable() {
public void run() {
onImageLoad(src);
}
......@@ -561,14 +564,14 @@ public class GLCanvas
}
}
public void syncData () {
private void syncData () {
if (data == null) return;
HashMap<Uri, GLImage> images = new HashMap<>();
renderData = recSyncData(data, images);
this.images = images;
}
public void recRender (GLRenderData renderData) {
private void recRender (GLRenderData renderData) {
DisplayMetrics dm = reactContext.getResources().getDisplayMetrics();
int w = Float.valueOf(renderData.width.floatValue() * dm.density).intValue();
......@@ -618,7 +621,7 @@ public class GLCanvas
glDrawArrays(GL_TRIANGLES, 0, 6);
}
public void render () {
private void render () {
if (renderData == null) return;
syncContentTextures();
......@@ -631,13 +634,13 @@ public class GLCanvas
glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
}
public void syncEventsThrough () {
private void syncEventsThrough () {
// TODO: figure out how to do this...
// For some reason, the click through is half working
}
private void dispatchOnProgress(double progress, int count, int total) {
private void dispatchOnProgress (double progress, int count, int total) {
WritableMap event = Arguments.createMap();
event.putDouble("progress", progress);
event.putInt("count", count);
......@@ -649,7 +652,7 @@ public class GLCanvas
event);
}
public void dispatchOnLoad () {
private void dispatchOnLoad () {
WritableMap event = Arguments.createMap();
ReactContext reactContext = (ReactContext)getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
......
......@@ -2,19 +2,23 @@ package com.projectseptember.RNGL;
import android.support.annotation.Nullable;
import com.facebook.imagepipeline.core.DefaultExecutorSupplier;
import com.facebook.imagepipeline.core.ExecutorSupplier;
import com.facebook.imagepipeline.memory.PoolConfig;
import com.facebook.imagepipeline.memory.PoolFactory;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ReactProp;
import java.util.Locale;
public class GLCanvasManager extends SimpleViewManager<GLCanvas> {
public static final String REACT_CLASS = "GLCanvas";
private ExecutorSupplier executorSupplier;
@ReactProp(name="nbContentTextures")
public void setNbContentTextures (GLCanvas view, int nbContentTextures) {
view.setNbContentTextures(nbContentTextures);
......@@ -66,6 +70,11 @@ public class GLCanvasManager extends SimpleViewManager<GLCanvas> {
@Override
public GLCanvas createViewInstance (ThemedReactContext context) {
return new GLCanvas(context);
if (executorSupplier == null) {
PoolFactory poolFactory = new PoolFactory(PoolConfig.newBuilder().build());
int numCpuBoundThreads = poolFactory.getFlexByteArrayPoolMaxNumThreads();
executorSupplier = new DefaultExecutorSupplier(numCpuBoundThreads);
}
return new GLCanvas(context, executorSupplier);
}
}
package com.projectseptember.RNGL;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.util.Log;
import com.facebook.common.references.CloseableReference;
import com.facebook.common.util.UriUtil;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.Executor;
/*
This class is maintained and inspired from
https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java
also inspired from
https://github.com/CyberAgent/android-gpuimage/blob/master/library/src/jp/co/cyberagent/android/gpuimage/GPUImage.java
*/
public class GLImage { // TODO : we need to check support for local images
private final Context context;
public class GLImage {
private Uri src;
private GLTexture texture;
private boolean isDirty;
private AsyncTask<Void, Void, Bitmap> task;
private Runnable onload;
private RunInGLThread glScheduler;
public GLImage (Context context, RunInGLThread glScheduler, Runnable onload) {
this.context = context;
this.onload = onload;
this.glScheduler = glScheduler;
private Runnable onLoad;
private Executor glExecutor;
private Executor decodeExecutor;
private DataSource<CloseableReference<CloseableImage>> pending;
public GLImage (Executor glExecutor, Executor decodeExecutor, Runnable onLoad) {
this.onLoad = onLoad;
this.glExecutor = glExecutor;
this.decodeExecutor = decodeExecutor;
this.texture = new GLTexture();
}
public void setSrc(Uri src) {
if (this.src == src) return;
public void setSrc (Uri src) {
if (this.src == src || this.src!=null && this.src.equals(src)) return;
this.src = src;
reloadImage();
}
private void reloadImage () {
isDirty = true;
if (pending != null && !pending.isFinished())
pending.close();
final Uri uri = src;
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(uri)
.build();
pending = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null);
pending.subscribe(new BaseBitmapDataSubscriber() {
@Override
protected void onNewResultImpl(@Nullable Bitmap bitmap) {
onLoad(bitmap);
}
@Override
protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
Log.e("GLImage", "Failed to load '" + uri.getPath() + "'", dataSource.getFailureCause());
}
}, decodeExecutor);
}
public void onLoad (final Bitmap bitmap) {
glScheduler.runInGLThread(new Runnable() {
public void onLoad (final Bitmap source) {
Matrix matrix = new Matrix();
matrix.postScale(1, -1);
final Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
glExecutor.execute(new Runnable() {
public void run() {
Log.i("GLImage", "loaded="+src.getPath());
texture.setPixels(bitmap);
onload.run();
bitmap.recycle();
onLoad.run();
}
});
}
public GLTexture getTexture() {
if (isDirty) {
if (task != null) task.cancel(true);
task = new LoadImageUriTask(this, src).execute();
isDirty = false;
}
return texture;
}
public static @Nullable Uri getResourceDrawableUri(Context context, @Nullable String name) {
public static @Nullable Uri getResourceDrawableUri (Context context, @Nullable String name) {
if (name == null || name.isEmpty()) {
return null;
}
......@@ -83,104 +98,4 @@ public class GLImage { // TODO : we need to check support for local images
.path(String.valueOf(resId))
.build();
}
private static class LoadImageUriTask extends LoadImageTask {
private final Uri mUri;
public LoadImageUriTask(GLImage gpuImage, Uri uri) {
super(gpuImage);
mUri = uri;
}
@Override
protected Bitmap decode(BitmapFactory.Options options) {
Log.i("GLImage", "loading...="+mUri.getPath());
// FIXME: image loading is very long (probably decoding)... possible to re-use some React Native work ?
try {
InputStream inputStream;
if (mUri.getScheme().startsWith("http") || mUri.getScheme().startsWith("https")) {
inputStream = new URL(mUri.toString()).openStream();
} else {
inputStream = glImage.context.getContentResolver().openInputStream(mUri);
}
return BitmapFactory.decodeStream(inputStream, null, options);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected int getImageOrientation() throws IOException {
Cursor cursor = glImage.context.getContentResolver().query(mUri,
new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null);
if (cursor == null || cursor.getCount() != 1) {
return 0;
}
cursor.moveToFirst();
int orientation = cursor.getInt(0);
cursor.close();
return orientation;
}
}
private static abstract class LoadImageTask extends AsyncTask<Void, Void, Bitmap> {
protected GLImage glImage;
public LoadImageTask (GLImage glImage) {
this.glImage = glImage;
}
@Override
protected Bitmap doInBackground(Void... params) {
return loadResizedImage();
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
glImage.onLoad(bitmap);
}
protected abstract Bitmap decode(BitmapFactory.Options options);
private Bitmap loadResizedImage() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
decode(options);
options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inTempStorage = new byte[32 * 1024];
Bitmap bitmap = decode(options);
if (bitmap == null) {
return null;
}
Bitmap transformedBitmap;
Matrix matrix = new Matrix();
try {
int orientation = getImageOrientation();
if (orientation != 0) {
matrix.postRotate(orientation);
}
} catch (IOException e) {
e.printStackTrace();
}
matrix.postScale(1, -1);
transformedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
bitmap.recycle();
return transformedBitmap;
}
protected abstract int getImageOrientation() throws IOException;
}
}
package com.projectseptember.RNGL;
public interface RunInGLThread {
void runInGLThread(final Runnable runnable);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment