feat(android): onBarCodeRead should also send raw photo bytes, fixed #2786 (#2923)

* feat: onBarCodeRead should also send raw photo bytes, fixed #2786

* docs: documentation to explain feature #2786
This commit is contained in:
Hendy Irawan 2020-07-24 00:04:18 +07:00 committed by GitHub
parent c3159aa374
commit 0df72c3114
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 144 additions and 61 deletions

View File

@ -29,19 +29,18 @@ public class BarcodeDetectorAsyncTask extends android.os.AsyncTask<Void, Void, S
private int mPaddingTop;
public BarcodeDetectorAsyncTask(
BarcodeDetectorAsyncTaskDelegate delegate,
RNBarcodeDetector barcodeDetector,
byte[] imageData,
int width,
int height,
int rotation,
float density,
int facing,
int viewWidth,
int viewHeight,
int viewPaddingLeft,
int viewPaddingTop
) {
BarcodeDetectorAsyncTaskDelegate delegate,
RNBarcodeDetector barcodeDetector,
byte[] imageData,
int width,
int height,
int rotation,
float density,
int facing,
int viewWidth,
int viewHeight,
int viewPaddingLeft,
int viewPaddingTop) {
mImageData = imageData;
mWidth = width;
mHeight = height;
@ -73,7 +72,7 @@ public class BarcodeDetectorAsyncTask extends android.os.AsyncTask<Void, Void, S
mDelegate.onBarcodeDetectionError(mBarcodeDetector);
} else {
if (barcodes.size() > 0) {
mDelegate.onBarcodesDetected(serializeEventData(barcodes));
mDelegate.onBarcodesDetected(serializeEventData(barcodes), mWidth, mHeight, mImageData);
}
mDelegate.onBarcodeDetectingTaskCompleted();
}

View File

