Android QR code scanning (imitating wechat interface), according to Google zxing

Android QR code scanning (imitating wechat interface), according to Google zxing

QR code scanning is often used in Android project development. For example, Google has an open source library (address: https://github.com/zxing/zxing ), there are still a lot of contents in it. If you want to learn more about it, it's good. Of course, we have to choose according to the actual needs, so we can choose the core Jar package in the class library (address: https://github.com/ASCN-BJ/ZhaoYun/blob/master/qrcodelibrary/libs/core-3.3.1.jar).
One of the problems with the original class library in Google zxing is that it can't be recognized when the screen is vertical. The reason is that the preview effect of the camera is not processed when the screen is vertical. Therefore, in the project, it is processed to make the recognition successful when the screen is vertical. So just look at it, and it's time to study.
design sketch



-Scan of QR code
The 2D code identification interface in zxing is mainly in CaptureActivity. The basic principle is to obtain the preview interface of the camera and then process the acquired data. The preview effect is to obtain the data in PreviewCallback and then send the data through DecodeHandler. The specific identification is processed in the DecodeHandler class.

      @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        Point cameraResolution = configManager.getCameraResolution();
        Handler thePreviewHandler = previewHandler;
//        if (cameraResolution != null && thePreviewHandler != null) {
//            Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
//                    cameraResolution.y, data);
//            message.sendToTarget();
//            previewHandler = null;
//        } else {
//            Log.d(TAG, "Got preview callback, but no handler or resolution available");
//        }
//        Log.d(TAG, "onPreviewFrame: " + "onRunning");
        if (cameraResolution != null && thePreviewHandler != null) {
            //add by tancolo
            Point screenResolution = configManager.getScreenResolution();
            Message message;
            if (screenResolution.x < screenResolution.y) {
                // portrait
                message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.y,
                        cameraResolution.x, data);
            } else {
                // landscape
                message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
                        cameraResolution.y, data);
            }
            message.sendToTarget();
            previewHandler = null;
        }
    }

You can see that the specific byte[] data data is sent out in the onPreviewFrame, and the width and height of the preview effect are sent out for subsequent processing.
Next, let's look at the specific QR code identification code

  @Override
    public void handleMessage(Message message) {
        if (message == null || !running) {
            return;
        }
        if (message.what == R.id.decode) {
            decode((byte[]) message.obj, message.arg1, message.arg2);

        } else if (message.what == R.id.quit) {
            running = false;
            Looper.myLooper().quit();

        }
    }
 private void decode(byte[] data, int width, int height) {
        long start = System.currentTimeMillis();
        //add by tancolo
        if (width < height) {
            // portrait
            byte[] rotatedData = new byte[data.length];
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++)
                    rotatedData[y * width + width - x - 1] = data[y + x * height];
            }
            data = rotatedData;
        }
        //end add


        Result rawResult = null;
        PlanarYUVLuminanceSource source =     activity.getCameraManager().buildLuminanceSource(data, width, height);
        if (source != null) {
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
            try {
                rawResult = multiFormatReader.decodeWithState(bitmap);//Specific identification method and data acquisition
            } catch (ReaderException re) {
                // continue
            } finally {
                multiFormatReader.reset();
            }
        }

        Handler handler = activity.getHandler();
        if (rawResult != null) {
            // Don't log the barcode contents for security.
            long end = System.currentTimeMillis();
            Log.d(TAG, "Found barcode in " + (end - start) + " ms");
            if (handler != null) {
                Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);
                Bundle bundle = new Bundle();
                bundleThumbnail(source, bundle);
                message.setData(bundle);
                message.sendToTarget();
            }
        } else {
            if (handler != null) {
                Message message = Message.obtain(handler, R.id.decode_failed);
                message.sendToTarget();
            }
        }
    }
  • Generation of QR code
Write code here
  try {
mQRCodeEncoder2 = new QRCodeEncoder2(smallerDimension);
 //Generate QR code
Bitmap bitmap = mQRCodeEncoder2.encodeAsBitmap(text,    BarcodeFormat.QR_CODE);
 if (bitmap == null) {
 qrCodeEncoder = null;
 return true;
}
  iv_image_view.setImageBitmap(bitmap);
  } catch (WriterException e) {
   qrCodeEncoder = null;
 }

Specific identification

    public Bitmap encodeAsBitmap(String contentsToEncode, BarcodeFormat format) throws WriterException {
        if (contentsToEncode == null) {
            return null;
        }
        Map<EncodeHintType, Object> hints = null;
        String encoding = guessAppropriateEncoding(contentsToEncode);
        if (encoding != null) {
            hints = new EnumMap<>(EncodeHintType.class);
            hints.put(EncodeHintType.CHARACTER_SET, encoding);
        }
        BitMatrix result;
        try {
            format = BarcodeFormat.valueOf(format.toString());
            result = new MultiFormatWriter().encode(contentsToEncode, format, dimension, dimension, hints);
        } catch (IllegalArgumentException iae) {
            // Unsupported format
            return null;
        }
        int width = result.getWidth();
        int height = result.getHeight();
        int[] pixels = new int[width * height];
        for (int y = 0; y < height; y++) {
            int offset = y * width;
            for (int x = 0; x < width; x++) {
                pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;//Generate QR code data
            }
        }
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
        return bitmap;
    }
  • Recognition of local pictures
