feat(ios): Add ability to scan inverted-color barcodes on iOS using Google Vision (#2851)

* Add required plumbing for supporiting inverted colors

* Update package.json

* Update package.json

* Update RNCamera.js

* Update package.json

* Update RNCameraManager.m

* Read barcode detection mode

* Update package.json

* Declare invertImageData as a property

* Access the property correctly

* Refer to the property correctly

* Update RNCamera.m

* Update RNCamera.md

* Set default scan mode to Alternate

* Update package.json

* Set the default barcode detection mode to normal

* Update package.json

* Make the property type consistent

* Update RNCamera.h

* Update package.json

* Rename variables for readability

* Update package.json

* Add barcode mode usage

* Revert version update

Co-authored-by: Manish Kumar <mkumar@acvauctions.com>
This commit is contained in:
Manish Kumar 2020-06-04 11:17:51 -04:00 committed by GitHub
parent ee88b38e22
commit fa61fce613
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 107 additions and 18 deletions

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
@ -436,6 +436,7 @@ Function to be called when native code stops recording video, but before all vid
Function to be called when a touch within the camera view is recognized.
The function is also called on the first touch of double tap.
Event will contain the following fields:
- `x`
- `y`
@ -443,6 +444,7 @@ Event will contain the following fields:
Function to be called when a double touch within the camera view is recognized.
Event will contain the following fields:
- `x`
- `y`
@ -537,7 +539,7 @@ Available settings:
- DATA_MATRIX
- ALL
### `Android` `googleVisionBarcodeMode`
### `googleVisionBarcodeMode`
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} />`

View File

@ -87,14 +87,14 @@ export default class CameraScreen extends React.Component {
});
}
takePicture = async function() {
takePicture = async function () {
if (this.camera) {
const data = await this.camera.takePictureAsync();
console.warn('takePicture ', data);
}
};
takeVideo = async function() {
takeVideo = async function () {
if (this.camera) {
try {
const promise = this.camera.recordAsync(this.state.recordOptions);
@ -111,7 +111,7 @@ export default class CameraScreen extends React.Component {
}
};
toggle = value => () => this.setState(prevState => ({ [value]: !prevState[value] }));
toggle = (value) => () => this.setState((prevState) => ({ [value]: !prevState[value] }));
facesDetected = ({ faces }) => this.setState({ faces });
@ -139,7 +139,7 @@ export default class CameraScreen extends React.Component {
);
renderLandmarksOfFace(face) {
const renderLandmark = position =>
const renderLandmark = (position) =>
position && (
<View
style={[
@ -204,7 +204,7 @@ export default class CameraScreen extends React.Component {
</React.Fragment>
);
textRecognized = object => {
textRecognized = (object) => {
const { textBlocks } = object;
this.setState({ textBlocks });
};
@ -238,7 +238,7 @@ export default class CameraScreen extends React.Component {
const { canDetectFaces, canDetectText, canDetectBarcode } = this.state;
return (
<RNCamera
ref={ref => {
ref={(ref) => {
this.camera = ref;
}}
style={{
@ -272,6 +272,7 @@ export default class CameraScreen extends React.Component {
onTextRecognized={canDetectText ? this.textRecognized : null}
onGoogleVisionBarcodesDetected={canDetectBarcode ? this.barcodeRecognized : null}
googleVisionBarcodeType={RNCamera.Constants.GoogleVisionBarcodeDetection.BarcodeType.ALL}
googleVisionBarcodeMode={RNCamera.Constants.GoogleVisionBarcodeMode.ALTERNATE}
>
<View
style={{

View File

@ -10,7 +10,9 @@ typedef void(^postRecognitionBlock)(NSArray *barcodes);
- (instancetype)init;
-(BOOL)isRealDetector;
-(NSInteger)fetchDetectionMode;
-(void)setType:(id)json queue:(dispatch_queue_t)sessionQueue;
-(void)setMode:(id)json queue:(dispatch_queue_t)sessionQueue;
-(void)findBarcodesInFrame:(UIImage *)image scaleX:(float)scaleX scaleY:(float)scaleY completed:(postRecognitionBlock)completed;
+(NSDictionary *)constants;

View File

@ -6,6 +6,7 @@
@property(nonatomic, strong) FIRVisionBarcodeDetector *barcodeRecognizer;
@property(nonatomic, strong) FIRVision *vision;
@property(nonatomic, assign) FIRVisionBarcodeFormat setOption;
@property(nonatomic, assign) NSInteger detectionMode;
@property(nonatomic, assign) float scaleX;
@property(nonatomic, assign) float scaleY;
@end
@ -26,6 +27,11 @@
return true;
}
-(NSInteger)fetchDetectionMode
{
return self.detectionMode;
}
+ (NSDictionary *)constants
{
return @{
@ -63,6 +69,12 @@
}
}
-(void)setMode:(id)json queue:(dispatch_queue_t)sessionQueue
{
NSInteger requestedValue = [RCTConvert NSInteger:json];
self.detectionMode = requestedValue;
}
- (void)findBarcodesInFrame:(UIImage *)uiImage
scaleX:(float)scaleX
scaleY:(float)scaleY

View File

@ -26,6 +26,7 @@
@property(nonatomic, strong) NSArray *barCodeTypes;
@property(nonatomic, strong) NSArray *googleVisionBarcodeTypes;
@property(nonatomic, assign) NSInteger *googleVisionBarcodeMode;
@property(nonatomic, assign) NSInteger presetCamera;
@property(nonatomic, copy) NSString *cameraId; // copy required for strings/pointers
@property(assign, nonatomic) NSInteger flashMode;
@ -76,6 +77,7 @@
- (void)updateRectOfInterest;
// google Barcode props
- (void)updateGoogleVisionBarcodeType:(id)requestedTypes;
- (void)updateGoogleVisionBarcodeMode:(id)requestedMode;
- (void)takePicture:(NSDictionary *)options
resolve:(RCTPromiseResolveBlock)resolve

View File

@ -43,6 +43,7 @@
@property (nonatomic, copy) RCTDirectEventBlock onSubjectAreaChanged;
@property (nonatomic, assign) BOOL isFocusedOnPoint;
@property (nonatomic, assign) BOOL isExposedOnPoint;
@property (nonatomic, assign) BOOL invertImageData;
@end
@ -89,6 +90,7 @@ BOOL _sessionInterrupted = NO;
self.cameraId = nil;
self.isFocusedOnPoint = NO;
self.isExposedOnPoint = NO;
self.invertImageData = false;
_recordRequested = NO;
_sessionInterrupted = NO;
@ -2175,6 +2177,11 @@ BOOL _sessionInterrupted = NO;
[self.barcodeDetector setType:requestedTypes queue:self.sessionQueue];
}
- (void)updateGoogleVisionBarcodeMode:(id)requestedMode
{
[self.barcodeDetector setMode:requestedMode queue:self.sessionQueue];
}
- (void)onBarcodesDetected:(NSDictionary *)event
{
if (_onGoogleVisionBarcodesDetected && _session) {
@ -2275,6 +2282,27 @@ BOOL _sessionInterrupted = NO;
if (canSubmitForBarcodeDetection) {
_finishedDetectingBarcodes = false;
self.startBarcode = [NSDate date];
// Check for the barcode detection mode (Normal, Alternate, Inverted)
switch ([self.barcodeDetector fetchDetectionMode]) {
case RNCameraGoogleVisionBarcodeModeNormal:
self.invertImageData = false;
break;
case RNCameraGoogleVisionBarcodeModeAlternate:
self.invertImageData = !self.invertImageData;
break;
case RNCameraGoogleVisionBarcodeModeInverted:
self.invertImageData = true;
break;
default:
self.invertImageData = false;
break;
}
if (self.invertImageData) {
image = [RNImageUtils invertColors:image];
}
[self.barcodeDetector findBarcodesInFrame:image scaleX:scaleX scaleY:scaleY completed:^(NSArray * barcodes) {
NSDictionary *eventBarcode = @{@"type" : @"barcode", @"barcodes" : barcodes};
[self onBarcodesDetected:eventBarcode];

View File

@ -55,6 +55,12 @@ typedef NS_ENUM(NSInteger, RNCameraVideoResolution) {
RNCameraVideo288p = 4,
};
typedef NS_ENUM(NSInteger, RNCameraGoogleVisionBarcodeMode) {
RNCameraGoogleVisionBarcodeModeNormal = 0,
RNCameraGoogleVisionBarcodeModeAlternate = 1,
RNCameraGoogleVisionBarcodeModeInverted = 2,
};
@interface RNCameraManager : RCTViewManager <RCTBridgeModule>
+ (NSDictionary *)validBarCodeTypes;

View File

@ -81,7 +81,12 @@ RCT_EXPORT_VIEW_PROPERTY(onTouch, RCTDirectEventBlock);
@"VideoStabilization": [[self class] validVideoStabilizationModes],
@"GoogleVisionBarcodeDetection": @{
@"BarcodeType": [[self class] barcodeDetectorConstants],
}
},
@"GoogleVisionBarcodeMode" : @{
@"NORMAL" : @(RNCameraGoogleVisionBarcodeModeNormal),
@"ALTERNATE" : @(RNCameraGoogleVisionBarcodeModeAlternate),
@"INVERTED" : @(RNCameraGoogleVisionBarcodeModeInverted),
},
};
}
@ -297,6 +302,11 @@ RCT_CUSTOM_VIEW_PROPERTY(googleVisionBarcodeType, NSString, RNCamera)
[view updateGoogleVisionBarcodeType:json];
}
RCT_CUSTOM_VIEW_PROPERTY(googleVisionBarcodeMode, NSInteger, RNCamera)
{
[view updateGoogleVisionBarcodeMode:json];
}
RCT_CUSTOM_VIEW_PROPERTY(googleVisionBarcodeDetectorEnabled, BOOL, RNCamera)
{
view.canDetectBarcodes = [RCTConvert BOOL:json];

View File

@ -18,6 +18,7 @@
+ (NSString *)writeImage:(NSData *)image toPath:(NSString *)path;
+ (UIImage *) scaleImage:(UIImage*)image toWidth:(NSInteger)width;
+ (void)updatePhotoMetadata:(CMSampleBufferRef)imageSampleBuffer withAdditionalData:(NSDictionary *)additionalData inResponse:(NSMutableDictionary *)response;
+ (UIImage *)invertColors:(UIImage *)image;
@end

View File

@ -115,5 +115,23 @@
response[@"exif"] = metadata;
}
+ (UIImage *)invertColors:(UIImage *)image
{
CIImage *inputCIImage = [[CIImage alloc] initWithImage:image];
// Invert colors
CIFilter *filterColorInvert = [CIFilter filterWithName:@"CIColorInvert"];
[filterColorInvert setValue:inputCIImage forKey:kCIInputImageKey];
CIImage *outputCIImage = [filterColorInvert valueForKey:kCIOutputImageKey];
// A UIImage initialized directly from CIImage has its CGImage property set to NULL. So it has
// to be converted to a CGImage first.
CIContext *context = [CIContext context];
CGImageRef outputCGImage = [context createCGImage:outputCIImage fromRect:[outputCIImage extent]];
UIImage *outputUIImage = [UIImage imageWithCGImage:outputCGImage];
return outputUIImage;
}
@end

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
0314E39D1B661A460092D183 /* CameraFocusSquare.m in Sources */ = {isa = PBXBuildFile; fileRef = 0314E39C1B661A460092D183 /* CameraFocusSquare.m */; };
1354413B2487FF390024C19F /* BarcodeDetectorManagerMlkit.m in Sources */ = {isa = PBXBuildFile; fileRef = 1354413A2487FF390024C19F /* BarcodeDetectorManagerMlkit.m */; };
2647D6712256BBD5007D2F91 /* FaceDetectorManagerMlkit.m in Sources */ = {isa = PBXBuildFile; fileRef = 2647D6702256BBD5007D2F91 /* FaceDetectorManagerMlkit.m */; };
2647D6742256BBE8007D2F91 /* RNFaceDetectorModuleMLKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 2647D6722256BBE8007D2F91 /* RNFaceDetectorModuleMLKit.m */; };
4107014D1ACB732B00C6AA39 /* RCTCamera.m in Sources */ = {isa = PBXBuildFile; fileRef = 410701481ACB732B00C6AA39 /* RCTCamera.m */; };
@ -37,6 +38,8 @@
/* Begin PBXFileReference section */
0314E39B1B661A0C0092D183 /* CameraFocusSquare.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraFocusSquare.h; sourceTree = "<group>"; };
0314E39C1B661A460092D183 /* CameraFocusSquare.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraFocusSquare.m; sourceTree = "<group>"; };
135441392487FF390024C19F /* BarcodeDetectorManagerMlkit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BarcodeDetectorManagerMlkit.h; sourceTree = "<group>"; };
1354413A2487FF390024C19F /* BarcodeDetectorManagerMlkit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BarcodeDetectorManagerMlkit.m; sourceTree = "<group>"; };
2647D66F2256BBD5007D2F91 /* FaceDetectorManagerMlkit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FaceDetectorManagerMlkit.h; sourceTree = "<group>"; };
2647D6702256BBD5007D2F91 /* FaceDetectorManagerMlkit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FaceDetectorManagerMlkit.m; sourceTree = "<group>"; };
2647D6722256BBE8007D2F91 /* RNFaceDetectorModuleMLKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFaceDetectorModuleMLKit.m; sourceTree = "<group>"; };
@ -112,6 +115,8 @@
714166162013E1B600EE9FCC /* RN */ = {
isa = PBXGroup;
children = (
135441392487FF390024C19F /* BarcodeDetectorManagerMlkit.h */,
1354413A2487FF390024C19F /* BarcodeDetectorManagerMlkit.m */,
F8393BEA21469C0000AB1995 /* RNSensorOrientationChecker.h */,
F8393BEB21469C0000AB1995 /* RNSensorOrientationChecker.m */,
2647D66F2256BBD5007D2F91 /* FaceDetectorManagerMlkit.h */,
@ -194,6 +199,7 @@
71C7FFD62013C824006EB75A /* RNFileSystem.m in Sources */,
4107014E1ACB732B00C6AA39 /* RCTCameraManager.m in Sources */,
4107014D1ACB732B00C6AA39 /* RCTCamera.m in Sources */,
1354413B2487FF390024C19F /* BarcodeDetectorManagerMlkit.m in Sources */,
F8393BEC21469C0000AB1995 /* RNSensorOrientationChecker.m in Sources */,
71C7FFD02013C7E5006EB75A /* RNCameraUtils.m in Sources */,
7162BE682013EAA400FE51FF /* RNCameraManager.m in Sources */,

View File

@ -247,7 +247,7 @@ type Rect = {
type PropsType = typeof View.props & {
zoom?: number,
useNativeZoom?:boolean,
useNativeZoom?: boolean,
maxZoom?: number,
ratio?: string,
focusDepth?: number,
@ -362,6 +362,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
VideoCodec: CameraManager.VideoCodec,
BarCodeType: CameraManager.BarCodeType,
GoogleVisionBarcodeDetection: CameraManager.GoogleVisionBarcodeDetection,
GoogleVisionBarcodeMode: CameraManager.GoogleVisionBarcodeMode,
FaceDetection: CameraManager.FaceDetection,
CameraStatus,
RecordAudioPermissionStatus: RecordAudioPermissionStatusEnum,
@ -379,6 +380,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
static ConversionTables = {
type: CameraManager.Type,
flashMode: CameraManager.FlashMode,
googleVisionBarcodeMode: CameraManager.GoogleVisionBarcodeMode,
exposure: CameraManager.Exposure,
autoFocus: CameraManager.AutoFocus,
whiteBalance: CameraManager.WhiteBalance,
@ -392,7 +394,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
static propTypes = {
...ViewPropTypes,
zoom: PropTypes.number,
useNativeZoom:PropTypes.bool,
useNativeZoom: PropTypes.bool,
maxZoom: PropTypes.number,
ratio: PropTypes.string,
focusDepth: PropTypes.number,
@ -445,7 +447,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
static defaultProps: Object = {
zoom: 0,
useNativeZoom:false,
useNativeZoom: false,
maxZoom: 0,
ratio: '4:3',
focusDepth: 0,
@ -651,7 +653,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
if (this.props.onTap && !nativeEvent.isDoubleTap) {
this.props.onTap(nativeEvent.touchOrigin);
}
if (this.props.onDoubleTap && nativeEvent.isDoubleTap){
if (this.props.onDoubleTap && nativeEvent.isDoubleTap) {
this.props.onTap(nativeEvent.touchOrigin);
}
};
@ -694,7 +696,7 @@ export default class Camera extends React.Component<PropsType, StateType> {
}
};
_onSubjectAreaChanged = e => {
_onSubjectAreaChanged = (e) => {
if (this.props.onSubjectAreaChanged) {
this.props.onSubjectAreaChanged(e);
}
@ -869,7 +871,6 @@ export default class Camera extends React.Component<PropsType, StateType> {
}
if (Platform.OS === 'ios') {
delete newProps.googleVisionBarcodeMode;
delete newProps.ratio;
}
@ -893,7 +894,7 @@ const RNCamera = requireNativeComponent('RNCamera', Camera, {
accessibilityLabel: true,
accessibilityLiveRegion: true,
barCodeScannerEnabled: true,
touchDetectorEnabled:true,
touchDetectorEnabled: true,
googleVisionBarcodeDetectorEnabled: true,
faceDetectorEnabled: true,
textRecognizerEnabled: true,
@ -905,7 +906,7 @@ const RNCamera = requireNativeComponent('RNCamera', Camera, {
onAudioConnected: true,
onPictureSaved: true,
onFaceDetected: true,
onTouch:true,
onTouch: true,
onLayout: true,
onMountError: true,
onSubjectAreaChanged: true,