(毎日何をやってるのかだんだんわからなくなってきた。。。)
AndroidでQRコードを使う場合、だいたいがZXing(ゼブラクロッシング)を利用してアプリを構築しているみたいです。
今日はカメラ上からQRコードを読み込んで読み込んだ内容を表示するという単純なものでしたがなかなかすんなりいかず苦労したんでメモ程度に。
とりあえずライブラリをダウンロードします。(現在の最新はZXing-2.0.zip)
http://code.google.com/p/zxing/downloads/list
ダウンロードしたファイルを解凍するといろいろ入ってますがアプリ作るのに必要なのは以下
- core/core.jar
- javase/javase.jar
- android/src/com/google/zxing/client/android/PlanarYUVLuminanceSource.java
まずAndroidのプロジェクトを作成。(ここではZXingTestとしました)
続けて「libs」フォルダと「com.google.zxing.client.android」パッケージを作成します。
libsには「core.jar」と「javase.jar」、パッケージには「PlanarYUVLuminanceSource.java」をコピーします。
libsはこのままでは使えないのでそれぞれ右クリックしてビルド・パスに追加をしておきます。
追加後のツリーはこんなかんじです。
次にメインの画面です。(res/layout/main.xml)
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <SurfaceView android:id="@+id/preview_view" android:layout_width="fill_parent" android:layout_height="fill_parent" /> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" > <View android:id="@+id/center_view" android:layout_width="300dip" android:layout_height="180dip" android:layout_centerVertical="true" android:layout_centerHorizontal="true" android:background="#55ff6666" /> </RelativeLayout> </FrameLayout>
SurfaceViewはカメラを使うんでお決まりですかね。
Viewにしてる部分はQRコードを読み込むエリアを描画しています。
次にカメラを使うのでマニフェストファイルを修正します。(AndroidManifest.xml)
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="jp.kuseful.zxingtest" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" /> <uses-permission android:name="android.permission.CAMERA" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".ZXingTestActivity" android:label="@string/app_name" android:screenOrientation="landscape" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
パーミッションの追加とあとアプリを横向きとするためscreenOrientationをlandscapeに指定しています。
次にメインのソース部分です。(ZXingTextActivity.java)
package jp.kuseful.zxingtest; import java.io.IOException; import com.google.zxing.BinaryBitmap; import com.google.zxing.MultiFormatReader; import com.google.zxing.Result; import com.google.zxing.client.android.PlanarYUVLuminanceSource; import com.google.zxing.common.HybridBinarizer; import android.app.Activity; import android.content.Context; import android.graphics.Point; import android.hardware.Camera; import android.os.Bundle; import android.util.Log; import android.view.Display; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Toast; public class ZXingTestActivity extends Activity implements SurfaceHolder.Callback, Camera.PreviewCallback, Camera.AutoFocusCallback { private static final String TAG = "ZXingTest"; private static final int MIN_PREVIEW_PIXCELS = 320 * 240; private static final int MAX_PREVIEW_PIXCELS = 800 * 480; private Camera myCamera; private SurfaceView surfaceView; private Boolean hasSurface; private Boolean initialized; private Point screenPoint; private Point previewPoint; /** lifecycle */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); hasSurface = false; initialized = false; setContentView(R.layout.main); } @Override protected void onResume() { super.onResume(); surfaceView = (SurfaceView)findViewById(R.id.preview_view); SurfaceHolder holder = surfaceView.getHolder(); if (hasSurface) { initCamera(holder); } else { holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } } @Override protected void onPause() { closeCamera(); if (!hasSurface) { SurfaceHolder holder = surfaceView.getHolder(); holder.removeCallback(this); } super.onPause(); } /** SurfaceHolder.Callback */ @Override public void surfaceCreated(SurfaceHolder holder) { if (!hasSurface) { hasSurface = true; initCamera(holder); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { hasSurface = false; } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } /** Camera.AutoFocusCallback */ @Override public void onAutoFocus(boolean success, Camera camera) { if (success) camera.setOneShotPreviewCallback(this); } /** devices */ @Override public boolean onTouchEvent(MotionEvent event) { if (myCamera != null) { if (event.getAction() == MotionEvent.ACTION_DOWN) { Camera.Parameters parameters = myCamera.getParameters(); if (!parameters.getFocusMode().equals(Camera.Parameters.FOCUS_MODE_FIXED)) { myCamera.autoFocus(this); } } } return true; } /** Camera.PreviewCallback */ @Override public void onPreviewFrame(byte[] data, Camera camera) { View centerView = (View)findViewById(R.id.center_view); int left = centerView.getLeft() * previewPoint.x / screenPoint.x; int top = centerView.getTop() * previewPoint.y / screenPoint.y; int width = centerView.getWidth() * previewPoint.x / screenPoint.x; int height = centerView.getHeight() * previewPoint.y / screenPoint.y; PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource( data, previewPoint.x, previewPoint.y, left, top, width, height, false); BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); MultiFormatReader reader = new MultiFormatReader(); try { Result result = reader.decode(bitmap); Toast.makeText(this, result.getText(), Toast.LENGTH_LONG).show(); } catch (Exception e) { Toast.makeText(this, "error: " + e.getMessage(), Toast.LENGTH_LONG).show(); } } /** * カメラ情報を初期化 * @param holder */ private void initCamera(SurfaceHolder holder) { try { openCamera(holder); } catch (Exception e) { Log.w(TAG, e); } } private void openCamera(SurfaceHolder holder) throws IOException { if (myCamera == null) { myCamera = Camera.open(); if (myCamera == null) { throw new IOException(); } } myCamera.setPreviewDisplay(holder); if (!initialized) { initialized = true; initFromCameraParameters(myCamera); } setCameraParameters(myCamera); myCamera.startPreview(); } /** * カメラ情報を破棄 */ private void closeCamera() { if (myCamera != null) { myCamera.stopPreview(); myCamera.release(); myCamera = null; } } /** * カメラ情報を設定 * @param camera */ private void setCameraParameters(Camera camera) { Camera.Parameters parameters = camera.getParameters(); parameters.setPreviewSize(previewPoint.x, previewPoint.y); camera.setParameters(parameters); } /** * カメラのプレビューサイズ・画面サイズを設定 * @param camera */ private void initFromCameraParameters(Camera camera) { Camera.Parameters parameters = camera.getParameters(); WindowManager manager = (WindowManager)getApplication().getSystemService(Context.WINDOW_SERVICE); Display display = manager.getDefaultDisplay(); int width = display.getWidth(); int height = display.getHeight(); if (width < height) { int tmp = width; width = height; height = tmp; } screenPoint = new Point(width, height); Log.d(TAG, "screenPoint = " + screenPoint); previewPoint = findPreviewPoint(parameters, screenPoint, false); Log.d(TAG, "previewPoint = " + previewPoint); } /** * 最適なプレビューサイズを設定 * @param parameters * @param screenPoint * @param portrait * @return */ private Point findPreviewPoint(Camera.Parameters parameters, Point screenPoint, boolean portrait) { Point previewPoint = null; int diff = Integer.MAX_VALUE; for (Camera.Size supportPreviewSize : parameters.getSupportedPreviewSizes()) { int pixels = supportPreviewSize.width * supportPreviewSize.height; if (pixels < MIN_PREVIEW_PIXCELS || pixels > MAX_PREVIEW_PIXCELS) { continue; } int supportedWidth = portrait ? supportPreviewSize.height : supportPreviewSize.width; int supportedHeight = portrait ? supportPreviewSize.width : supportPreviewSize.height; int newDiff = Math.abs(screenPoint.x * supportedHeight - supportedWidth * screenPoint.y); if (newDiff == 0) { previewPoint = new Point(supportedWidth, supportedHeight); break; } if (newDiff < diff) { previewPoint = new Point(supportedWidth, supportedHeight); diff = newDiff; } } if (previewPoint == null) { Camera.Size defaultPreviewSize = parameters.getPreviewSize(); previewPoint = new Point(defaultPreviewSize.width, defaultPreviewSize.height); } return previewPoint; } }
コメント書いてないからわかりづらいですね。すみません。。。
動作としては起動後にカメラプレビューを表示し、画面タッチをした際にオートフォーカス実装後、QRコードの読み込みを開始する。
といったかんじです。
肝となるのはPlanarYUVLuminanceSource。
引数は順に、byte配列の画像データ、プレビューサイズの横幅、プレビューサイズの高さ、読み取りエリアの開始X座標、読み取りエリアの開始Y座標、読み取りエリアの横幅、読み取りエリアの高さ、反転の有無です。
正常に読み込めた場合にはこんなかんじで表示されます。
iOSのほうも試してみました。
http://teru2-bo2.blogspot.jp/2012/07/iosqrzxing-20.html
こんにちは てるてる坊主さんのブログを拝見さてもらい
返信削除QRコードリーダーを作ってみました掲載されていたように
やってみたらうまく動作しました。それで今は横向きなので
縦向き固定のリーダー機を作っているのですが、なかなか
うまくいきません できるだけここのブログにあるファイルを
元に作りたいのですがご存知のことがありましたら教えてください
よろしくお願いします