React-Native+react-native-camera+react-native-image-picker实现扫码功能、拍照显示及从拍好的照片中读取二维码或条形码

2023-11-12

一、用到技术版本

1、react-native(0.59.8)

2、Android:buildToolsVersion = "28.0.3",minSdkVersion = 16,compileSdkVersion = 28,targetSdkVersion = 28,supportLibVersion = "28.0.0"

二、具体实现

react使用 

  • react-native-camera  (npm install --save react-native-camera@git+https://git@github.com/react-native-community/react-native-camera.git     ,  react-native link react-native-camera)
  • react-native-image-picker (npm install react-native-image-picker --save   , react-native link react-native-image-picker)
  • react-native-local-barcode-recognizer (npm install react-native-local-barcode-recognizer --save   ,  react-native link react-native-local-barcode-recognizer) 备选(npm install  rn-local-qrdecode,react-native link rn-local-qrdecode)
import React, { Component } from 'react';
import {
    AppRegistry,
    Navigator,
    TouchableOpacity,
    StyleSheet,
    Text,
    View,
    Image,
    Platform,
    Animated,
    Easing,
} from 'react-native';
import { RNCamera } from 'react-native-camera'
import LocalBarcodeRecognizer from 'react-native-local-barcode-recognizer';
//图片选择器
var ImagePicker = require('react-native-image-picker');
//图片选择器参数设置
var options = {
  title: '请选择图片来源',
  cancelButtonTitle:'取消',
  takePhotoButtonTitle:'拍照',
  chooseFromLibraryButtonTitle:'相册图片',
  storageOptions: {
    skipBackup: true,
    path: 'images'
  }
};
const imageBase64 = "data:image/jpeg;base64,";  
export default class electronicsOrder extends Component{
        constructor(props){
            super(props);
             // 初始化数据源
            this.state={
                tabTitle : this.props.tabLabel,
                avatarSource: null,
                textval:null,
                sourceData:null,
                viewAppear:false,
                moveAnim: new Animated.Value(0)
            }
        }
        componentDidMount() {
            //进入后直接开始扫描
            //this.startAnimation();
        }
        startAnimation = () => {
            this.state.moveAnim.setValue(0);
            Animated.timing(
                this.state.moveAnim,
                {
                    toValue: -200,
                    duration: 1500,
                    easing: Easing.linear
                }
            ).start(() => this.startAnimation());
        };
        //  识别二维码
        onBarCodeRead = (result) => {
            const { navigate } = this.props.navigation;
            const {data} = result;
            if(data!=null && data!=''){
                this.setState({textval:data,viewAppear:false},function(){
                
                });
            }
            //如果要跳转地址使用下面的
                // navigate('createOrder', {
                //     url: data
                // })
        };
    //选择照片按钮点击
   choosePic() {
        ImagePicker.showImagePicker(options, (response) => {
            console.log('Response = ', response);
            if (response.didCancel) {
                console.log('用户取消了选择!');
            }
            else if (response.error) {
                alert("ImagePicker发生错误:" + response.error);
            }
            else if (response.customButton) {
                alert("自定义按钮点击:" + response.customButton);
            }
            else {
                let source = { uri: response.uri };
                // You can also display the image using data:
                // let source = { uri: 'data:image/jpeg;base64,' + response.data };
                this.setState({
                    avatarSource: source,
                    sourceData:'data:image/jpeg;base64,' + response.data
                });
            }
        });
    }
    beginSaoMiao(){
        if(null!=this.state.avatarSource){
            this.recoginze();
        }
    } 
    
    recoginze = async ()=>{
        // Here is the demoe
         let result = await LocalBarcodeRecognizer.decode(this.state.sourceData.replace("data:image/jpeg;base64,",""),{codeTypes:['ean13','qr']});
         alert(result);
    }   
    clickSaoMiao(){
        this.setState({viewAppear: true},function(){
            this.startAnimation();
        });
    } 
    render(){
        return(
             <View style={styles.container}>
                <Text style={[{fontSize:20},styles.red]}>
                    {this.state.textval==null?this.state.tabTitle:this.state.textval}</Text>
                <Text style={styles.item} onPress={this.choosePic.bind(this)}>选择照片</Text>
                <Image source={this.state.avatarSource} style={styles.image} />
                <TouchableOpacity onPress={()=>this.beginSaoMiao()}>
                    <Text style={{fontSize:20,alignItems:'center'}}>获取图片中二维码或者条形码信息</Text>
                </TouchableOpacity>
                <TouchableOpacity onPress={()=>this.clickSaoMiao()}>
                    <Text style={{fontSize:20,alignItems:'center'}}>开始扫描</Text>
                </TouchableOpacity>
                {this.state.viewAppear ?
                    <View style={{flex: 1, backgroundColor: 'black',}}> 
                        <RNCamera
                            ref={ref => {
                                this.camera = ref;
                            }}
                            style={styles.preview}
                            type={RNCamera.Constants.Type.back}
                            flashMode={RNCamera.Constants.FlashMode.on}
                            onBarCodeRead={this.onBarCodeRead}
                        >
                            <View style={styles.rectangleContainer}>
                                <View style={styles.rectangle}/>
                                <Animated.View style={[
                                    styles.border,
                                    {transform: [{translateY: this.state.moveAnim}]}]}/>
                                <Text style={styles.rectangleText}>将二维码放入框内,即可自动扫描</Text>
                            </View>
                        </RNCamera>
                    </View>: null
                    }
            </View>
        );
    }
}
const styles = StyleSheet.create({
    center:{
        flex:1,
        justifyContent:'center',
        alignItems:'center',
    },
    red:{
        color:'#f00',
    },
    container:{
        flex: 1,
        // marginTop:25,
        backgroundColor: '#F5FCFF',
        // justifyContent: 'center',
        // alignItems: 'center',
       // flexDirection: 'row'
      },
      item:{
        margin:15,
        height:30,
        borderWidth:1,
        padding:6,
        borderColor:'#ddd',
        textAlign:'center'
      },
      image:{
       height:198,
       width:300,
       alignSelf:'center',
     },
     welcome: {
        fontSize: 20,
        textAlign: 'center',
        margin: 10,
      },
      instructions: {
        textAlign: 'center',
        color: '#333333',
        marginBottom: 5,
      },
      preview: {
        flex: 1,
        justifyContent: 'flex-end',
        alignItems: 'center'
    },
    rectangleContainer: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: 'transparent'
    },
    rectangle: {
        height: 200,
        width: 200,
        borderWidth: 1,
        borderColor: '#00FF00',
        backgroundColor: 'transparent'
    },
    rectangleText: {
        flex: 0,
        color: '#fff',
        marginTop: 10
    },
    border: {
        flex: 0,
        width: 200,
        height: 2,
        backgroundColor: '#00FF00',
    }
});