public static String getResult(Bitmap bitmap) {
        //Zxing's own analytic class
        byte data[];
        int[] datas = new int[bitmap.getWidth() * bitmap.getHeight()];
        data = new byte[bitmap.getWidth() * bitmap.getHeight()];
        bitmap.getPixels(datas, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
        for (int i = 0; i < datas.length; i++) {
            data[i] = (byte) datas[i];
        }
        Set<BarcodeFormat> decodeFormats = new HashSet<>();
        decodeFormats.addAll(DecodeFormatManager.PRODUCT_FORMATS);
        decodeFormats.addAll(DecodeFormatManager.INDUSTRIAL_FORMATS);
        decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
        decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
        decodeFormats.addAll(DecodeFormatManager.AZTEC_FORMATS);
        decodeFormats.addAll(DecodeFormatManager.PDF417_FORMATS);
        Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
        hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
        MultiFormatReader multiFormatReader = new MultiFormatReader();
        multiFormatReader.setHints(hints);
        Result rawResult = null;
        PlanarYUVLuminanceSource source = buildLuminanceSource(data, bitmap.getWidth(), bitmap.getHeight());
        if (source != null) {
            BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
            try {
                rawResult = multiFormatReader.decodeWithState(bitmap1);
            } catch (ReaderException re) {
                // continue
            } finally {
                multiFormatReader.reset();
            }
        }

        return rawResult != null ? rawResult.getText() : "";
    }
  • Gesture add
    We can find gesture recognition in wechat, mainly including double-click effect, multi touch effect, flashlight (display flashlight icon according to sensor)
public final class ViewfinderView extends View implements ScaleGestureDetector.OnScaleGestureListener,
        View.OnTouchListener{

    private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
    private static final long ANIMATION_DELAY = 80L;
    private static final int CURRENT_POINT_OPACITY = 0xA0;
    private static final int MAX_RESULT_POINTS = 20;
    private static final int POINT_SIZE = 6;

    private CameraManager cameraManager;
    private final Paint paint;
    private Bitmap resultBitmap;
    private final int maskColor;
    private final int resultColor;
    private final int laserColor;
    private final int resultPointColor;
    private int scannerAlpha;
    private List<ResultPoint> possibleResultPoints;
    private List<ResultPoint> lastPossibleResultPoints;
    private Rect tmpRect;
    private Bitmap tmpBitmap;
    private float cornerWidth;//Square width
    private float cornerLength;//Square length
    private int mStrokeWidth;//Inner ring width
    private int corner_rect_length;//inside color 
    private int cornerColor;//Angle color
    private String description;//Description text
    private int textColor;
    private boolean textVisible;
    private float textMargin;//Describe the distance above the text
    private float text_size;//Description text font size
    private ScaleGestureDetector scaleGestureDetector;
    private GestureDetector gestureDetector;
    private boolean zoomMaxFlag = true;

    // This constructor is used when the class is built from an XML resource.
    public ViewfinderView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // Initialize these once for performance rather than calling them every time in onDraw().
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Resources resources = getResources();
        resultColor = resources.getColor(R.color.result_view);
        laserColor = resources.getColor(R.color.viewfinder_laser);
        resultPointColor = resources.getColor(R.color.possible_result_points);
        scannerAlpha = 0;
        possibleResultPoints = new ArrayList<>(5);
        lastPossibleResultPoints = null;
        tmpRect = new Rect();
        tmpBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.scan);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewfinderView);
        cornerWidth = a.getDimensionPixelSize(R.styleable.ViewfinderView_corner_width, dip2px(context, 4));
        cornerLength = a.getDimensionPixelSize(R.styleable.ViewfinderView_corner_length, dip2px(context, 20));
        mStrokeWidth = a.getDimensionPixelSize(R.styleable.ViewfinderView_corner_rect_length, dip2px(context, 1));
        cornerColor = a.getColor(R.styleable.ViewfinderView_corner_color, Color.parseColor("#62e203"));
        corner_rect_length = a.getColor(R.styleable.ViewfinderView_corner_rect_color, Color.parseColor("#66ffffff"));
        maskColor = a.getColor(R.styleable.ViewfinderView_mask_color, resources.getColor(R.color.viewfinder_mask));
        description = a.getString(R.styleable.ViewfinderView_text_description);
        if (TextUtils.isEmpty(description)) {
            description = "Put QR code/Barcode in the box,Can scan automatically";
        }
        textColor = a.getColor(R.styleable.ViewfinderView_text_color, Color.parseColor("#66ffffff"));
        textVisible = a.getBoolean(R.styleable.ViewfinderView_text_visible, true);
        textMargin = a.getDimensionPixelSize(R.styleable.ViewfinderView_text_margin, dip2px(context, 20));
        text_size = a.getDimensionPixelSize(R.styleable.ViewfinderView_text_size, sp2px(context, 14));
        a.recycle();
        setOnTouchListener(this);
        scaleGestureDetector = new ScaleGestureDetector(context, this);
        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDoubleTap(MotionEvent e) {
                if (zoomMaxFlag) {
                    if (mCameraZoomListener != null) {
                        mCameraZoomListener.onZooming(true, false, true, 1);
                    }
                    zoomMaxFlag = false;
                } else {
                    if (mCameraZoomListener != null) {
                        mCameraZoomListener.onZooming(true, false, false, 1);
                    }
                    zoomMaxFlag = true;
                }
                return true;
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
//                if (mCameraZoomListener != null) {
//                    //Determine whether the click event is in
//                    mCameraZoomListener.onZooming(false, true, false, 1);
//                }
                return false;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                super.onLongPress(e);
//                if (mCameraZoomListener != null) {
//                    //Determine whether the click event is in
//                    mCameraZoomListener.onZooming(false, true, false, 1);
//                }
            }
        });
    }

    public void setCameraManager(CameraManager cameraManager) {
        this.cameraManager = cameraManager;
    }

    @SuppressLint("DrawAllocation")
    @Override
    public void onDraw(Canvas canvas) {
        if (cameraManager == null) {
            return; // not ready yet, early draw before done configuring
        }
        Rect frame = cameraManager.getFramingRect();
        Rect previewFrame = cameraManager.getFramingRectInPreview();
        if (frame == null || previewFrame == null) {
            return;
        }
        int width = canvas.getWidth();
        int height = canvas.getHeight();

        // Draw the exterior (i.e. outside the framing rect) darkened
        paint.setColor(resultBitmap != null ? resultColor : maskColor);
        canvas.drawRect(0, 0, width, frame.top, paint);
        canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
        canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
        canvas.drawRect(0, frame.bottom + 1, width, height, paint);
        if (resultBitmap != null) {
            // Draw the opaque result bitmap over the scanning rectangle
            paint.setAlpha(CURRENT_POINT_OPACITY);
            canvas.drawBitmap(resultBitmap, null, frame, paint);
        } else {

            // Draw a red "laser scanner" line through the middle to show decoding is active
            paint.setColor(laserColor);
//            paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
            scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
//            int middle = frame.height() / 2 + frame.top;
//            canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint); / / remove the red line

            float scaleX = frame.width() / (float) previewFrame.width();
            float scaleY = frame.height() / (float) previewFrame.height();

            List<ResultPoint> currentPossible = possibleResultPoints;
            List<ResultPoint> currentLast = lastPossibleResultPoints;
            int frameLeft = frame.left;
            int frameTop = frame.top;
            if (currentPossible.isEmpty()) {
                lastPossibleResultPoints = null;
            } else {
                possibleResultPoints = new ArrayList<>(5);
                lastPossibleResultPoints = currentPossible;
                paint.setAlpha(CURRENT_POINT_OPACITY);
                paint.setColor(resultPointColor);
                synchronized (currentPossible) {
                    for (ResultPoint point : currentPossible) {
                        canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                                frameTop + (int) (point.getY() * scaleY),
                                POINT_SIZE, paint);
                    }
                }
            }
            if (currentLast != null) {
                paint.setAlpha(CURRENT_POINT_OPACITY / 2);
                paint.setColor(resultPointColor);
                synchronized (currentLast) {
                    float radius = POINT_SIZE / 2.0f;
                    for (ResultPoint point : currentLast) {
                        canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                                frameTop + (int) (point.getY() * scaleY),
                                radius, paint);
                    }
                }
            }
            drawRectCorner(frame, canvas);
            drawBitmap(frame, canvas);
            drawBottomText(frame, canvas);


            // Request another update at the animation interval, but only repaint the laser line,
            // not the entire viewfinder mask.
