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

Commit

Permalink
feat(android): integrating Google Vision's text recognition
Browse files Browse the repository at this point in the history
  • Loading branch information
alculquicondor committed Mar 22, 2018
1 parent d5da2ef commit fcaa945
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 5 deletions.
Expand Up @@ -19,7 +19,8 @@ public enum Events {
EVENT_ON_MOUNT_ERROR("onMountError"),
EVENT_ON_BAR_CODE_READ("onBarCodeRead"),
EVENT_ON_FACES_DETECTED("onFacesDetected"),
EVENT_ON_FACE_DETECTION_ERROR("onFaceDetectionError");
EVENT_ON_FACE_DETECTION_ERROR("onFaceDetectionError"),
EVENT_ON_TEXT_RECOGNIZED("onTextRecognized");

private final String mName;

Expand Down Expand Up @@ -138,4 +139,9 @@ public void setFaceDetectionLandmarks(RNCameraView view, int landmarks) {
public void setFaceDetectionClassifications(RNCameraView view, int classifications) {
view.setFaceDetectionClassifications(classifications);
}

@ReactProp(name = "textRecognizerEnabled")
public void setTextRecognizing(RNCameraView view, boolean textRecognizerEnabled) {
view.setShouldRecognizeText(textRecognizerEnabled);
}
}
46 changes: 42 additions & 4 deletions android/src/main/java/org/reactnative/camera/RNCameraView.java
Expand Up @@ -17,6 +17,9 @@
import com.facebook.react.uimanager.ThemedReactContext;
import com.google.android.cameraview.CameraView;
import com.google.android.gms.vision.face.Face;
import com.google.android.gms.vision.text.Text;
import com.google.android.gms.vision.text.TextBlock;
import com.google.android.gms.vision.text.TextRecognizer;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
Expand All @@ -27,6 +30,8 @@
import org.reactnative.camera.tasks.FaceDetectorAsyncTask;
import org.reactnative.camera.tasks.FaceDetectorAsyncTaskDelegate;
import org.reactnative.camera.tasks.ResolveTakenPictureAsyncTask;
import org.reactnative.camera.tasks.TextRecognizerAsyncTask;
import org.reactnative.camera.tasks.TextRecognizerAsyncTaskDelegate;
import org.reactnative.camera.utils.ImageDimensions;
import org.reactnative.camera.utils.RNFileUtils;
import org.reactnative.facedetector.RNFaceDetector;
Expand All @@ -41,7 +46,8 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

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

// Scanning-related properties
private final MultiFormatReader mMultiFormatReader = new MultiFormatReader();
private final RNFaceDetector mFaceDetector;
private final TextRecognizer mTextRecognizer;
private boolean mShouldDetectFaces = false;
private boolean mShouldScanBarCodes = false;
private boolean mShouldRecognizeText = false;
private int mFaceDetectorMode = RNFaceDetector.FAST_MODE;
private int mFaceDetectionLandmarks = RNFaceDetector.NO_LANDMARKS;
private int mFaceDetectionClassifications = RNFaceDetector.NO_CLASSIFICATIONS;
Expand All @@ -71,6 +80,7 @@ public RNCameraView(ThemedReactContext themedReactContext) {
mThemedReactContext = themedReactContext;
mFaceDetector = new RNFaceDetector(themedReactContext);
setupFaceDetector();
mTextRecognizer = new TextRecognizer.Builder(themedReactContext).build();
themedReactContext.addLifecycleEventListener(this);

addCallback(new Callback() {
Expand Down Expand Up @@ -121,6 +131,12 @@ public void onFramePreview(CameraView cameraView, byte[] data, int width, int he
FaceDetectorAsyncTaskDelegate delegate = (FaceDetectorAsyncTaskDelegate) cameraView;
new FaceDetectorAsyncTask(delegate, mFaceDetector, data, width, height, correctRotation).execute();
}

if (mShouldRecognizeText && !textRecognizerTaskLock && cameraView instanceof TextRecognizerAsyncTaskDelegate) {
textRecognizerTaskLock = true;
TextRecognizerAsyncTaskDelegate delegate = (TextRecognizerAsyncTaskDelegate) cameraView;
new TextRecognizerAsyncTask(delegate, mTextRecognizer, data, width, height, correctRotation).execute();
}
}
});
}
Expand All @@ -145,7 +161,7 @@ public void requestLayout() {
@Override
public void onViewAdded(View child) {
if (this.getView() == child || this.getView() == null) return;
// remove and readd view to make sure it is in the back.
// remove and read view to make sure it is in the back.
// @TODO figure out why there was a z order issue in the first place and fix accordingly.
this.removeView(this.getView());
this.addView(this.getView(), 0);
Expand Down Expand Up @@ -210,7 +226,7 @@ private void initBarcodeReader() {

public void setShouldScanBarCodes(boolean shouldScanBarCodes) {
this.mShouldScanBarCodes = shouldScanBarCodes;
setScanning(mShouldDetectFaces || mShouldScanBarCodes);
setScanning(mShouldDetectFaces || mShouldScanBarCodes || mShouldRecognizeText);
}

public void onBarCodeRead(Result barCode) {
Expand Down Expand Up @@ -260,7 +276,7 @@ public void setFaceDetectionMode(int mode) {

public void setShouldDetectFaces(boolean shouldDetectFaces) {
this.mShouldDetectFaces = shouldDetectFaces;
setScanning(mShouldDetectFaces || mShouldScanBarCodes);
setScanning(mShouldDetectFaces || mShouldScanBarCodes || mShouldRecognizeText);
}

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

public void setShouldRecognizeText(boolean shouldRecognizeText) {
this.mShouldRecognizeText = shouldRecognizeText;
setScanning(mShouldDetectFaces || mShouldScanBarCodes || mShouldRecognizeText);
}

@Override
public void onTextRecognized(SparseArray<TextBlock> textBlocks, int sourceWidth, int sourceHeight, int sourceRotation) {
if (!mShouldRecognizeText) {
return;
}

SparseArray<TextBlock> textBlocksDetected = textBlocks == null ? new SparseArray<TextBlock>() : textBlocks;
ImageDimensions dimensions = new ImageDimensions(sourceWidth, sourceHeight, sourceRotation, getFacing());

RNCameraViewHelper.emitTextRecognizedEvent(this, textBlocksDetected, dimensions);
}

@Override
public void onTextRecognizerTaskCompleted() {
textRecognizerTaskLock = false;
}

@Override
public void onHostResume() {
if (hasCameraPermissions()) {
Expand Down
Expand Up @@ -16,13 +16,15 @@
import com.facebook.react.uimanager.UIManagerModule;
import com.google.android.cameraview.CameraView;
import com.google.android.gms.vision.face.Face;
import com.google.android.gms.vision.text.TextBlock;
import com.google.zxing.Result;

import org.reactnative.camera.events.BarCodeReadEvent;
import org.reactnative.camera.events.CameraMountErrorEvent;
import org.reactnative.camera.events.CameraReadyEvent;
import org.reactnative.camera.events.FaceDetectionErrorEvent;
import org.reactnative.camera.events.FacesDetectedEvent;
import org.reactnative.camera.events.TextRecognizedEvent;
import org.reactnative.camera.utils.ImageDimensions;
import org.reactnative.facedetector.RNFaceDetector;

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

// Text recognition event

public static void emitTextRecognizedEvent(
ViewGroup view,
SparseArray<TextBlock> textBlocks,
ImageDimensions dimensions) {
float density = view.getResources().getDisplayMetrics().density;

double scaleX = (double) view.getWidth() / (dimensions.getWidth() * density);
double scaleY = (double) view.getHeight() / (dimensions.getHeight() * density);

TextRecognizedEvent event = TextRecognizedEvent.obtain(
view.getId(),
textBlocks,
dimensions,
scaleX,
scaleY
);

ReactContext reactContext = (ReactContext) view.getContext();
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
}

// Utilities

public static int getCorrectCameraRotation(int rotation, int facing) {
Expand Down
@@ -0,0 +1,157 @@
package org.reactnative.camera.events;

import android.support.v4.util.Pools;
import android.util.SparseArray;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.google.android.cameraview.CameraView;
import com.google.android.gms.vision.text.Line;
import com.google.android.gms.vision.text.Text;
import com.google.android.gms.vision.text.TextBlock;
import org.reactnative.camera.CameraViewManager;
import org.reactnative.camera.utils.ImageDimensions;
import org.reactnative.facedetector.FaceDetectorUtils;


public class TextRecognizedEvent extends Event<TextRecognizedEvent> {

private static final Pools.SynchronizedPool<TextRecognizedEvent> EVENTS_POOL =
new Pools.SynchronizedPool<>(3);


private double mScaleX;
private double mScaleY;
private SparseArray<TextBlock> mTextBlocks;
private ImageDimensions mImageDimensions;

private TextRecognizedEvent() {}

public static TextRecognizedEvent obtain(
int viewTag,
SparseArray<TextBlock> textBlocks,
ImageDimensions dimensions,
double scaleX,
double scaleY) {
TextRecognizedEvent event = EVENTS_POOL.acquire();
if (event == null) {
event = new TextRecognizedEvent();
}
event.init(viewTag, textBlocks, dimensions, scaleX, scaleY);
return event;
}

private void init(
int viewTag,
SparseArray<TextBlock> textBlocks,
ImageDimensions dimensions,
double scaleX,
double scaleY) {
super.init(viewTag);
mTextBlocks = textBlocks;
mImageDimensions = dimensions;
mScaleX = scaleX;
mScaleY = scaleY;
}

@Override
public String getEventName() {
return CameraViewManager.Events.EVENT_ON_TEXT_RECOGNIZED.toString();
}

@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
}

private WritableMap serializeEventData() {
WritableArray textBlocksList = Arguments.createArray();
for (int i = 0; i < mTextBlocks.size(); ++i) {
TextBlock textBlock = mTextBlocks.valueAt(i);
WritableMap serializedTextBlock = serializeText(textBlock);
if (mImageDimensions.getFacing() == CameraView.FACING_FRONT) {
serializedTextBlock = rotateTextX(serializedTextBlock);
}
textBlocksList.pushMap(serializedTextBlock);
}

WritableMap event = Arguments.createMap();
event.putString("type", "textBlock");
event.putArray("textBlocks", textBlocksList);
event.putInt("target", getViewTag());
return event;
}

private WritableMap serializeText(Text text) {
WritableMap encodedText = Arguments.createMap();

WritableArray components = Arguments.createArray();
for (Text component : text.getComponents()) {
components.pushMap(serializeText(component));
}
encodedText.putArray("components", components);

encodedText.putString("value", text.getValue());

WritableMap origin = Arguments.createMap();
origin.putDouble("x", text.getBoundingBox().left * this.mScaleX);
origin.putDouble("y", text.getBoundingBox().top * this.mScaleY);

WritableMap size = Arguments.createMap();
size.putDouble("width", text.getBoundingBox().width() * this.mScaleX);
size.putDouble("height", text.getBoundingBox().width() * this.mScaleY);

WritableMap bounds = Arguments.createMap();
bounds.putMap("origin", origin);
bounds.putMap("size", size);

encodedText.putMap("bounds", bounds);

String type_;
if (text instanceof TextBlock) {
type_ = "block";
} else if (text instanceof Line) {
type_ = "line";
} else /*if (text instanceof Element)*/ {
type_ = "element";
}
encodedText.putString("type", type_);

return encodedText;
}

private WritableMap rotateTextX(WritableMap text) {
ReadableMap faceBounds = text.getMap("bounds");

ReadableMap oldOrigin = faceBounds.getMap("origin");
WritableMap mirroredOrigin = FaceDetectorUtils.positionMirroredHorizontally(
oldOrigin, mImageDimensions.getWidth(), mScaleX);

double translateX = -faceBounds.getMap("size").getDouble("width");
WritableMap translatedMirroredOrigin = FaceDetectorUtils.positionTranslatedHorizontally(mirroredOrigin, translateX);

WritableMap newBounds = Arguments.createMap();
newBounds.merge(faceBounds);
newBounds.putMap("origin", translatedMirroredOrigin);

text.putMap("bounds", newBounds);

ReadableArray oldComponents = text.getArray("components");
WritableArray newComponents = Arguments.createArray();
for (int i = 0; i < oldComponents.size(); ++i) {
WritableMap component = Arguments.createMap();
component.merge(oldComponents.getMap(i));
rotateTextX(component);
newComponents.pushMap(component);
}
text.putArray("components", newComponents);

return text;
}

}

0 comments on commit fcaa945

Please sign in to comment.