Android

1) react-native-camera 需要修改 android目录下build.gradle文件中allprojects的repositories信息 加入如下代码

maven {

  url 'https://maven.google.com'

}

maven {

  url "https://jitpack.io"

}

allprojects {
    repositories {
        mavenLocal()
        google()
        jcenter()
        maven {
            url 'https://maven.google.com'
        }
        maven {
            url "https://jitpack.io"
        }
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url "$rootDir/../node_modules/react-native/android"
        }

    }
}

继续修改android/app/build.gradle文件

在defaultConfig中加入missingDimensionStrategy 'react-native-camera', 'general' 。因为加入三个第三方包导致android执行数量超过65536,所以在defaultConfig中继续加入如下代码:multiDexEnabled true

例如

 defaultConfig {
        applicationId "com.demo"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
        missingDimensionStrategy 'react-native-camera', 'general' 
        multiDexEnabled true
        
    }

2)react-native-local-barcode-recognizer

因为默认版本与我使用的不一致 我修改了其源码版本配置如下,

buildscript {
    repositories {
        jcenter()
        google()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.1' //修改为一直3.3.1
    }
}

apply plugin: 'com.android.library'

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.3"

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
    }
    lintOptions {
        abortOnError false
    }
    sourceSets {
        main {
            aidl.srcDirs = ['src/main/java']
        }
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'com.facebook.react:react-native:+'
    implementation "com.google.zxing:core:3.3.0"
}

