美文网首页图像处理
Zxing扫描二维码和识别二维码图片

Zxing扫描二维码和识别二维码图片

作者: 取了个很好听的名字 | 来源:发表于2019-06-03 17:39 被阅读0次

    需求说明

    前面几篇文章讲述了Zxing自定义扫描界面,但是没有说明如何扫描二维码和识别二维码图片,通过这篇文章希望给大家一些思路。

    修改过程

    1.识别二维码图片

    首先导入知乎的图片选择器

    //图片选择
        implementation 'com.zhihu.android:matisse:0.5.1'
    

    在CaptureActivitty中初始化matisse

          Matisse.from(CaptureActivity.this)
                                    .choose(MimeType.ofAll())//图片类型
                                    .countable(true)//是否显示选择图片的数字
                                    .maxSelectable(1)//最大图片选择数
                                    //gridExpectedSize(120)getResources().getDimensionPixelSize(R.dimen.grid_expected_size)   显示图片大小
                                    .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)//图像选择和预览活动所需的方向
                                    .thumbnailScale(0.85f)//清晰度
                                    .theme(R.style.Matisse_Zhihu)//主题样式
                                    .imageEngine(new MyGlideEngine())//图片加载引擎
                                    .forResult(REQUEST_CODE);
    

    注意matisse使用Glide的是3.7.X的版本如果你的Glide版本为4.X.X,请修改图片加载引擎,按照matisse的GlideEngine重写出一个Glide 4版本的图片加载引擎,代码如下:

    public class MyGlideEngine implements  ImageEngine {
    
    
        @Override
        public void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
            Glide.with(context)
                    .asBitmap()  // some .jpeg files are actually gif
                    .load(uri)
                    .override(resize, resize)
                    .centerCrop()
                    .into(imageView);
    
        }
    
        @Override
        public void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
            Glide.with(context)
                    .asBitmap()
                    .load(uri)
                    .placeholder(placeholder)
                    .override(resize, resize)
                    .centerCrop()
                    .into(imageView);
    
        }
    
        @Override
        public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
            Glide.with(context)
                    .load(uri)
                    .override(resizeX, resizeY)
                    .priority(Priority.HIGH)
                    .into(imageView);
        }
    
        @Override
        public void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
            Glide.with(context)
                    .load(uri)
                    .override(resizeX, resizeY)
                    .priority(Priority.HIGH)
                    .into(imageView);
        }
    
        @Override
        public boolean supportAnimatedGif() {
            return true;
        }
    }
    

    读取图片需要Android权限,不要忘记在6.0以上动态获取

     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
      <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    
    tv_picture.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    .subscribe(granted->{
                          if (granted){
                            Matisse.from(CaptureActivity.this)
                                    .choose(MimeType.ofAll())//图片类型
                                    .countable(true)//是否显示选择图片的数字
                                    .maxSelectable(1)//最大图片选择数
                                    //gridExpectedSize(120)getResources().getDimensionPixelSize(R.dimen.grid_expected_size)   显示图片大小
                                    .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)//图像选择和预览活动所需的方向
                                    .thumbnailScale(0.85f)//清晰度
                                    .theme(R.style.Matisse_Zhihu)
                                    .imageEngine(new MyGlideEngine())
                                    .forResult(REQUEST_CODE);//请求码
                          }else{
                                    Toast.makeText(CaptureActivity.this,"未获取相关权限,请稍后再试",Toast.LENGTH_SHORT).show();
                          }
                    });
    
          }
        });
    

    在onActivityResult获取选中图片的uri,这里使用了Glide的SimpleTarget,作用是选中图片的bitmap对象

     @Override
      public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (resultCode == RESULT_OK && requestCode == HISTORY_REQUEST_CODE && historyManager != null) {
          int itemNumber = intent.getIntExtra(Intents.History.ITEM_NUMBER, -1);
          if (itemNumber >= 0) {
            HistoryItem historyItem = historyManager.buildHistoryItem(itemNumber);
            decodeOrStoreSavedBitmap(null, historyItem.getResult());
          }
        }
    
        //获取选中的照片
        if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
          List<Uri> list = Matisse.obtainResult(intent);
          Glide.with(this).asBitmap().load(list.get(0)).into(target);
    
        }
      }
    
    private SimpleTarget target = new SimpleTarget<Bitmap>() {
    
        @Override
        public void onResourceReady(@NonNull Bitmap bitmap, @Nullable Transition<? super Bitmap> transition) {
          String s = decodeQRCode(bitmap);
           if (s!=null){
            runOnUiThread(()-> {
                Toast.makeText(CaptureActivity.this,s,Toast.LENGTH_SHORT).show();
            });
          }
        }
      };
    

    decodeQRcode(Bitmap bitmap)是解析图片二维码的核心方法,通过调用zxing的BinaryBitmap将图片转换成二进制图片对象,再由QRCodeReader解析出信息

      /**
       * 解析图片
       *
       * @param srcBitmap
       * @return
       */
      public static String decodeQRCode(Bitmap srcBitmap) {
        // 解码的参数
        Hashtable<DecodeHintType, Object> hints = new Hashtable<>(2);
        // 可以解析的编码类型
        Vector<BarcodeFormat> decodeFormats = new Vector<>();
        if (decodeFormats.isEmpty()) {
          decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
        }
        hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
        hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
        Result result = null;
        int width = srcBitmap.getWidth();
        int height = srcBitmap.getHeight();
        int[] pixels = new int[width * height];
        srcBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
        //新建一个RGBLuminanceSource对象
        RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels);
        //将图片转换成二进制图片
        BinaryBitmap binaryBitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
        QRCodeReader reader = new QRCodeReader();//初始化解析对象
        try {
          result = reader.decode(binaryBitmap, hints);//开始解析
        } catch (NotFoundException | ChecksumException | FormatException e) {
          e.printStackTrace();
        }
        if (result != null) {
          return result.getText();
        }
        return null;
      }
    

    最终效果如图


    最终结果.gif

    可以看到读取除了图片中的链接

    2扫描二维码

    当开启CaptureActivity时设置的action为com.google.zxing.client.android.SCAN时,扫描后会将结果返回


    image.png
      @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == 0 && resultCode == RESULT_OK) {
                if (data != null) {
                    String content = data.getStringExtra("SCAN_RESULT");
                    Log.e("result",content);
                }
            }
    
    
        }
    

    可能你会问我怎么知道是key是SCAN_RESULT?你可以看一下CaptureActivityHandler中的handlerMessage

     @Override
      public void handleMessage(Message message) {
        if(message.what==R.id.restart_preview){
          restartPreviewAndDecode();
        }else if (message.what==R.id.decode_succeeded){
          state = State.SUCCESS;
          Bundle bundle = message.getData();
          Bitmap barcode = null;
          float scaleFactor = 1.0f;
          if (bundle != null) {
            byte[] compressedBitmap = bundle.getByteArray(DecodeThread.BARCODE_BITMAP);
            if (compressedBitmap != null) {
              barcode = BitmapFactory.decodeByteArray(compressedBitmap, 0, compressedBitmap.length, null);
              // Mutable copy:
              barcode = barcode.copy(Bitmap.Config.ARGB_8888, true);
            }
            scaleFactor = bundle.getFloat(DecodeThread.BARCODE_SCALED_FACTOR);
          }
          activity.handleDecode((Result) message.obj, barcode, scaleFactor);
        }else  if (message.what==R.id.decode_failed){
          // We're decoding as fast as possible, so when one decode fails, start another.
          state = State.PREVIEW;
          cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
        }else  if (message.what==R.id.return_scan_result){
          activity.setResult(Activity.RESULT_OK, (Intent) message.obj);
          activity.finish();
        }else if (message.what==R.id.launch_product_query){
          String url = (String) message.obj;
    
          Intent intent = new Intent(Intent.ACTION_VIEW);
          intent.addFlags(Intents.FLAG_NEW_DOC);
          intent.setData(Uri.parse(url));
    
          ResolveInfo resolveInfo =
                  activity.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
          String browserPackageName = null;
          if (resolveInfo != null && resolveInfo.activityInfo != null) {
            browserPackageName = resolveInfo.activityInfo.packageName;
            Log.d(TAG, "Using browser in package " + browserPackageName);
          }
    
          // Needed for default Android browser / Chrome only apparently
          if (browserPackageName != null) {
            switch (browserPackageName) {
              case "com.android.browser":
              case "com.android.chrome":
                intent.setPackage(browserPackageName);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra(Browser.EXTRA_APPLICATION_ID, browserPackageName);
                break;
            }
          }
    
          try {
            activity.startActivity(intent);
          } catch (ActivityNotFoundException ignored) {
            Log.w(TAG, "Can't find anything to handle VIEW of URI");
          }
        }
    
      }
    

    其中message.what==R.id.return_scan_result,在CaptureActivity中查找该id可以找到如下信息:

       case NATIVE_APP_INTENT:
            // Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when
            // the deprecated intent is retired.
            Intent intent = new Intent(getIntent().getAction());
            intent.addFlags(Intents.FLAG_NEW_DOC);
            intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
            intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
            byte[] rawBytes = rawResult.getRawBytes();
            if (rawBytes != null && rawBytes.length > 0) {
              intent.putExtra(Intents.Scan.RESULT_BYTES, rawBytes);
            }
            Map<ResultMetadataType, ?> metadata = rawResult.getResultMetadata();
            if (metadata != null) {
              if (metadata.containsKey(ResultMetadataType.UPC_EAN_EXTENSION)) {
                intent.putExtra(Intents.Scan.RESULT_UPC_EAN_EXTENSION,
                    metadata.get(ResultMetadataType.UPC_EAN_EXTENSION).toString());
              }
              Number orientation = (Number) metadata.get(ResultMetadataType.ORIENTATION);
              if (orientation != null) {
                intent.putExtra(Intents.Scan.RESULT_ORIENTATION, orientation.intValue());
              }
              String ecLevel = (String) metadata.get(ResultMetadataType.ERROR_CORRECTION_LEVEL);
              if (ecLevel != null) {
                intent.putExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL, ecLevel);
              }
              @SuppressWarnings("unchecked")
              Iterable<byte[]> byteSegments = (Iterable<byte[]>) metadata.get(ResultMetadataType.BYTE_SEGMENTS);
              if (byteSegments != null) {
                int i = 0;
                for (byte[] byteSegment : byteSegments) {
                  intent.putExtra(Intents.Scan.RESULT_BYTE_SEGMENTS_PREFIX + i, byteSegment);
                  i++;
                }
              }
            }
            sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS);
            break;
    

    注释中说 action 为Intents.Scan.ACTION 时 会发送sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS),返回的结果会封装在key 为SCAN_RESULT;Intents对RESULT 有相关的解释

       /**
         * If a barcode is found, Barcodes returns {@link android.app.Activity#RESULT_OK} to
         * {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)}
         * of the app which requested the scan via
         * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
         * The barcodes contents can be retrieved with
         * {@link android.content.Intent#getStringExtra(String)}. 
         * If the user presses Back, the result code will be {@link android.app.Activity#RESULT_CANCELED}.
         */
        public static final String RESULT = "SCAN_RESULT";
    

    在handleDecodeExternally中注释掉中的相关代码,该方法的功能是如果获取到二维码则会在viewFinderView中绘制获取到的图片bitmap对象。

     private void handleDecodeExternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) {
    
        if (barcode != null) {
           //注释掉
          //绘制结果图片
          //viewfinderView.drawResultBitmap(barcode);
        }
    
        long resultDurationMS;
        if (getIntent() == null) {
          resultDurationMS = DEFAULT_INTENT_RESULT_DURATION_MS;
        } else {
          resultDurationMS = getIntent().getLongExtra(Intents.Scan.RESULT_DISPLAY_DURATION_MS,
                                                      DEFAULT_INTENT_RESULT_DURATION_MS);
        }
    
        if (resultDurationMS > 0) {
          String rawResultString = String.valueOf(rawResult);
          if (rawResultString.length() > 32) {
            rawResultString = rawResultString.substring(0, 32) + " ...";
          }
        }
    
        maybeSetClipboard(resultHandler);
    
        switch (source) {
          case NATIVE_APP_INTENT:
            // Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when
            // the deprecated intent is retired.
            Intent intent = new Intent(getIntent().getAction());
            intent.addFlags(Intents.FLAG_NEW_DOC);
            intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
            intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
            byte[] rawBytes = rawResult.getRawBytes();
            if (rawBytes != null && rawBytes.length > 0) {
              intent.putExtra(Intents.Scan.RESULT_BYTES, rawBytes);
            }
            Map<ResultMetadataType, ?> metadata = rawResult.getResultMetadata();
            if (metadata != null) {
              if (metadata.containsKey(ResultMetadataType.UPC_EAN_EXTENSION)) {
                intent.putExtra(Intents.Scan.RESULT_UPC_EAN_EXTENSION,
                    metadata.get(ResultMetadataType.UPC_EAN_EXTENSION).toString());
              }
              Number orientation = (Number) metadata.get(ResultMetadataType.ORIENTATION);
              if (orientation != null) {
                intent.putExtra(Intents.Scan.RESULT_ORIENTATION, orientation.intValue());
              }
              String ecLevel = (String) metadata.get(ResultMetadataType.ERROR_CORRECTION_LEVEL);
              if (ecLevel != null) {
                intent.putExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL, ecLevel);
              }
              @SuppressWarnings("unchecked")
              Iterable<byte[]> byteSegments = (Iterable<byte[]>) metadata.get(ResultMetadataType.BYTE_SEGMENTS);
              if (byteSegments != null) {
                int i = 0;
                for (byte[] byteSegment : byteSegments) {
                  intent.putExtra(Intents.Scan.RESULT_BYTE_SEGMENTS_PREFIX + i, byteSegment);
                  i++;
                }
              }
            }
            sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS);
            break;
    
          case PRODUCT_SEARCH_LINK:
            // Reformulate the URL which triggered us into a query, so that the request goes to the same
            // TLD as the scan URL.
            int end = sourceUrl.lastIndexOf("/scan");
            String productReplyURL = sourceUrl.substring(0, end) + "?q=" + 
                resultHandler.getDisplayContents() + "&source=zxing";
            sendReplyMessage(R.id.launch_product_query, productReplyURL, resultDurationMS);
            break;
            
          case ZXING_LINK:
            if (scanFromWebPageManager != null && scanFromWebPageManager.isScanFromWebPage()) {
              String linkReplyURL = scanFromWebPageManager.buildReplyURL(rawResult, resultHandler);
              scanFromWebPageManager = null;
              sendReplyMessage(R.id.launch_product_query, linkReplyURL, resultDurationMS);
            }
            break;
        }
      }
    

    MainActivity代码如下:

    package com.zhqy.zxingdemo;
    
    import android.content.Intent;
    import android.support.annotation.Nullable;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.Toast;
    
    import com.google.zxing.client.android.CaptureActivity;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Intent intent=new Intent(this, CaptureActivity.class);
            intent.setAction("com.google.zxing.client.android.SCAN");
            startActivityForResult(intent,0);
        }
    
        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == 0 && resultCode == RESULT_OK) {
                if (data != null) {
                    String content = data.getStringExtra("SCAN_RESULT");
                    Toast.makeText(this,content,Toast.LENGTH_SHORT).show();
                }
            }
    
    
        }
    }
    
    

    最终结果如下:


    扫描二维码.gif

    相关文章

      网友评论

        本文标题:Zxing扫描二维码和识别二维码图片

        本文链接:https://www.haomeiwen.com/subject/yegcxctx.html