Skip to content
This repository was archived by the owner on Jun 16, 2023. It is now read-only.

Commit fcaa945

Browse files
feat(android): integrating Google Vision's text recognition
1 parent d5da2ef commit fcaa945

File tree

7 files changed

+305
-5
lines changed

7 files changed

+305
-5
lines changed

android/src/main/java/org/reactnative/camera/CameraViewManager.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public enum Events {
1919
EVENT_ON_MOUNT_ERROR("onMountError"),
2020
EVENT_ON_BAR_CODE_READ("onBarCodeRead"),
2121
EVENT_ON_FACES_DETECTED("onFacesDetected"),
22-
EVENT_ON_FACE_DETECTION_ERROR("onFaceDetectionError");
22+
EVENT_ON_FACE_DETECTION_ERROR("onFaceDetectionError"),
23+
EVENT_ON_TEXT_RECOGNIZED("onTextRecognized");
2324

2425
private final String mName;
2526

@@ -138,4 +139,9 @@ public void setFaceDetectionLandmarks(RNCameraView view, int landmarks) {
138139
public void setFaceDetectionClassifications(RNCameraView view, int classifications) {
139140
view.setFaceDetectionClassifications(classifications);
140141
}
142+
143+
@ReactProp(name = "textRecognizerEnabled")
144+
public void setTextRecognizing(RNCameraView view, boolean textRecognizerEnabled) {
145+
view.setShouldRecognizeText(textRecognizerEnabled);
146+
}
141147
}

android/src/main/java/org/reactnative/camera/RNCameraView.java

+42-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
import com.facebook.react.uimanager.ThemedReactContext;
1818
import com.google.android.cameraview.CameraView;
1919
import com.google.android.gms.vision.face.Face;
20+
import com.google.android.gms.vision.text.Text;
21+
import com.google.android.gms.vision.text.TextBlock;
22+
import com.google.android.gms.vision.text.TextRecognizer;
2023
import com.google.zxing.BarcodeFormat;
2124
import com.google.zxing.DecodeHintType;
2225
import com.google.zxing.MultiFormatReader;
@@ -27,6 +30,8 @@
2730
import org.reactnative.camera.tasks.FaceDetectorAsyncTask;
2831
import org.reactnative.camera.tasks.FaceDetectorAsyncTaskDelegate;
2932
import org.reactnative.camera.tasks.ResolveTakenPictureAsyncTask;
33+
import org.reactnative.camera.tasks.TextRecognizerAsyncTask;
34+
import org.reactnative.camera.tasks.TextRecognizerAsyncTaskDelegate;
3035
import org.reactnative.camera.utils.ImageDimensions;
3136
import org.reactnative.camera.utils.RNFileUtils;
3237
import org.reactnative.facedetector.RNFaceDetector;
@@ -41,7 +46,8 @@
4146
import java.util.concurrent.ConcurrentHashMap;
4247
import java.util.concurrent.ConcurrentLinkedQueue;
4348

44-
public class RNCameraView extends CameraView implements LifecycleEventListener, BarCodeScannerAsyncTaskDelegate, FaceDetectorAsyncTaskDelegate {
49+
public class RNCameraView extends CameraView implements LifecycleEventListener, BarCodeScannerAsyncTaskDelegate, FaceDetectorAsyncTaskDelegate,
50+
TextRecognizerAsyncTaskDelegate {
4551
private ThemedReactContext mThemedReactContext;
4652
private Queue<Promise> mPictureTakenPromises = new ConcurrentLinkedQueue<>();
4753
private Map<Promise, ReadableMap> mPictureTakenOptions = new ConcurrentHashMap<>();
@@ -55,12 +61,15 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
5561
// Concurrency lock for scanners to avoid flooding the runtime
5662
public volatile boolean barCodeScannerTaskLock = false;
5763
public volatile boolean faceDetectorTaskLock = false;
64+
public volatile boolean textRecognizerTaskLock = false;
5865

5966
// Scanning-related properties
6067
private final MultiFormatReader mMultiFormatReader = new MultiFormatReader();
6168
private final RNFaceDetector mFaceDetector;
69+
private final TextRecognizer mTextRecognizer;
6270
private boolean mShouldDetectFaces = false;
6371
private boolean mShouldScanBarCodes = false;
72+
private boolean mShouldRecognizeText = false;
6473
private int mFaceDetectorMode = RNFaceDetector.FAST_MODE;
6574
private int mFaceDetectionLandmarks = RNFaceDetector.NO_LANDMARKS;
6675
private int mFaceDetectionClassifications = RNFaceDetector.NO_CLASSIFICATIONS;
@@ -71,6 +80,7 @@ public RNCameraView(ThemedReactContext themedReactContext) {
7180
mThemedReactContext = themedReactContext;
7281
mFaceDetector = new RNFaceDetector(themedReactContext);
7382
setupFaceDetector();
83+
mTextRecognizer = new TextRecognizer.Builder(themedReactContext).build();
7484
themedReactContext.addLifecycleEventListener(this);
7585

7686
addCallback(new Callback() {
@@ -121,6 +131,12 @@ public void onFramePreview(CameraView cameraView, byte[] data, int width, int he
121131
FaceDetectorAsyncTaskDelegate delegate = (FaceDetectorAsyncTaskDelegate) cameraView;
122132
new FaceDetectorAsyncTask(delegate, mFaceDetector, data, width, height, correctRotation).execute();
123133
}
134+
135+
if (mShouldRecognizeText && !textRecognizerTaskLock && cameraView instanceof TextRecognizerAsyncTaskDelegate) {
136+
textRecognizerTaskLock = true;
137+
TextRecognizerAsyncTaskDelegate delegate = (TextRecognizerAsyncTaskDelegate) cameraView;
138+
new TextRecognizerAsyncTask(delegate, mTextRecognizer, data, width, height, correctRotation).execute();
139+
}
124140
}
125141
});
126142
}
@@ -145,7 +161,7 @@ public void requestLayout() {
145161
@Override
146162
public void onViewAdded(View child) {
147163
if (this.getView() == child || this.getView() == null) return;
148-
// remove and readd view to make sure it is in the back.
164+
// remove and read view to make sure it is in the back.
149165
// @TODO figure out why there was a z order issue in the first place and fix accordingly.
150166
this.removeView(this.getView());
151167
this.addView(this.getView(), 0);
@@ -210,7 +226,7 @@ private void initBarcodeReader() {
210226

211227
public void setShouldScanBarCodes(boolean shouldScanBarCodes) {
212228
this.mShouldScanBarCodes = shouldScanBarCodes;
213-
setScanning(mShouldDetectFaces || mShouldScanBarCodes);
229+
setScanning(mShouldDetectFaces || mShouldScanBarCodes || mShouldRecognizeText);
214230
}
215231

216232
public void onBarCodeRead(Result barCode) {
@@ -260,7 +276,7 @@ public void setFaceDetectionMode(int mode) {
260276

261277
public void setShouldDetectFaces(boolean shouldDetectFaces) {
262278
this.mShouldDetectFaces = shouldDetectFaces;
263-
setScanning(mShouldDetectFaces || mShouldScanBarCodes);
279+
setScanning(mShouldDetectFaces || mShouldScanBarCodes || mShouldRecognizeText);
264280
}
265281

266282
public void onFacesDetected(SparseArray<Face> facesReported, int sourceWidth, int sourceHeight, int sourceRotation) {
@@ -287,6 +303,28 @@ public void onFaceDetectingTaskCompleted() {
287303
faceDetectorTaskLock = false;
288304
}
289305

306+
public void setShouldRecognizeText(boolean shouldRecognizeText) {
307+
this.mShouldRecognizeText = shouldRecognizeText;
308+
setScanning(mShouldDetectFaces || mShouldScanBarCodes || mShouldRecognizeText);
309+
}
310+
311+
@Override
312+
public void onTextRecognized(SparseArray<TextBlock> textBlocks, int sourceWidth, int sourceHeight, int sourceRotation) {
313+
if (!mShouldRecognizeText) {
314+
return;
315+
}
316+
317+
SparseArray<TextBlock> textBlocksDetected = textBlocks == null ? new SparseArray<TextBlock>() : textBlocks;
318+
ImageDimensions dimensions = new ImageDimensions(sourceWidth, sourceHeight, sourceRotation, getFacing());
319+
320+
RNCameraViewHelper.emitTextRecognizedEvent(this, textBlocksDetected, dimensions);
321+
}
322+
323+
@Override
324+
public void onTextRecognizerTaskCompleted() {
325+
textRecognizerTaskLock = false;
326+
}
327+
290328
@Override
291329
public void onHostResume() {
292330
if (hasCameraPermissions()) {

android/src/main/java/org/reactnative/camera/RNCameraViewHelper.java

+25
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
import com.facebook.react.uimanager.UIManagerModule;
1717
import com.google.android.cameraview.CameraView;
1818
import com.google.android.gms.vision.face.Face;
19+
import com.google.android.gms.vision.text.TextBlock;
1920
import com.google.zxing.Result;
2021

2122
import org.reactnative.camera.events.BarCodeReadEvent;
2223
import org.reactnative.camera.events.CameraMountErrorEvent;
2324
import org.reactnative.camera.events.CameraReadyEvent;
2425
import org.reactnative.camera.events.FaceDetectionErrorEvent;
2526
import org.reactnative.camera.events.FacesDetectedEvent;
27+
import org.reactnative.camera.events.TextRecognizedEvent;
2628
import org.reactnative.camera.utils.ImageDimensions;
2729
import org.reactnative.facedetector.RNFaceDetector;
2830

@@ -217,6 +219,29 @@ public static void emitBarCodeReadEvent(ViewGroup view, Result barCode) {
217219
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
218220
}
219221

222+
// Text recognition event
223+
224+
public static void emitTextRecognizedEvent(
225+
ViewGroup view,
226+
SparseArray<TextBlock> textBlocks,
227+
ImageDimensions dimensions) {
228+
float density = view.getResources().getDisplayMetrics().density;
229+
230+
double scaleX = (double) view.getWidth() / (dimensions.getWidth() * density);
231+
double scaleY = (double) view.getHeight() / (dimensions.getHeight() * density);
232+
233+
TextRecognizedEvent event = TextRecognizedEvent.obtain(
234+
view.getId(),
235+
textBlocks,
236+
dimensions,
237+
scaleX,
238+
scaleY
239+
);
240+
241+
ReactContext reactContext = (ReactContext) view.getContext();
242+
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
243+
}
244+
220245
// Utilities
221246

222247
public static int getCorrectCameraRotation(int rotation, int facing) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package org.reactnative.camera.events;
2+
3+
import android.support.v4.util.Pools;
4+
import android.util.SparseArray;
5+
6+
import com.facebook.react.bridge.Arguments;
7+
import com.facebook.react.bridge.ReadableArray;
8+
import com.facebook.react.bridge.ReadableMap;
9+
import com.facebook.react.bridge.WritableArray;
10+
import com.facebook.react.bridge.WritableMap;
11+
import com.facebook.react.uimanager.events.Event;
12+
import com.facebook.react.uimanager.events.RCTEventEmitter;
13+
import com.google.android.cameraview.CameraView;
14+
import com.google.android.gms.vision.text.Line;
15+
import com.google.android.gms.vision.text.Text;
16+
import com.google.android.gms.vision.text.TextBlock;
17+
import org.reactnative.camera.CameraViewManager;
18+
import org.reactnative.camera.utils.ImageDimensions;
19+
import org.reactnative.facedetector.FaceDetectorUtils;
20+
21+
22+
public class TextRecognizedEvent extends Event<TextRecognizedEvent> {
23+
24+
private static final Pools.SynchronizedPool<TextRecognizedEvent> EVENTS_POOL =
25+
new Pools.SynchronizedPool<>(3);
26+
27+
28+
private double mScaleX;
29+
private double mScaleY;
30+
private SparseArray<TextBlock> mTextBlocks;
31+
private ImageDimensions mImageDimensions;
32+
33+
private TextRecognizedEvent() {}
34+
35+
public static TextRecognizedEvent obtain(
36+
int viewTag,
37+
SparseArray<TextBlock> textBlocks,
38+
ImageDimensions dimensions,
39+
double scaleX,
40+
double scaleY) {
41+
TextRecognizedEvent event = EVENTS_POOL.acquire();
42+
if (event == null) {
43+
event = new TextRecognizedEvent();
44+
}
45+
event.init(viewTag, textBlocks, dimensions, scaleX, scaleY);
46+
return event;
47+
}
48+
49+
private void init(
50+
int viewTag,
51+
SparseArray<TextBlock> textBlocks,
52+
ImageDimensions dimensions,
53+
double scaleX,
54+
double scaleY) {
55+
super.init(viewTag);
56+
mTextBlocks = textBlocks;
57+
mImageDimensions = dimensions;
58+
mScaleX = scaleX;
59+
mScaleY = scaleY;
60+
}
61+
62+
@Override
63+
public String getEventName() {
64+
return CameraViewManager.Events.EVENT_ON_TEXT_RECOGNIZED.toString();
65+
}
66+
67+
@Override
68+
public void dispatch(RCTEventEmitter rctEventEmitter) {
69+
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
70+
}
71+
72+
private WritableMap serializeEventData() {
73+
WritableArray textBlocksList = Arguments.createArray();
74+
for (int i = 0; i < mTextBlocks.size(); ++i) {
75+
TextBlock textBlock = mTextBlocks.valueAt(i);
76+
WritableMap serializedTextBlock = serializeText(textBlock);
77+
if (mImageDimensions.getFacing() == CameraView.FACING_FRONT) {
78+
serializedTextBlock = rotateTextX(serializedTextBlock);
79+
}
80+
textBlocksList.pushMap(serializedTextBlock);
81+
}
82+
83+
WritableMap event = Arguments.createMap();
84+
event.putString("type", "textBlock");
85+
event.putArray("textBlocks", textBlocksList);
86+
event.putInt("target", getViewTag());
87+
return event;
88+
}
89+
90+
private WritableMap serializeText(Text text) {
91+
WritableMap encodedText = Arguments.createMap();
92+
93+
WritableArray components = Arguments.createArray();
94+
for (Text component : text.getComponents()) {
95+
components.pushMap(serializeText(component));
96+
}
97+
encodedText.putArray("components", components);
98+
99+
encodedText.putString("value", text.getValue());
100+
101+
WritableMap origin = Arguments.createMap();
102+
origin.putDouble("x", text.getBoundingBox().left * this.mScaleX);
103+
origin.putDouble("y", text.getBoundingBox().top * this.mScaleY);
104+
105+
WritableMap size = Arguments.createMap();
106+
size.putDouble("width", text.getBoundingBox().width() * this.mScaleX);
107+
size.putDouble("height", text.getBoundingBox().width() * this.mScaleY);
108+
109+
WritableMap bounds = Arguments.createMap();
110+
bounds.putMap("origin", origin);
111+
bounds.putMap("size", size);
112+
113+
encodedText.putMap("bounds", bounds);
114+
115+
String type_;
116+
if (text instanceof TextBlock) {
117+
type_ = "block";
118+
} else if (text instanceof Line) {
119+
type_ = "line";
120+
} else /*if (text instanceof Element)*/ {
121+
type_ = "element";
122+
}
123+
encodedText.putString("type", type_);
124+
125+
return encodedText;
126+
}
127+
128+
private WritableMap rotateTextX(WritableMap text) {
129+
ReadableMap faceBounds = text.getMap("bounds");
130+
131+
ReadableMap oldOrigin = faceBounds.getMap("origin");
132+
WritableMap mirroredOrigin = FaceDetectorUtils.positionMirroredHorizontally(
133+
oldOrigin, mImageDimensions.getWidth(), mScaleX);
134+
135+
double translateX = -faceBounds.getMap("size").getDouble("width");
136+
WritableMap translatedMirroredOrigin = FaceDetectorUtils.positionTranslatedHorizontally(mirroredOrigin, translateX);
137+
138+
WritableMap newBounds = Arguments.createMap();
139+
newBounds.merge(faceBounds);
140+
newBounds.putMap("origin", translatedMirroredOrigin);
141+
142+
text.putMap("bounds", newBounds);
143+
144+
ReadableArray oldComponents = text.getArray("components");
145+
WritableArray newComponents = Arguments.createArray();
146+
for (int i = 0; i < oldComponents.size(); ++i) {
147+
WritableMap component = Arguments.createMap();
148+
component.merge(oldComponents.getMap(i));
149+
rotateTextX(component);
150+
newComponents.pushMap(component);
151+
}
152+
text.putArray("components", newComponents);
153+
154+
return text;
155+
}
156+
157+
}

0 commit comments

Comments
 (0)