From 7c335bbe4e11bf7bcd634f1ae4e14c9037dde8de Mon Sep 17 00:00:00 2001 From: Gevorg Gasparyan Date: Wed, 20 Oct 2021 16:55:35 +0400 Subject: [PATCH] feat: Add more code types to decode --- README.md | 20 +++ RNQrGenerator.podspec | 1 + .../reactlibrary/RNQrGeneratorModule.java | 67 +++++++-- example/QRGeneratorExample/App.js | 3 +- example/QRGeneratorExample/package.json | 2 +- index.js | 5 + ios/RNQrGenerator.m | 132 +++++++++++++----- package.json | 6 +- typings/index.d.ts | 5 + 9 files changed, 193 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 0f995af..fba703c 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,24 @@ payload | Property | Type | Description | | :------------- | :------: | :----------- | | values | string[] | Array of detected QR code values. Empty if nothing found. +| type | string | Type of detected code. + +The following barcode types are currently supported for decoding: + +* UPC-A and UPC-E +* EAN-8 and EAN-13 +* Code 39 +* Code 93 +* Code 128 +* ITF +* Codabar +* RSS-14 (all variants) +* QR Code +* Data Matrix +* Maxicode +* Aztec ('beta' quality) +* PDF 417 ('beta' quality) + ![example](https://user-images.githubusercontent.com/13519034/104821872-50268480-5858-11eb-9e5b-77190f9da71d.gif) @@ -132,5 +150,7 @@ RNQRGenerator.generate({ More information about totp can be found [here](https://github.com/google/google-authenticator/wiki/Key-Uri-Format). + +This module uses `Zxing` library for encoding and decoding codes ( [ios](https://github.com/zxingify/zxingify-objc), [Android](https://github.com/journeyapps/zxing-android-embedded)). # Note Some simulators may not generate qr code properly. Use real device if you get an error. diff --git a/RNQrGenerator.podspec b/RNQrGenerator.podspec index 11d56a8..44bb537 100644 --- a/RNQrGenerator.podspec +++ b/RNQrGenerator.podspec @@ -18,6 +18,7 @@ Pod::Spec.new do |s| s.requires_arc = true s.dependency "React" + s.dependency "ZXingObjC" end \ No newline at end of file diff --git a/android/src/main/java/com/gevorg/reactlibrary/RNQrGeneratorModule.java b/android/src/main/java/com/gevorg/reactlibrary/RNQrGeneratorModule.java index 8226bd5..4262b95 100644 --- a/android/src/main/java/com/gevorg/reactlibrary/RNQrGeneratorModule.java +++ b/android/src/main/java/com/gevorg/reactlibrary/RNQrGeneratorModule.java @@ -8,6 +8,7 @@ import android.graphics.Color; import android.net.Uri; import android.util.Base64; +import android.util.Log; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactApplicationContext; @@ -38,7 +39,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.EnumMap; +import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -46,6 +49,7 @@ public class RNQrGeneratorModule extends ReactContextBaseJavaModule { private final ReactApplicationContext reactContext; private final static String SCHEME_CONTENT = "content"; + private final String TAG = "RNQRGenerator"; public RNQrGeneratorModule(ReactApplicationContext reactContext) { super(reactContext); @@ -143,24 +147,68 @@ public void detect(final ReadableMap options, @Nullable Callback failureCallback } } try { - String decoded = scanQRImage(bitmap); - onDetectResult(decoded, successCallback); + Result result = scanQRImage(bitmap); + BarcodeFormat format = result.getBarcodeFormat(); + String codeType = getCodeType(format); + onDetectResult(result.getText(), codeType, successCallback); } catch (Exception e) { e.printStackTrace(); - onDetectResult("", successCallback); + onDetectResult("", "", successCallback); } } - private void onDetectResult(String result, Callback successCallback) { + private void onDetectResult(String result, String type, Callback successCallback) { WritableArray values = Arguments.createArray(); if (result != "") { values.pushString(result); } WritableMap response = Arguments.createMap(); response.putArray("values", values); + response.putString("type", type); successCallback.invoke(response); } + private String getCodeType(BarcodeFormat format) { + + switch (format) { + case AZTEC: + return "Aztec"; + case CODABAR: + return "Codabar"; + case CODE_39: + return "Code39"; + case CODE_93: + return "Code93"; + case CODE_128: + return "Code128"; + case DATA_MATRIX: + return "DataMatrix"; + case EAN_8: + return "Ean8"; + case EAN_13: + return "Ean13"; + case ITF: + return "ITF"; + case MAXICODE: + return "MaxiCode"; + case PDF_417: + return "PDF417"; + case QR_CODE: + return "QRCode"; + case RSS_14: + return "RSS14"; + case RSS_EXPANDED: + return "RSSExpanded"; + case UPC_A: + return "UPCA"; + case UPC_E: + return "UPCE"; + case UPC_EAN_EXTENSION: + return "UPCEANExtension"; + default: + return ""; + } + } public static Bitmap generateQrCode(String myCodeText, int qrWidth, int qrHeight, int backgroundColor, int color, String correctionLevel) throws WriterException { /** * Allow the zxing engine use the default argument for the margin variable @@ -206,9 +254,7 @@ public static Bitmap generateQrCode(String myCodeText, int qrWidth, int qrHeight return bitmap; } - public static String scanQRImage(Bitmap bMap) throws Exception { - String contents = null; - + public static Result scanQRImage(Bitmap bMap) throws Exception { int[] intArray = new int[bMap.getWidth()*bMap.getHeight()]; //copy pixel data from the Bitmap into the 'intArray' array bMap.getPixels(intArray, 0, bMap.getWidth(), 0, 0, bMap.getWidth(), bMap.getHeight()); @@ -219,12 +265,11 @@ public static String scanQRImage(Bitmap bMap) throws Exception { Reader reader = new MultiFormatReader(); try { Result result = reader.decode(bitmap); - contents = result.getText(); - } - catch (Exception e) { + return result; + } catch (Exception e) { + Log.e("RNQRGenerator", "Decode Failed:", e); throw e; } - return contents; } public static File ensureDirExists(File dir) throws IOException { diff --git a/example/QRGeneratorExample/App.js b/example/QRGeneratorExample/App.js index fe716d0..f6618c3 100644 --- a/example/QRGeneratorExample/App.js +++ b/example/QRGeneratorExample/App.js @@ -71,8 +71,9 @@ const App: () => React$Node = () => { setDetectImageUri({uri: response.uri}); RNQRGenerator.detect({uri: response.uri}) .then((res) => { + console.log('Detected', res); if (res.values.length === 0) { - setDetectedValues(['QR code not found']); + setDetectedValues(['Code not found']); } else { setDetectedValues(res.values); } diff --git a/example/QRGeneratorExample/package.json b/example/QRGeneratorExample/package.json index 1b087ce..20ab79a 100644 --- a/example/QRGeneratorExample/package.json +++ b/example/QRGeneratorExample/package.json @@ -13,7 +13,7 @@ "react": "16.13.1", "react-native": "0.63.4", "react-native-image-picker": "^3.1.3", - "rn-qr-generator": "^1.1.4" + "rn-qr-generator": "^1.2.0" }, "devDependencies": { "@babel/core": "^7.12.10", diff --git a/index.js b/index.js index a5d5c13..03570a0 100644 --- a/index.js +++ b/index.js @@ -41,8 +41,13 @@ export type QRCodeDetectOptions = { base64?: string, }; +export type CodeType = 'Aztec' | 'Codabar' | 'Code39' | 'Code93' | 'Code128' | + 'DataMatrix' | 'Ean8' | 'Ean13' | 'ITF' | 'MaxiCode' | 'PDF417' | 'QRCode' | + 'RSS14' | 'RSSExpanded' | 'UPCA' | 'UPCE' | 'UPCEANExtension'; + export type QRCodeScanResult = { values: string[], + type: CodeType }; export default { diff --git a/ios/RNQrGenerator.m b/ios/RNQrGenerator.m index 1c73f41..621db75 100644 --- a/ios/RNQrGenerator.m +++ b/ios/RNQrGenerator.m @@ -8,6 +8,8 @@ #import "React/RCTConvert.h" // Required when used as a Pod in a Swift project #endif +#import + @implementation RNQrGenerator - (dispatch_queue_t)methodQueue @@ -20,7 +22,6 @@ - (dispatch_queue_t)methodQueue failureCallback:(RCTResponseErrorBlock)failureCallback successCallback:(RCTResponseSenderBlock)successCallback) { - NSString *qrData = [RCTConvert NSString:options[@"value"]]; NSString *level = [RCTConvert NSString:options[@"correctionLevel"]]; NSString *fileName = [RCTConvert NSString:options[@"fileName"]]; @@ -127,40 +128,64 @@ - (dispatch_queue_t)methodQueue RCTLogWarn(@"key 'uri' or 'base64' are missing in options"); return; } - CIImage* ciImage = [[CIImage alloc] initWithImage:image]; - - NSMutableDictionary* detectorOptions; - detectorOptions[CIDetectorAccuracy] = CIDetectorAccuracyHigh; -// detectorOptions[CIDetectorAccuracy] = CIDetectorAccuracyLow; // Fast but superficial - - if (@available(iOS 8.0, *)) { - CIDetector* qrDetector = [CIDetector detectorOfType:CIDetectorTypeQRCode - context:NULL - options:options]; - if ([[ciImage properties] valueForKey:(NSString*) kCGImagePropertyOrientation] == nil) { - detectorOptions[CIDetectorImageOrientation] = @1; - } else { - id orientation = [[ciImage properties] valueForKey:(NSString*) kCGImagePropertyOrientation]; - detectorOptions[CIDetectorImageOrientation] = orientation; - } + ZXLuminanceSource *source = [[ZXCGImageLuminanceSource alloc] initWithCGImage:image.CGImage]; + ZXBinaryBitmap *bitmap = [ZXBinaryBitmap binaryBitmapWithBinarizer:[ZXHybridBinarizer binarizerWithSource:source]]; - NSArray * features = [qrDetector featuresInImage:ciImage - options:detectorOptions]; - NSMutableArray *rawValues = [NSMutableArray array]; - [features enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [rawValues addObject: [obj messageString]]; - }]; - NSMutableDictionary *response = [[NSMutableDictionary alloc] init]; - response[@"values"] = rawValues; - successCallback(@[response]); + NSError *error = nil; - } else { - NSString *errorMessage = @"QRCode iOS 8+ required"; - NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedString(errorMessage, nil)}; - NSError *error = [NSError errorWithDomain:@"com.rnqrcode" code:1 userInfo:userInfo]; - failureCallback(error); - RCTLogWarn(@"Required iOS 8 or later"); - } + // There are a number of hints we can give to the reader, including + // possible formats, allowed lengths, and the string encoding. + ZXDecodeHints *hints = [ZXDecodeHints hints]; + [hints setTryHarder:TRUE]; + + ZXMultiFormatReader *reader = [ZXMultiFormatReader reader]; + ZXResult *result = [reader decode:bitmap + hints:hints + error:&error]; + NSMutableDictionary *response = [[NSMutableDictionary alloc] init]; + if (result) { + // The coded result as a string. The raw data can be accessed with + // result.rawBytes and result.length. + NSString *contents = result.text; + + // The barcode format, such as a QR code or UPC-A + ZXBarcodeFormat format = result.barcodeFormat; + response[@"values"] = @[contents]; + response[@"type"] = [self getCodeType:format]; + successCallback(@[response]); + } else { + CIImage* ciImage = [[CIImage alloc] initWithImage:image]; + NSMutableDictionary* detectorOptions; + detectorOptions[CIDetectorAccuracy] = CIDetectorAccuracyHigh; + if (@available(iOS 8.0, *)) { + CIDetector* qrDetector = [CIDetector detectorOfType:CIDetectorTypeQRCode + context:NULL + options:options]; + if ([[ciImage properties] valueForKey:(NSString*) kCGImagePropertyOrientation] == nil) { + detectorOptions[CIDetectorImageOrientation] = @1; + } else { + id orientation = [[ciImage properties] valueForKey:(NSString*) kCGImagePropertyOrientation]; + detectorOptions[CIDetectorImageOrientation] = orientation; + } + + NSArray * features = [qrDetector featuresInImage:ciImage + options:detectorOptions]; + NSMutableArray *rawValues = [NSMutableArray array]; + [features enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + [rawValues addObject: [obj messageString]]; + }]; + NSMutableDictionary *response = [[NSMutableDictionary alloc] init]; + response[@"values"] = rawValues; + response[@"type"] = @"QRCode"; + successCallback(@[response]); + } else { + NSString *errorMessage = @"QRCode iOS 8+ required"; + NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedString(errorMessage, nil)}; + NSError *error = [NSError errorWithDomain:@"com.rnqrcode" code:1 userInfo:userInfo]; + failureCallback(error); + RCTLogWarn(@"Required iOS 8 or later"); + } + } } - (NSString *)generatePathInDirectory:(NSString *)directory fileName:(NSString *)name withExtension:(NSString *)extension @@ -184,6 +209,47 @@ - (UIImage *)imageFromBase64:(NSString *)base64String return [UIImage imageWithData:imageData]; } +- (NSString*) getCodeType:(ZXBarcodeFormat) format { + switch (format) { + case kBarcodeFormatAztec: + return @"Aztec"; + case kBarcodeFormatCodabar: + return @"Codabar"; + case kBarcodeFormatCode39: + return @"Code39"; + case kBarcodeFormatCode93: + return @"Code93"; + case kBarcodeFormatCode128: + return @"Code128"; + case kBarcodeFormatDataMatrix: + return @"DataMatrix"; + case kBarcodeFormatEan8: + return @"Ean8"; + case kBarcodeFormatEan13: + return @"Ean13"; + case kBarcodeFormatITF: + return @"ITF"; + case kBarcodeFormatMaxiCode: + return @"MaxiCode"; + case kBarcodeFormatPDF417: + return @"PDF417"; + case kBarcodeFormatQRCode: + return @"QRCode"; + case kBarcodeFormatRSS14: + return @"RSS14"; + case kBarcodeFormatRSSExpanded: + return @"RSSExpanded"; + case kBarcodeFormatUPCA: + return @"UPCA"; + case kBarcodeFormatUPCE: + return @"UPCE"; + case kBarcodeFormatUPCEANExtension: + return @"UPCEANExtension"; + default: + return @""; + } +} + - (BOOL)ensureDirExistsWithPath:(NSString *)path { BOOL isDir = NO; diff --git a/package.json b/package.json index 240fd75..d450437 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "rn-qr-generator", - "version": "1.1.7", - "description": "React native QR Code generator", + "version": "1.2.0", + "description": "React native QR Code generator / reader", "main": "index.js", "types": "typings/index.d.ts", "files": [ @@ -18,6 +18,8 @@ "react-native", "qr", "qrcode", + "Data Matrix", + "DataMatrix", "qr generator", "qrcode image", "qr decode", diff --git a/typings/index.d.ts b/typings/index.d.ts index 83033fc..e39687c 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -34,8 +34,13 @@ export interface QRCodeDetectOptions { base64?: string; }; +export type CodeType = 'Aztec' | 'Codabar' | 'Code39' | 'Code93' | 'Code128' | + 'DataMatrix' | 'Ean8' | 'Ean13' | 'ITF' | 'MaxiCode' | 'PDF417' | 'QRCode' | + 'RSS14' | 'RSSExpanded' | 'UPCA' | 'UPCE' | 'UPCEANExtension'; + export interface QRCodeScanResult { values: Array; + type: CodeType; }; declare namespace RNQRGenerator {