@ -152,6 +152,11 @@ public class CameraViewManager extends ViewGroupManager<RNCameraView> {
view.setBarCodeTypes(result);
}
@ReactProp(name = "detectedImageInEvent")
public void setDetectedImageInEvent(RNCameraView view, boolean detectedImageInEvent) {
view.setDetectedImageInEvent(detectedImageInEvent);
}
@ReactProp(name = "barCodeScannerEnabled")
public void setBarCodeScanning(RNCameraView view, boolean barCodeScannerEnabled) {
view.setShouldScanBarCodes(barCodeScannerEnabled);

View File

@ -6,6 +6,9 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.media.CamcorderProfile;
import android.os.Build;
import androidx.core.content.ContextCompat;
@ -28,6 +31,7 @@ import org.reactnative.camera.tasks.*;
import org.reactnative.camera.utils.RNFileUtils;
import org.reactnative.facedetector.RNFaceDetector;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.*;
@ -42,6 +46,7 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
private Map<Promise, File> mPictureTakenDirectories = new ConcurrentHashMap<>();
private Promise mVideoRecordedPromise;
private List<String> mBarCodeTypes = null;
private boolean mDetectedImageInEvent = false;
private ScaleGestureDetector mScaleGestureDetector;
private GestureDetector mGestureDetector;
@ -196,7 +201,9 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
}
}
BarcodeDetectorAsyncTaskDelegate delegate = (BarcodeDetectorAsyncTaskDelegate) cameraView;
new BarcodeDetectorAsyncTask(delegate, mGoogleBarcodeDetector, data, width, height, correctRotation, getResources().getDisplayMetrics().density, getFacing(), getWidth(), getHeight(), mPaddingX, mPaddingY).execute();
new BarcodeDetectorAsyncTask(delegate, mGoogleBarcodeDetector, data, width, height,
correctRotation, getResources().getDisplayMetrics().density, getFacing(),
getWidth(), getHeight(), mPaddingX, mPaddingY).execute();
}
if (willCallTextTask) {
@ -256,6 +263,10 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
initBarcodeReader();
}
public void setDetectedImageInEvent(boolean detectedImageInEvent) {
this.mDetectedImageInEvent = detectedImageInEvent;
}
public void takePicture(final ReadableMap options, final Promise promise, final File cacheDirectory) {
mBgHandler.post(new Runnable() {
@Override
@ -354,13 +365,28 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
setScanning(mShouldDetectFaces || mShouldGoogleDetectBarcodes || mShouldScanBarCodes || mShouldRecognizeText);
}
public void onBarCodeRead(Result barCode, int width, int height) {
public void onBarCodeRead(Result barCode, int width, int height, byte[] imageData) {
String barCodeType = barCode.getBarcodeFormat().toString();
if (!mShouldScanBarCodes || !mBarCodeTypes.contains(barCodeType)) {
return;
}
RNCameraViewHelper.emitBarCodeReadEvent(this, barCode, width, height);
final byte[] compressedImage;
if (mDetectedImageInEvent) {
try {
// https://stackoverflow.com/a/32793908/122441
final YuvImage yuvImage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
final ByteArrayOutputStream imageStream = new ByteArrayOutputStream();
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, imageStream);
compressedImage = imageStream.toByteArray();
} catch (Exception e) {
throw new RuntimeException(String.format("Error decoding imageData from NV21 format (%d bytes)", imageData.length), e);
}
} else {
compressedImage = null;
}
RNCameraViewHelper.emitBarCodeReadEvent(this, barCode, width, height, compressedImage);
}
public void onBarCodeScanningTaskCompleted() {
@ -508,11 +534,28 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
mGoogleVisionBarCodeMode = barcodeMode;
}
public void onBarcodesDetected(WritableArray barcodesDetected) {
public void onBarcodesDetected(WritableArray barcodesDetected, int width, int height, byte[] imageData) {
if (!mShouldGoogleDetectBarcodes) {
return;
}
RNCameraViewHelper.emitBarcodesDetectedEvent(this, barcodesDetected);
// See discussion in https://github.com/react-native-community/react-native-camera/issues/2786
final byte[] compressedImage;
if (mDetectedImageInEvent) {
try {
// https://stackoverflow.com/a/32793908/122441
final YuvImage yuvImage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
final ByteArrayOutputStream imageStream = new ByteArrayOutputStream();
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, imageStream);
compressedImage = imageStream.toByteArray();
} catch (Exception e) {
throw new RuntimeException(String.format("Error decoding imageData from NV21 format (%d bytes)", imageData.length), e);
}
} else {
compressedImage = null;
}
RNCameraViewHelper.emitBarcodesDetectedEvent(this, barcodesDetected, compressedImage);
}
public void onBarcodeDetectionError(RNBarcodeDetector barcodeDetector) {

View File

@ -10,7 +10,6 @@ import androidx.exifinterface.media.ExifInterface;
import android.view.ViewGroup;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableArray;
@ -285,13 +284,13 @@ public class RNCameraViewHelper {
// Barcode detection events
public static void emitBarcodesDetectedEvent(final ViewGroup view, final WritableArray barcodes) {
public static void emitBarcodesDetectedEvent(final ViewGroup view, final WritableArray barcodes, final byte[] compressedImage) {
final ReactContext reactContext = (ReactContext) view.getContext();
reactContext.runOnNativeModulesQueueThread(new Runnable() {
@Override
public void run() {
BarcodesDetectedEvent event = BarcodesDetectedEvent.obtain(view.getId(), barcodes);
BarcodesDetectedEvent event = BarcodesDetectedEvent.obtain(view.getId(), barcodes, compressedImage);
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
}
});
@ -311,12 +310,12 @@ public class RNCameraViewHelper {
// Bar code read event
public static void emitBarCodeReadEvent(final ViewGroup view, final Result barCode, final int width, final int height) {
public static void emitBarCodeReadEvent(final ViewGroup view, final Result barCode, final int width, final int height, final byte[] compressedImage) {
final ReactContext reactContext = (ReactContext) view.getContext();
reactContext.runOnNativeModulesQueueThread(new Runnable() {
@Override
public void run() {
BarCodeReadEvent event = BarCodeReadEvent.obtain(view.getId(), barCode, width, height);
BarCodeReadEvent event = BarCodeReadEvent.obtain(view.getId(), barCode, width, height, compressedImage);
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
}
});

View File

@ -1,5 +1,7 @@
package org.reactnative.camera.events;
import android.util.Base64;
import androidx.core.util.Pools;
import org.reactnative.camera.CameraViewManager;
@ -20,23 +22,25 @@ public class BarCodeReadEvent extends Event<BarCodeReadEvent> {
private Result mBarCode;
private int mWidth;
private int mHeight;
private byte[] mCompressedImage;
private BarCodeReadEvent() {}
public static BarCodeReadEvent obtain(int viewTag, Result barCode, int width, int height) {
public static BarCodeReadEvent obtain(int viewTag, Result barCode, int width, int height, byte[] compressedImage) {
BarCodeReadEvent event = EVENTS_POOL.acquire();
if (event == null) {
event = new BarCodeReadEvent();
}
event.init(viewTag, barCode, width, height);
event.init(viewTag, barCode, width, height, compressedImage);
return event;
}
private void init(int viewTag, Result barCode, int width, int height) {
private void init(int viewTag, Result barCode, int width, int height, byte[] compressedImage) {
super.init(viewTag);
mBarCode = barCode;
mWidth = width;
mHeight = height;
mCompressedImage = compressedImage;
}
/**
@ -95,6 +99,9 @@ public class BarCodeReadEvent extends Event<BarCodeReadEvent> {
eventOrigin.putInt("height", mHeight);
eventOrigin.putInt("width", mWidth);
event.putMap("bounds", eventOrigin);
if (mCompressedImage != null) {
event.putString("image", Base64.encodeToString(mCompressedImage, Base64.NO_WRAP));
}
return event;
}
}

View File

@ -1,7 +1,9 @@
package org.reactnative.camera.events;
import android.util.Base64;
import androidx.core.util.Pools;
import android.util.SparseArray;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
@ -15,28 +17,30 @@ public class BarcodesDetectedEvent extends Event<BarcodesDetectedEvent> {
new Pools.SynchronizedPool<>(3);
private WritableArray mBarcodes;
private byte[] mCompressedImage;
private BarcodesDetectedEvent() {
}
public static BarcodesDetectedEvent obtain(
int viewTag,
WritableArray barcodes
) {
int viewTag,
WritableArray barcodes,
byte[] compressedImage) {
BarcodesDetectedEvent event = EVENTS_POOL.acquire();
if (event == null) {
event = new BarcodesDetectedEvent();
}
event.init(viewTag, barcodes);
event.init(viewTag, barcodes, compressedImage);
return event;
}
private void init(
int viewTag,
WritableArray barcodes
) {
int viewTag,
WritableArray barcodes,
byte[] compressedImage) {
super.init(viewTag);
mBarcodes = barcodes;
mCompressedImage = compressedImage;
}
/**
@ -68,6 +72,9 @@ public class BarcodesDetectedEvent extends Event<BarcodesDetectedEvent> {
event.putString("type", "barcode");
event.putArray("barcodes", mBarcodes);
event.putInt("target", getViewTag());
if (mCompressedImage != null) {
event.putString("image", Base64.encodeToString(mCompressedImage, Base64.NO_WRAP));
}
return event;
}
}

View File

@ -63,7 +63,7 @@ public class BarCodeScannerAsyncTask extends android.os.AsyncTask<Void, Void, Re
/**
* mCameraViewWidth and mCameraViewHeight are obtained from portait orientation
* mWidth and mHeight are measured with landscape orientation with Home button to the right
* adjustedCamViewWidth is the adjusted width fromt the Aspect ratio setting
* adjustedCamViewWidth is the adjusted width from the Aspect ratio setting
*/
int adjustedCamViewWidth = (int) (mCameraViewHeight / mRatio);
float adjustedScanY = (((adjustedCamViewWidth - mCameraViewWidth) / 2) + (mScanAreaY * mCameraViewWidth)) / adjustedCamViewWidth;
@ -148,7 +148,7 @@ public class BarCodeScannerAsyncTask extends android.os.AsyncTask<Void, Void, Re
protected void onPostExecute(Result result) {
super.onPostExecute(result);
if (result != null) {
mDelegate.onBarCodeRead(result, mWidth, mHeight);
mDelegate.onBarCodeRead(result, mWidth, mHeight, mImageData);
}
mDelegate.onBarCodeScanningTaskCompleted();
}

View File

@ -3,6 +3,6 @@ package org.reactnative.camera.tasks;
import com.google.zxing.Result;
public interface BarCodeScannerAsyncTaskDelegate {
void onBarCodeRead(Result barCode, int width, int height);
void onBarCodeRead(Result barCode, int width, int height, byte[] imageData);
void onBarCodeScanningTaskCompleted();
}

View File

@ -5,7 +5,7 @@ import org.reactnative.barcodedetector.RNBarcodeDetector;
public interface BarcodeDetectorAsyncTaskDelegate {
void onBarcodesDetected(WritableArray barcodes);
void onBarcodesDetected(WritableArray barcodes, int width, int height, byte[] imageData);
void onBarcodeDetectionError(RNBarcodeDetector barcodeDetector);

View File

@ -82,7 +82,7 @@ public class BarcodeDetectorAsyncTask extends android.os.AsyncTask<Void, Void, V
@Override
public void onSuccess(List<FirebaseVisionBarcode> barcodes) {
WritableArray serializedBarcodes = serializeEventData(barcodes);
mDelegate.onBarcodesDetected(serializedBarcodes);
mDelegate.onBarcodesDetected(serializedBarcodes, mWidth, mHeight, mImageData);
mDelegate.onBarcodeDetectingTaskCompleted();
}
})

View File

@ -20,7 +20,7 @@ class ExampleApp extends PureComponent {
return (
<View style={styles.container}>
<RNCamera
ref={(ref) => {
ref={ref => {
this.camera = ref;
}}
style={styles.preview}
@ -144,7 +144,7 @@ class ExampleApp extends PureComponent {
);
}
takePicture = async function (camera) {
takePicture = async function(camera) {
const options = { quality: 0.5, base64: true };
const data = await camera.takePictureAsync(options);
// eslint-disable-next-line
@ -286,7 +286,7 @@ The idea is that you select the appropriate white balance setting for the type o
Use the `whiteBalance` property to specify which white balance setting the camera should use.
On iOS it's also possible to specify custom temperature, tint and rgb offset values instead of using one of the presets (auto, sunny, ...):
On iOS it's also possible to specify custom temperature, tint and rgb offset values instead of using one of the presets (auto, sunny, ...):
Value: Object (e.g. `{temperature: 4000, tint: -10.0, redGainOffset: 0.0, greenGainOffset: 0.0, blueGainOffset: 0.0}`)
@ -551,6 +551,13 @@ Available settings:
Change the mode in order to scan "inverted" barcodes. You can either change it to `alternate`, which will inverted the image data every second screen and be able to read both normal and inverted barcodes, or `inverted`, which will only read inverted barcodes. Default is `normal`, which only reads "normal" barcodes. Note: this property only applies to the Google Vision barcode detector.
Example: `<RNCamera googleVisionBarcodeMode={RNCamera.Constants.GoogleVisionBarcodeDetection.BarcodeMode.ALTERNATE} />`
### `detectedImageInEvent`
When `detectedImageInEvent` is `false` (default), `onBarCodeRead` / `onBarcodesDetected` only gives metadata, but not the image (bytes/base64) itself.
When `detectedImageInEvent` is `true`, `onBarCodeRead` / `onGoogleVisionBarcodesDetected` will fill the `image` field inside the object given to the callback handler.
It contains raw image bytes in JPEG format (quality 100) as Base64-encoded string.
### Face Detection Related props
RNCamera uses the Firebase MLKit for Face Detection, you can read more about it [here](https://firebase.google.com/docs/ml-kit/detect-faces).

52
types/index.d.ts vendored
View File

@ -39,7 +39,7 @@ type CustomWhiteBalance = {
tint: number;
redGainOffset?: number;
greenGainOffset?: number;
blueGainOffset?: number
blueGainOffset?: number;
};
type BarCodeType = Readonly<{
aztec: any;
@ -139,6 +139,33 @@ export interface Constants {
VideoStabilization: VideoStabilization;
}
export interface BarCodeReadEvent {
data: string;
rawData?: string;
type: keyof BarCodeType;
/**
* @description For Android use `{ width: number, height: number, origin: Array<Point<string>> }`
* @description For iOS use `{ origin: Point<string>, size: Size<string> }`
*/
bounds:
| { width: number; height: number; origin: Array<Point<string>> }
| { origin: Point<string>; size: Size<string> };
/**
* Raw image bytes in JPEG format (quality 100) as Base64-encoded string, only provided if `detectedImageInEvent=true`.
*/
image: string;
}
export interface GoogleVisionBarcodesDetectedEvent {
type: string;
barcodes: Barcode[];
target: number;
/**
* Raw image bytes in JPEG format (quality 100) as Base64-encoded string, only provided if `detectedImageInEvent=true`.
*/
image?: string;
}
export interface RNCameraProps {
children?: ReactNode | FaCC;
@ -177,10 +204,10 @@ export interface RNCameraProps {
/** iOS only */
onAudioInterrupted?(): void;
onAudioConnected?(): void;
onTap?(origin:Point):void;
onDoubleTap?(origin:Point):void;
onTap?(origin: Point): void;
onDoubleTap?(origin: Point): void;
/** Use native pinch to zoom implementation*/
useNativeZoom?:boolean;
useNativeZoom?: boolean;
/** Value: float from 0 to 1.0 */
zoom?: number;
/** iOS only. float from 0 to any. Locks the max zoom value to the provided value
@ -192,23 +219,12 @@ export interface RNCameraProps {
focusDepth?: number;
// -- BARCODE PROPS
detectedImageInEvent?: boolean;
barCodeTypes?: Array<keyof BarCodeType>;
googleVisionBarcodeType?: Constants['GoogleVisionBarcodeDetection']['BarcodeType'];
googleVisionBarcodeMode?: Constants['GoogleVisionBarcodeDetection']['BarcodeMode'];
onBarCodeRead?(event: {
data: string;
rawData?: string;
type: keyof BarCodeType;
/**
* @description For Android use `{ width: number, height: number, origin: Array<Point<string>> }`
* @description For iOS use `{ origin: Point<string>, size: Size<string> }`
*/
bounds:
| { width: number; height: number; origin: Array<Point<string>> }
| { origin: Point<string>; size: Size<string> };
}): void;
onGoogleVisionBarcodesDetected?(event: { barcodes: Barcode[] }): void;
onBarCodeRead?(event: BarCodeReadEvent): void;
onGoogleVisionBarcodesDetected?(event: GoogleVisionBarcodesDetectedEvent): void;
// limiting scan area
rectOfInterest?: Point;