Skip to content

Commit 0c988ef

Browse files
committed
added image extraction
1 parent 44de4ab commit 0c988ef

18 files changed

+1245
-746
lines changed

android/src/main/java/iyegoroff/imagefilterkit/ImageFilter.java

+83-7
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public class ImageFilter extends ReactViewGroup {
5555

5656
private @Nullable JSONObject mConfig = null;
5757
private int mClearCachesMaxRetries = 10;
58+
private boolean mExtractImageEnabled = false;
5859
private boolean mIsReady = false;
5960
private int mDefaultWidth = 0;
6061
private int mDefaultHeight = 0;
@@ -87,6 +88,20 @@ public void setClearCachesMaxRetries(int retries) {
8788
mClearCachesMaxRetries = retries;
8889
}
8990

91+
public void setExtractImageEnabled(boolean extractImageEnabled) {
92+
boolean shouldExtractImage = !mExtractImageEnabled && extractImageEnabled;
93+
94+
mExtractImageEnabled = extractImageEnabled;
95+
96+
if (shouldExtractImage) {
97+
List<ReactImageView> images = this.images();
98+
99+
if (images.size() > 0) {
100+
this.extractImage(images.get(0));
101+
}
102+
}
103+
}
104+
90105
@Override
91106
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
92107
super.onLayout(changed, left, top, right, bottom);
@@ -105,8 +120,7 @@ private Task<FilterableImage> createSingularImage(
105120
return prevImage
106121
.onSuccess(task -> {
107122
final FilterableImage result = task.getResult();
108-
final ArrayList<Postprocessor> postProcessors =
109-
new ArrayList<>(result.getPostProcessors());
123+
final ArrayList<Postprocessor> postProcessors = new ArrayList<>(result.getPostProcessors());
110124

111125
postProcessors.add(
112126
PostProcessorRegistry.getInstance()
@@ -314,6 +328,59 @@ private void reset(final int retries) {
314328
}
315329
}
316330

331+
private void extractImage(ReactImageView mainImage) {
332+
if (!mExtractImageEnabled) {
333+
return;
334+
}
335+
336+
DataSource<CloseableReference<CloseableImage>> ds = ReactImageViewUtils
337+
.getDataSource(mainImage);
338+
339+
if (ds != null) {
340+
ds.subscribe(
341+
new BaseDataSubscriber<CloseableReference<CloseableImage>>() {
342+
@Override
343+
protected void onNewResultImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
344+
if (dataSource.isFinished()) {
345+
CloseableReference<CloseableImage> ref = dataSource.getResult();
346+
347+
if (ref != null) {
348+
try {
349+
TempFileUtils.writeTmpFile(
350+
(ReactContext) getContext(),
351+
ref,
352+
uri -> sendJSEvent(ImageFilterEvent.ON_EXTRACT_IMAGE, uri),
353+
error -> sendJSEvent(ImageFilterEvent.ON_FILTERING_ERROR, error)
354+
);
355+
356+
} finally {
357+
CloseableReference.closeSafely(ref);
358+
}
359+
360+
} else {
361+
handleError(new Error("Can't extract image"));
362+
}
363+
}
364+
}
365+
366+
@Override
367+
protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
368+
Throwable throwable = dataSource.getFailureCause();
369+
370+
if (throwable != null) {
371+
handleError(
372+
throwable instanceof Exception
373+
? (Exception) throwable
374+
: new Exception(throwable)
375+
);
376+
}
377+
}
378+
},
379+
UiThreadImmediateExecutorService.getInstance()
380+
);
381+
}
382+
}
383+
317384
private void runFilterPipeline() {
318385
ImageFilter.ashmemInfo("before filtering");
319386
final ArrayList<ReactImageView> images = images();
@@ -338,7 +405,7 @@ private void runFilterPipeline() {
338405
self.mIsReady = true;
339406
}
340407

341-
ReactImageView mainImage = images.get(0);
408+
final ReactImageView mainImage = images.get(0);
342409

343410
ImageFilter.filterImage(
344411
new FilterableImage(
@@ -350,6 +417,7 @@ private void runFilterPipeline() {
350417
mFiltering
351418
).onSuccess(task -> {
352419
sendJSEvent(ImageFilterEvent.ON_FILTERING_FINISH, null);
420+
self.extractImage(task.getResult());
353421
return null;
354422
}, mFiltering.getToken());
355423

@@ -378,6 +446,7 @@ private void runFilterPipeline() {
378446
ImageFilter.ashmemInfo("after filtering");
379447

380448
sendJSEvent(ImageFilterEvent.ON_FILTERING_FINISH, null);
449+
self.extractImage(task1.getResult());
381450

382451
return null;
383452
}, mFiltering.getToken());
@@ -402,10 +471,13 @@ private void runFilterPipeline() {
402471
}
403472
}
404473

405-
private void sendJSEvent(final @Nonnull String eventName, final @Nullable String message) {
474+
private void sendJSEvent(final @Nonnull String eventName, final @Nullable String arg) {
406475
WritableMap event = Arguments.createMap();
407-
if (message != null) {
408-
event.putString("message", message);
476+
if (arg != null) {
477+
event.putString(
478+
eventName.equals(ImageFilterEvent.ON_FILTERING_ERROR) ? "message" : "uri",
479+
arg
480+
);
409481
}
410482

411483
((ReactContext)getContext()).getJSModule(RCTEventEmitter.class).receiveEvent(
@@ -428,7 +500,11 @@ private void handleError(
428500

429501
MemoryTrimmer trimmer = MemoryTrimmer.getInstance();
430502

431-
if (error instanceof TooManyBitmapsException && trimmer.isUsed() && retries < mClearCachesMaxRetries) {
503+
if (
504+
error instanceof TooManyBitmapsException &&
505+
trimmer.isUsed() &&
506+
retries < mClearCachesMaxRetries
507+
) {
432508
FLog.d(ReactConstants.TAG, "ImageFilterKit: clearing caches ...");
433509
trimmer.trim();
434510
Fresco.getImagePipeline().clearCaches();

android/src/main/java/iyegoroff/imagefilterkit/ImageFilterEvent.java

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ class ImageFilterEvent {
77
static final @Nonnull String ON_FILTERING_START = "onIFKFilteringStart";
88
static final @Nonnull String ON_FILTERING_FINISH = "onIFKFilteringFinish";
99
static final @Nonnull String ON_FILTERING_ERROR = "onIFKFilteringError";
10+
static final @Nonnull String ON_EXTRACT_IMAGE = "onIFKExtractImage";
1011
}

android/src/main/java/iyegoroff/imagefilterkit/ImageFilterManager.java

+38
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package iyegoroff.imagefilterkit;
22

3+
import android.content.Context;
4+
import android.os.AsyncTask;
5+
6+
import androidx.annotation.NonNull;
7+
8+
import com.facebook.react.bridge.ReactContext;
39
import com.facebook.react.common.MapBuilder;
410
import com.facebook.react.module.annotations.ReactModule;
511
import com.facebook.react.uimanager.annotations.ReactProp;
612
import com.facebook.react.views.view.ReactViewManager;
713
import com.facebook.react.uimanager.ThemedReactContext;
814

15+
import java.lang.ref.WeakReference;
916
import java.util.Map;
1017

1118
import javax.annotation.Nonnull;
@@ -18,6 +25,13 @@ public class ImageFilterManager extends ReactViewManager {
1825

1926
private static final String PROP_CONFIG = "config";
2027
private static final String PROP_CLEAR_CACHES_MAX_RETRIES = "clearCachesMaxRetries";
28+
private static final String PROP_EXTRACT_IMAGE_ENABLED = "extractImageEnabled";
29+
30+
private @Nullable WeakReference<ReactContext> mContext = null;
31+
32+
ImageFilterManager() {
33+
super();
34+
}
2135

2236
@Override
2337
public @Nonnull String getName() {
@@ -26,6 +40,7 @@ public class ImageFilterManager extends ReactViewManager {
2640

2741
@Override
2842
public @Nonnull ImageFilter createViewInstance(ThemedReactContext reactContext) {
43+
mContext = new WeakReference<>(reactContext);
2944
return new ImageFilter(reactContext);
3045
}
3146

@@ -41,6 +56,22 @@ public void setClearCachesMaxRetries(ImageFilter view, int retries) {
4156
view.setClearCachesMaxRetries(retries);
4257
}
4358

59+
@SuppressWarnings("unused")
60+
@ReactProp(name = PROP_EXTRACT_IMAGE_ENABLED)
61+
public void setExtractImageEnabled(ImageFilter view, boolean extractImageEnabled) {
62+
view.setExtractImageEnabled(extractImageEnabled);
63+
}
64+
65+
@Override
66+
public void onCatalystInstanceDestroy() {
67+
super.onCatalystInstanceDestroy();
68+
ReactContext context = mContext != null ? mContext.get() : null;
69+
70+
if (context != null) {
71+
new TempFileUtils.CleanTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
72+
}
73+
}
74+
4475
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
4576
return MapBuilder.<String, Object>builder()
4677
.put(
@@ -64,6 +95,13 @@ public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
6495
MapBuilder.of("bubbled", ImageFilterEvent.ON_FILTERING_ERROR)
6596
)
6697
)
98+
.put(
99+
ImageFilterEvent.ON_EXTRACT_IMAGE,
100+
MapBuilder.of(
101+
"phasedRegistrationNames",
102+
MapBuilder.of("bubbled", ImageFilterEvent.ON_EXTRACT_IMAGE)
103+
)
104+
)
67105
.build();
68106
}
69107
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package iyegoroff.imagefilterkit;
2+
3+
import android.content.Context;
4+
import android.graphics.Bitmap;
5+
import android.net.Uri;
6+
7+
import androidx.annotation.NonNull;
8+
9+
import com.facebook.common.executors.UiThreadImmediateExecutorService;
10+
import com.facebook.common.logging.FLog;
11+
import com.facebook.common.references.CloseableReference;
12+
import com.facebook.imagepipeline.image.CloseableBitmap;
13+
import com.facebook.imagepipeline.image.CloseableImage;
14+
import com.facebook.react.bridge.GuardedAsyncTask;
15+
import com.facebook.react.bridge.ReactContext;
16+
import com.facebook.react.common.ReactConstants;
17+
18+
import java.io.File;
19+
import java.io.FileOutputStream;
20+
import java.io.FilenameFilter;
21+
import java.io.IOException;
22+
import java.util.concurrent.Callable;
23+
24+
import bolts.Task;
25+
26+
class TempFileUtils {
27+
28+
private static final String TEMP_FILE_PREFIX = "rnifk_";
29+
30+
static class CleanTask extends GuardedAsyncTask<Void, Void> implements FilenameFilter {
31+
private final File cacheDir;
32+
private final File externalCacheDir;
33+
34+
CleanTask(ReactContext context) {
35+
super(context);
36+
37+
cacheDir = context.getCacheDir();
38+
externalCacheDir = context.getExternalCacheDir();
39+
}
40+
41+
@Override
42+
protected void doInBackgroundGuarded(Void... params) {
43+
if (null != cacheDir) {
44+
cleanDirectory(cacheDir);
45+
}
46+
47+
if (externalCacheDir != null) {
48+
cleanDirectory(externalCacheDir);
49+
}
50+
}
51+
52+
@Override
53+
public final boolean accept(File dir, String filename) {
54+
return filename.startsWith(TEMP_FILE_PREFIX);
55+
}
56+
57+
private void cleanDirectory(@NonNull final File directory) {
58+
final File[] toDelete = directory.listFiles(this);
59+
60+
if (toDelete != null) {
61+
for (File file : toDelete) {
62+
if (file.delete()) {
63+
FLog.w(
64+
ReactConstants.TAG,
65+
"ImageFilterKit: deleted file " + file.getAbsolutePath()
66+
);
67+
}
68+
}
69+
}
70+
}
71+
}
72+
73+
static void writeTmpFile(
74+
@NonNull final ReactContext context,
75+
@NonNull final CloseableReference<CloseableImage> ref,
76+
@NonNull final Functor.Arity1<String> sendUri,
77+
@NonNull final Functor.Arity1<String> sendError
78+
) {
79+
CloseableReference<CloseableImage> cloned = ref.clone();
80+
81+
Task.callInBackground((Callable<Void>) () -> {
82+
try {
83+
final File outputFile = createTempFile(context);
84+
final FileOutputStream fos = new FileOutputStream(outputFile);
85+
final Bitmap bitmap = ((CloseableBitmap) cloned.get()).getUnderlyingBitmap();
86+
87+
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
88+
89+
Task.call((Callable<Void>) () -> {
90+
sendUri.call(Uri.fromFile(outputFile).toString());
91+
92+
return null;
93+
}, UiThreadImmediateExecutorService.getInstance());
94+
95+
} catch (Exception e) {
96+
Task.call((Callable<Void>) () -> {
97+
sendError.call(e.getMessage());
98+
99+
return null;
100+
}, UiThreadImmediateExecutorService.getInstance());
101+
102+
} finally {
103+
CloseableReference.closeSafely(cloned);
104+
}
105+
106+
return null;
107+
});
108+
}
109+
110+
@NonNull
111+
private static File createTempFile(@NonNull final Context context) throws IOException {
112+
final File externalCacheDir = context.getExternalCacheDir();
113+
final File internalCacheDir = context.getCacheDir();
114+
final File cacheDir;
115+
116+
if (externalCacheDir == null && internalCacheDir == null) {
117+
throw new IOException("No cache directory available");
118+
}
119+
120+
if (externalCacheDir == null) {
121+
cacheDir = internalCacheDir;
122+
} else if (internalCacheDir == null) {
123+
cacheDir = externalCacheDir;
124+
} else {
125+
cacheDir = externalCacheDir.getFreeSpace() > internalCacheDir.getFreeSpace() ?
126+
externalCacheDir : internalCacheDir;
127+
}
128+
129+
return File.createTempFile(TEMP_FILE_PREFIX, ".rnifk.png", cacheDir);
130+
}
131+
}

0 commit comments

Comments
 (0)