因为扫描识别率较低,有些还不能识别所以我参照原作者cicistream 的代码进行了如下修改 ,可以直接覆盖整个文件

文件路径android/src/main/java/cn.jystudio.local.barcode.recognizer.LocalBarcodeRecognizerModule.java

完整代码:

package cn.jystudio.local.barcode.recognizer;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.util.Base64;
import com.facebook.react.bridge.*;
import com.google.zxing.*;
import com.google.zxing.common.HybridBinarizer;

import java.util.*;

public class LocalBarcodeRecognizerModule extends ReactContextBaseJavaModule {
    public static final String BARCODE_CODE_TYPE_KEY="codeTypes";

    public static final Map<String, Object> VALID_BARCODE_TYPES =
            Collections.unmodifiableMap(new HashMap<String, Object>() {
                {
                    put("aztec", BarcodeFormat.AZTEC.toString());
                    put("ean13", BarcodeFormat.EAN_13.toString());
                    put("ean8", BarcodeFormat.EAN_8.toString());
                    put("qr", BarcodeFormat.QR_CODE.toString());
                    put("pdf417", BarcodeFormat.PDF_417.toString());
                    put("upc_e", BarcodeFormat.UPC_E.toString());
                    put("datamatrix", BarcodeFormat.DATA_MATRIX.toString());
                    put("code39", BarcodeFormat.CODE_39.toString());
                    put("code93", BarcodeFormat.CODE_93.toString());
                    put("interleaved2of5", BarcodeFormat.ITF.toString());
                    put("codabar", BarcodeFormat.CODABAR.toString());
                    put("code128", BarcodeFormat.CODE_128.toString());
                    put("maxicode", BarcodeFormat.MAXICODE.toString());
                    put("rss14", BarcodeFormat.RSS_14.toString());
                    put("rssexpanded", BarcodeFormat.RSS_EXPANDED.toString());
                    put("upc_a", BarcodeFormat.UPC_A.toString());
                    put("upc_ean", BarcodeFormat.UPC_EAN_EXTENSION.toString());
                }
            });


    public LocalBarcodeRecognizerModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    /**
     * @return the name of this module. This will be the name used to {@code require()} this module
     * from javascript.
     */
    @Override
    public String getName() {
        return "LocalBarcodeRecognizer";
    }

    @ReactMethod
    public void decode(String base64Data, ReadableMap options, final Promise p){
        try {
            byte[] decodedString =  Base64.decode(base64Data,Base64.DEFAULT);
            Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);

            Result result = null;
            MultiFormatReader reader = new MultiFormatReader();

            if(options.hasKey(BARCODE_CODE_TYPE_KEY)){
                ReadableArray codeTypes = options.getArray(BARCODE_CODE_TYPE_KEY);
                if(codeTypes.size()>0) {
                    EnumMap<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
                    EnumSet<BarcodeFormat> decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
                    for(int i=0;i<codeTypes.size();i++){
                        String code = codeTypes.getString(i);
                        String formatString = (String) VALID_BARCODE_TYPES.get(code);
                        if(formatString!=null){
                            decodeFormats.add(BarcodeFormat.valueOf(formatString));
                        }
                    }
                    hints.put(DecodeHintType.POSSIBLE_FORMATS,decodeFormats);
                    reader.setHints(hints);
                }
            }

            try {
                BinaryBitmap bitmap = generateBitmapFromImageData(decodedByte);
                result = reader.decode(bitmap);
            } catch (NotFoundException e) {
                BinaryBitmap bitmap = generateBitmapFromImageData(rotateImage(decodedByte,90));
                try {
                    result = reader.decode(bitmap);
                } catch (NotFoundException e1) {
                    //no barcode Found
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }

            p.resolve(result!=null?result.getText():"");
        }catch (Exception e){
            p.reject(e);
        }
    }