//            postInvalidateDelayed(ANIMATION_DELAY,
//                    frame.left - POINT_SIZE,
//                    frame.top - POINT_SIZE,
//                    frame.right + POINT_SIZE,
//                    frame.bottom + POINT_SIZE);
        }
    }

    public void drawViewfinder() {
        Bitmap resultBitmap = this.resultBitmap;
        this.resultBitmap = null;
        if (resultBitmap != null) {
            resultBitmap.recycle();
        }
        invalidate();
    }

    /**
     * Draw a bitmap with the result points highlighted instead of the live scanning display.
     *
     * @param barcode An image of the decoded barcode.
     */
    public void drawResultBitmap(Bitmap barcode) {
        resultBitmap = barcode;
        invalidate();
    }

    public void addPossibleResultPoint(ResultPoint point) {
        List<ResultPoint> points = possibleResultPoints;
        synchronized (points) {
            points.add(point);
            int size = points.size();
            if (size > MAX_RESULT_POINTS) {
                // trim it
                points.subList(0, size - MAX_RESULT_POINTS / 2).clear();
            }
        }
    }

    private ValueAnimator animator;
    private float x;
    private boolean flag = true;

    /*Draw four corners. If you draw path, you will get stuck*/
    private void drawRectCorner(Rect frame, Canvas canvas) {
        //Draw internal rect
        paint.setStrokeWidth(mStrokeWidth);
        paint.setColor(corner_rect_length);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawRect(frame, paint);
        paint.reset();
        //Draw four corners
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(cornerColor);
        //top left corner
        canvas.drawRect(frame.left, frame.top, frame.left + cornerLength, frame.top + cornerWidth, paint);
        canvas.drawRect(frame.left, frame.top, frame.left + cornerWidth, frame.top + cornerLength, paint);
        //Upper right corner
        canvas.drawRect(frame.right - cornerLength, frame.top, frame.right, frame.top + cornerWidth, paint);
        canvas.drawRect(frame.right - cornerWidth, frame.top, frame.right, frame.top + cornerLength, paint);
        //lower left quarter
        canvas.drawRect(frame.left, frame.bottom - cornerLength, frame.left + cornerWidth, frame.bottom, paint);
        canvas.drawRect(frame.left, frame.bottom - cornerWidth, frame.left + cornerLength, frame.bottom, paint);
        //Lower right corner
        canvas.drawRect(frame.right - cornerLength, frame.bottom - cornerWidth, frame.right, frame.bottom, paint);
        canvas.drawRect(frame.right - cornerWidth, frame.bottom - cornerLength, frame.right, frame.bottom, paint);
        paint.reset();
    }

    /*Draw scan line*/
    private void drawBitmap(final Rect frame, Canvas canvas) {
        if (animator == null) {
            animator = ValueAnimator.ofFloat(frame.top, frame.bottom);
            animator.setDuration(3000);
            animator.setRepeatCount(ValueAnimator.INFINITE);
            animator.setRepeatMode(ValueAnimator.RESTART);
            animator.setInterpolator(new LinearInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    x = (Float) animation.getAnimatedValue();
                    postInvalidate(
                            frame.left - POINT_SIZE,
                            frame.top - POINT_SIZE,
                            frame.right + POINT_SIZE,
                            frame.bottom + POINT_SIZE);
                }
            });
        }

        if (flag) {
            animator.start();
            flag = false;
        }
        tmpRect.set(frame.left, (int) x, frame.right, (int) x + 40);
        canvas.drawBitmap(tmpBitmap, null, tmpRect, paint);
    }

    private Rect tmpRect1 = new Rect();

    private void drawBottomText(Rect frame, Canvas canvas) {
        if (textVisible) {
            paint.setTextSize(text_size);
            paint.setColor(textColor);
            paint.setTextAlign(Paint.Align.CENTER);
            paint.getTextBounds(description, 0, description.length(), tmpRect1);
            canvas.drawText(description, 0, description.length(), frame.centerX(),
                    frame.centerY() + frame.height() / 2 + textMargin + tmpRect1.height(), paint);
            paint.reset();
        }
    }

    /**
     * Convert the dip or dp value to px value to ensure the size remains the same
     */
    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    /**
     * Convert sp value to px value to keep the text size unchanged
     */
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }


    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (mCameraZoomListener != null) {
            mCameraZoomListener.onZooming(false, false, false, detector.getScaleFactor());
        }
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {

    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        scaleGestureDetector.onTouchEvent(event);
        gestureDetector.onTouchEvent(event);
        return true;
    }

    public interface CameraZoomListener {
        /**
         * @param isDouble   Double click
         * @param isMax      //Move to max or not
         * @param scaleValue //Zoom out
         * @param isSingle   //Click or not
         */
        void onZooming(boolean isDouble, boolean isSingle, boolean isMax, float scaleValue);
    }

    private CameraZoomListener mCameraZoomListener;

    public void setCameraZoomListener(CameraZoomListener mCameraZoomListener) {
        this.mCameraZoomListener = mCameraZoomListener;
    }
}

If the production environment, the interface can be modified according to the specific product
Project address: https://github.com/ASCN-BJ/ZXingLibrary

Tags: github Google encoding Android

Posted on Sat, 09 May 2020 11:47:55 -0400 by zampu