マサルの備忘録

ハイオク仕様のトルクフルな備忘録

AndroidでQRコードを読むアプリをつくった

スポンサーリンク

前回まで。


AndroidでQRコードを読むアプリをつくろうと思ったんだけど、まずはカメラのプレビューを表示できた - マサルの備忘録

QRコードを読み書きするには外部ライブラリが必要

言うまでもないですが。
今回はZXing(ゼブラクロッシング)を使います。

ZXing


GitHub - zxing/zxing: Official ZXing ("Zebra Crossing") project home

元はGoogleが開発していたようです。
今ではGithub上にソースが上がってます。

そしてアパッチライセンスで使用可能。ありがたや。
License Questions · zxing/zxing Wiki · GitHub

Apache License v2.0

This project is licensed under the Apache License v2.0. It is not a copyleft license like the GPL; it is relatively generous about what you can do with the code.

Can This Be Used In Commercial Products?

The license does not forbid this. The license terms still apply, of course.

使い方

f:id:domomasarudesu:20150228163232p:plain
まぁ、ほかのサイトを見てもらえればいいです。
本当はソースからMavenでビルドしてhogehogeって感じですが、面倒なのでMavenリポジトリーからJarファイルをダンロード。使うのはcore.jarとjavase.jarのみ。これをAndroidプロジェクトのlibフォルダにつっこんで終了。

やっとできた

このQRを読んでみます。

f:id:domomasarudesu:20150228163439g:plain

で、自作アプリで読む様子はこちら。

f:id:domomasarudesu:20150228163531p:plain

プレビューをタッチ⇒AutoFocus⇒QR読み込みの流れ。
正常に読めると画面下部に読めた情報が表示されます。

意外と読めない

画面をタッチすると1回だけQRを読みに行きます。
ただ、一回読むだけでは失敗することがあり・・・。
再帰的に読みに行かなきゃダメっぽいです。
ただ、合焦に若干時間がかかるのがアレですね。

クソコードはこちら

Activity拡張クラスから、実際の処理のクラスを分けました。

package com.masaru.qrcodereader;
import android.app.Activity;
import android.os.Bundle;
import android.view.SurfaceView;

public class MainActivity extends Activity {

	private SurfaceView mSurfaceView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	public void onResume() {
		super.onResume();
		mSurfaceView = (SurfaceView) findViewById(R.id.surface_view);
		mSurfaceView.getHolder().addCallback(SurfaceViewCallback.create(this));
	}
}

適当です。

本体はこちら。

package com.masaru.qrcodereader;

import java.io.IOException;
import java.util.List;

import android.app.Activity;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.google.zxing.BinaryBitmap;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Reader;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;

public class SurfaceViewCallback {

	public static SurfaceHolder.Callback create(final Activity activity) {

		SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {

			private Camera mCamera;

			private RelativeLayout mRelativeLayout;

			private SurfaceView mSurfaceView;

			private TextView mTextView;

			@Override
			public void surfaceDestroyed(SurfaceHolder holder) {
				mCamera.cancelAutoFocus();
				mCamera.release();
				mCamera = null;
			}

			@Override
			public void surfaceCreated(SurfaceHolder holder) {
				mTextView = (TextView) (activity
						.findViewById(R.id.result_textview));
				mSurfaceView = (SurfaceView) (activity
						.findViewById(R.id.surface_view));
				mRelativeLayout = (RelativeLayout) (activity
						.findViewById(R.id.relative_layout));

				mCamera = Camera.open();

				setParameters();
				setDisplayOrientation();
				setSurfaceViewSize();

				try {
					mCamera.setPreviewDisplay(holder);
				} catch (IOException e) {
					e.printStackTrace();
				}

				mSurfaceView.setOnClickListener(mOnClickListener);
			}

			@Override
			public void surfaceChanged(SurfaceHolder holder, int format,
					int width, int height) {

				mCamera.startPreview();
			}

			private void setParameters() {
				Camera.Parameters parameters = mCamera.getParameters();
				List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
				Camera.Size size = sizes.get(0);
				parameters.setPreviewSize(size.width, size.height);
				mCamera.setParameters(parameters);
				mTextView.setText(size.width + " x " + size.height);
			}

			private void setDisplayOrientation() {
				int degree;

				switch (activity.getWindowManager().getDefaultDisplay()
						.getRotation()) {
				case Surface.ROTATION_0:
					degree = 0;
					break;
				case Surface.ROTATION_90:
					degree = 90;
					break;
				case Surface.ROTATION_180:
					degree = 180;
					break;
				case Surface.ROTATION_270:
					degree = 270;
					break;
				default:
					degree = 0;
				}

				CameraInfo info = new CameraInfo();
				Camera.getCameraInfo(0, info);
				mCamera.setDisplayOrientation((info.orientation - degree + 360) % 360);
			}

			private void setSurfaceViewSize() {
				float relativeWidth = mRelativeLayout.getWidth();
				float relativeHeight = mRelativeLayout.getHeight();
				float previewWidth;
				float previewHeight;

				Size size = mCamera.getParameters().getSupportedPreviewSizes()
						.get(0);
				if (relativeHeight > relativeWidth) {
					previewHeight = size.width;
					previewWidth = size.height;
				} else {
					previewHeight = size.height;
					previewWidth = size.width;
				}

				int surfaceWidth;
				int surfaceHeight;
				if (previewHeight / relativeHeight > previewWidth
						/ relativeWidth) {
					surfaceWidth = (int) (previewWidth * relativeHeight / previewHeight);
					surfaceHeight = (int) (relativeHeight);
				} else {
					surfaceWidth = (int) (relativeWidth);
					surfaceHeight = (int) (previewHeight * relativeWidth / previewWidth);
				}

				RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
						surfaceWidth, surfaceHeight);
				params.addRule(RelativeLayout.CENTER_IN_PARENT);
				mSurfaceView.setLayoutParams(params);
			}

			View.OnClickListener mOnClickListener = new OnClickListener() {

				@Override
				public void onClick(View v) {
					mCamera.autoFocus(new AutoFocusCallback() {

						@Override
						public void onAutoFocus(boolean success, Camera camera) {
							if (success) {
								camera.setOneShotPreviewCallback(mPreviewCallback);
							}
						}
					});
				}
			};

			Camera.PreviewCallback mPreviewCallback = new PreviewCallback() {

				@Override
				public void onPreviewFrame(byte[] data, Camera camera) {
					TextView mTextView = (TextView) activity
							.findViewById(R.id.result_textview);
					int previewWidth = camera.getParameters().getPreviewSize().width;
					int previewHeight = camera.getParameters().getPreviewSize().height;

					PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(
							data, previewWidth, previewHeight, 0, 0,
							previewWidth, previewHeight, false);
					BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(
							source));

					Reader reader = new MultiFormatReader();
					Result result = null;
					try {
						result = reader.decode(bitmap);
						String text = result.getText();
						mTextView.setText(text);
					} catch (Exception e) {
						mTextView.setText("reading...");
					}
				}
			};
		};

		return callback;
	}
}

消費電力的に問題がなければ、再帰的にReadするように修正しようかしら。