    // private BinaryBitmap generateBitmapFromImageData(Bitmap bitmap) {
    //     int[] mImageData = new int[bitmap.getWidth()*bitmap.getHeight()];
    //     bitmap.getPixels(mImageData, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
    //     LuminanceSource source = new RGBLuminanceSource(bitmap.getWidth(), bitmap.getHeight(),mImageData);
    //     return new BinaryBitmap(new HybridBinarizer(source));
    // }
    private BinaryBitmap generateBitmapFromImageData(Bitmap bitmap) {
        bitmap = getSmallerBitmap(bitmap);
        int[] mImageData = new int[bitmap.getWidth() * bitmap.getHeight()];
        bitmap.getPixels(mImageData, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
        int inputWidth = bitmap.getWidth();
        int inputHeight = bitmap.getHeight();
        byte[] yuv = new byte[inputWidth * inputHeight + ((inputWidth % 2 == 0 ? inputWidth : (inputWidth + 1))
                * (inputHeight % 2 == 0 ? inputHeight : (inputHeight + 1))) / 2];
        encodeYUV420SP(yuv, mImageData, inputWidth, inputHeight);
        bitmap.recycle();
        PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(yuv, // byte[] yuvData
                inputWidth, // int dataWidth
                inputHeight, // int dataHeight
                0, // int left
                0, // int top
                inputWidth, // int width
                inputHeight, // int height
                false // boolean reverseHorizontal
        );
        return new BinaryBitmap(new HybridBinarizer(source));
    }

    private static void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
        // 帧图片的像素大小
        final int frameSize = width * height;
        // Y的index从0开始
        int yIndex = 0;
        // UV的index从frameSize开始
        int uvIndex = frameSize;
        // YUV数据, ARGB数据
        int Y, U, V, a, R, G, B;
        ;
        int argbIndex = 0;
        // ---循环所有像素点,RGB转YUV---
        for (int j = 0; j < height; j++) {
            for (int i = 0; i < width; i++) {

                // a is not used obviously
                a = (argb[argbIndex] & 0xff000000) >> 24;
                R = (argb[argbIndex] & 0xff0000) >> 16;
                G = (argb[argbIndex] & 0xff00) >> 8;
                B = (argb[argbIndex] & 0xff);
                argbIndex++;

                // well known RGB to YUV algorithm
                Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
                U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
                V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;

                Y = Math.max(0, Math.min(Y, 255));
                U = Math.max(0, Math.min(U, 255));
                V = Math.max(0, Math.min(V, 255));
                yuv420sp[yIndex++] = (byte) Y;
                // ---UV---
                if ((j % 2 == 0) && (i % 2 == 0)) {
                    yuv420sp[uvIndex++] = (byte) V;
                    yuv420sp[uvIndex++] = (byte) U;
                }
            }
        }
    }

    private Bitmap rotateImage(Bitmap src, float degree)
    {
        // create new matrix
        Matrix matrix = new Matrix();
        // setup rotation degree
        matrix.postRotate(degree);
        Bitmap bmp = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
        return bmp;
    }
    private  Bitmap getSmallerBitmap(Bitmap bitmap){
        int size = bitmap.getWidth() * bitmap.getHeight() / 160000;
        if (size <= 1){
            return bitmap; // 如果小于
        }else {
            Matrix matrix = new Matrix();
            matrix.postScale((float) (1 / Math.sqrt(size)), (float) (1 / Math.sqrt(size)));
            Bitmap resizeBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix,true);
            return resizeBitmap;
        }
    }

}

上传几张示例图

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React-Native+react-native-camera+react-native-image-picker实现扫码功能、拍照显示及从拍好的照片中读取二维码或条形码 的相关文章

随机推荐