今日はAndroidをさわってました。
(毎日何をやってるのかだんだんわからなくなってきた。。。)
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