作成者別アーカイブ: ryan

カメラのビュークラス 4

3、カメラの設定値を取得
使用しているカメラの情報を取得しておく。
設定を変更したらそれらを設定しなおさなきゃいけないからね。

4、プレビューサイズを選択
さてと、これがいろいろ問題を引き起こすところです。
CameraInformation に、カメラのオブジェクトとプレビューする幅と高さをわたして、カメラがサポートしているプレビューのサイズを取得する。
実装の説明は、CameraInformation の記述で書きます。
今のところ、要件として、サポートされているビューサイズの中から適切なサイズを取得してほしい。
将来、ズームアップしたプレビューなどの要件が追加されても、このメソッドが使用できるように CameraInformation で工夫しないといけないかも・・・

5、プレビューのサイズを設定
選択したプレビューサイズを、設定する。

6、画像サイズを取得
プレビューデータを保存するとき、そのサイズを設定しておかなければならないらしいが、ひとまず、ここで、サポートされている画像サイズの中から指定する画像サイズを取得する。
画像サイズの指定は、ユーザがどれどれにしたいと要望を唱えるはずだから、外部から設定する仕組みを実装しなければならない。
それが、

	/**
	 * 画像サイズの設定値 画像サイズのインデックス 0:最大 値が増えると画像サイズは小さくなる<br>
	 * 画像サイズは機種に依存するので、アプリケーションの設定などから取得、設定される。
	 */
	private int pictureSize = 0;

	/**
	 * 画像サイズを取得
	 * 
	 * @return 画像サイズのインデックス 0:最大 値が増えると画像サイズは小さくなる
	 */
	public int getPictureSize() {
		return pictureSize;
	}

	/**
	 * 画像サイズを設定
	 * 
	 * @param pictureSize
	 *            機種に依存する<br>
	 *            サポートされている画像サイズを知っておく必要がある。サポートされていない画像サイズを設定すると、プレビュー開始時に例外が発生する。<br>
	 */
	public void setPictureSize(int pictureSize) {
		this.pictureSize = pictureSize;
	}

メンバー変数の pictureSize である。

メンバー変数の命名について、少し触れておく。サンプルソースに private int mSize などと、mのSizeですと宣言されているのが良く見かける。
C++で m_ をメンバー変数につけてメンバー変数であることがわかりやすいと、規約されていたことがあったが、もしかして、その m ?
もしそうであれば、私はあえてそうはしない!
だって、メンバー変数かどうか、ソースの可読性を良くする手段とはおもえないから!
メソッド内で宣言している以外の変数であればメンバー変数でしょう。C++ではグローバル変数やマクロ変数などクラスやメソッド内で使用される変数のスコープが曖昧である。そのために、g_ だとか m_ だとか区別していた。
Javaでは必要ないでしょう。もし、必要であるなら、クラス設計そのものがおかしい!

それと、やたら public  変数が多い。
get 、setをコーディングするのが面倒? いやいやツールが自動生成してくれる。
参考書として説明を書いていくとページ内で収まらない? いやいやそんなことするから、使えないSE、プログラマを量産する、仕組みを構築していくのだ!
あくまでも参考書というのは、初心者やそれを学ぼうとしている者が読むものである。
報告書類、論文や研究成果の発表ならばいいけど・・・

なので、私は m には なりたくないから m を つけない。

わき道がたくさんあるので、次へとつづく・・・ To Be Continued…

カメラのビュークラス 3

サーフェイス変更イベントについて

	/*
	 * (non-Javadoc) サーフェイス変更イベントの処理
	 * 
	 * @see android.view.SurfaceHolder.Callback#surfaceChanged(android.view.SurfaceHolder, int, int, int)
	 */
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		CameraInformation info = new CameraInformation();
		Size previewSize = null;
		Size pictureSize = null;
		Log.d(LOG_TAG, "DefaultDisplay Rotation=" + getDefaultDisplayRotation());
		Log.d(LOG_TAG, "format=" + format + " width=" + width + " height=" + height);
		try {
			if (null != camera) {
				// プレビューの停止
				camera.stopPreview();
				// ローテーションを設定
				info.setRotation(getDefaultDisplayRotation());
				// カメラの設定値を取得
				Camera.Parameters params = camera.getParameters();
				// プレビューサイズを選択
				previewSize = info.getPreviewSize(camera, width, height);
				// プレビューのサイズを設定
				params.setPreviewSize(previewSize.width, previewSize.height);
				// 画像サイズを取得
				pictureSize = info.getPictureSize(camera, this.pictureSize);
				// 画像のサイズを設定
				params.setPictureSize(pictureSize.width, pictureSize.height);
				// カメラへ設定値を適用
				camera.setParameters(params);
				// プレビューの開始
				camera.startPreview();

				Log.d(LOG_TAG, "preview width=" + previewSize.width + " height=" + previewSize.height);
				Log.d(LOG_TAG, "picture width=" + pictureSize.width + " height=" + pictureSize.height);
			}
		} catch (Exception e) {
			Log.e(LOG_TAG, "Can not preview. " + e.getMessage());
		}
	}

カメラのプレビュー表示につて、説明をします。
1、プレビューの停止
使用しているカメラを camera.stopPreview() で停止する。

プレビューの停止しなくてもいいけど、したほうが安全である。
なぜか?

例えば、工場の機械を設定してくれよとお客様から要望があったとする。
「機械は止めちゃだめだよ。止めたら損害賠償を請求するからね!」なんて無茶な要件と同じである。
コンベアに巻き込まれて、手や足が挟まれてぐちゃぐちゃになってもいい、動いている機械の内部に入って設定するぞ!
なんて、猛者ならいいですよ。

自分にはできない・・・けど、金銭保障するからお願いと、懇願されたらするかも・・・

2、 ローテーションを設定
スマホやタブレットを立てて使用しているか横向きで使用しているかを設定します。

ほとんどのサイトで、横で使用するのが標準だけど立てて使用する。(呪縛)
標準が横なら横でいいじゃん!
使う人が、横で使おうがねっころがって逆さまで使おうがいいのでは?
回転させたくなかったら、使う人が回転をロック(固定)すればいいだけでしょう?
なので、「横でも縦でも好きにしてくれ!」要件を実装します。

さてと、回転の情報をどこから取得するかというと、ウインドウマネージャに聞くといいらしいので、getDefaultDisplayRotation() を実装します。

	/**
	 * 回転の状態を取得
	 * 
	 * @return Surface.ROTATION_0 or Surface.ROTATION_90 , or Surface.ROTATION_180, or Surface.ROTATION_270.
	 */
	public int getDefaultDisplayRotation() {
		return getWindowManager().getDefaultDisplay().getRotation();
	}

	/**
	 * ウインドウマネージャーを取得
	 * 
	 * @return
	 */
	protected WindowManager getWindowManager() {
		return (WindowManager) this.getContext().getSystemService(Context.WINDOW_SERVICE);
	}

ウインドウマネージャーを呼び出して、デフォルトのディスプレイ情報から回転情報を取得します。
そんじゃ、ウインドウマネジャーはどこから呼び出せばいいかというと、コンテキストからサービスを指定して取得します。
ウインドウマネジャーは、よそものから使われたくないので protected にしておきます。

参考書やよくあるサイトだと

public static void setCameraDisplayOrientation(Activity activity, 
         int cameraId, android.hardware.Camera camera) {
      android.hardware.Camera.CameraInfo info =
              new android.hardware.Camera.CameraInfo();
      android.hardware.Camera.getCameraInfo(cameraId, info);
      int rotation = activity.getWindowManager().getDefaultDisplay() 
             .getRotation();
      int degrees = 0;
      switch (rotation) {
          case Surface.ROTATION_0: degrees = 0; break;
          case Surface.ROTATION_90: degrees = 90; break;
          case Surface.ROTATION_180: degrees = 180; break;
          case Surface.ROTATION_270: degrees = 270; break;
      }
       int result;
      if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
          result = (info.orientation + degrees) % 360;
          result = (360 - result) % 360;  // compensate the mirror
      } else {  // back-facing
          result = (info.orientation - degrees + 360) % 360;
      }
      camera.setDisplayOrientation(result);
}  

「このソースを参考にして回転情報の取得メソッドを実装します。」とか書かれてるけど、なんでやねん!
難しいこと書かんで、いいでしょう!

「コンテキストからサービスを指定して、ウインドウマネージャーを呼び出し、デフォルトのディスプレイ情報から回転情報を取得します。」
これでいいのだ!

長くなりそうなので、次へと続く・・・ To Be Continued…

カメラのビュークラス 2

クラスの記述

	/*
	 * (non-Javadoc) サーフェイス生成イベントの処理
	 * 
	 * @see android.view.SurfaceHolder.Callback#surfaceCreated(android.view.SurfaceHolder)
	 */
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		CameraInformation info = new CameraInformation();
		try {
			// カメラの初期化
			if (null == camera)
				camera = info.openCamera(cameraFacing);
			// カメラにサーフェイスホルダーを設定
			if (null != camera)
				camera.setPreviewDisplay(holder);
		} catch (Exception e) {
			Log.e(LOG_TAG, "Camera not open. " + e.getMessage());
		}
	}

	/*
	 * (non-Javadoc) サーフェイス変更イベントの処理
	 * 
	 * @see android.view.SurfaceHolder.Callback#surfaceChanged(android.view.SurfaceHolder, int, int, int)
	 */
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		CameraInformation info = new CameraInformation();
		Size previewSize = null;
		Size pictureSize = null;
		Log.d(LOG_TAG, "DefaultDisplay Rotation=" + getDefaultDisplayRotation());
		Log.d(LOG_TAG, "format=" + format + " width=" + width + " height=" + height);
		try {
			if (null != camera) {
				// プレビューの停止
				camera.stopPreview();
				// ローテーションを設定
				info.setRotation(getDefaultDisplayRotation());
				// カメラの設定値を取得
				Camera.Parameters params = camera.getParameters();
				// プレビューサイズを選択
				previewSize = info.getPreviewSize(camera, width, height);
				// プレビューのサイズを設定
				params.setPreviewSize(previewSize.width, previewSize.height);
				// 画像サイズを取得
				pictureSize = info.getPictureSize(camera, this.pictureSize);
				// 画像のサイズを設定
				params.setPictureSize(pictureSize.width, pictureSize.height);
				// カメラへ設定値を適用
				camera.setParameters(params);
				// プレビューの開始
				camera.startPreview();

				Log.d(LOG_TAG, "preview width=" + previewSize.width + " height=" + previewSize.height);
				Log.d(LOG_TAG, "picture width=" + pictureSize.width + " height=" + pictureSize.height);
			}
		} catch (Exception e) {
			Log.e(LOG_TAG, "Can not preview. " + e.getMessage());
		}
	}

	/*
	 * (non-Javadoc) サーフェイス解放イベントの処理
	 * 
	 * @see android.view.SurfaceHolder.Callback#surfaceDestroyed(android.view.SurfaceHolder)
	 */
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		if (null != camera) {
			// プレビュー終了
			camera.setPreviewCallback(null);
			camera.stopPreview();
			camera.release();
		}
		camera = null;
	}

今回は、ビューを生成、表示、終了までです。

1、surfaceCreated(SurfaceHolder holder)
カメラを初期化して、カメラにサーフェイスホルダーをぶっこみます。

2、surfaceChanged(SurfaceHolder holder, int format, int width, int height)
ビューが生成されたもしくは変更されたときに、カメラのプレビュー機能を使用してビューに表示します。

3、surfaceDestroyed(SurfaceHolder holder)
ビューが削除されるときに呼び出されるので、カメラを使用していたら解放してあげます。
camera.release() これを忘れるとメモリリークするらしい、検証していないので定かではないが、まぁ画像処理ではメモリを大量に使って高速に処理しなければならないからな。

さて、機種に依存するため、詳細な機能を実装するためにはそれらをラップしていきます。
手始めに、CameraInformation こいつでカメラの設定情報を取得したり変更したりします。
それと外部からの要件を満たすために、cameraFacing と pictureSize を実装します。

プレビューサイズは? と疑問をもつ?
surfaceChanged() で表示するサーフェイスホルダーとフォーマット、幅と高さをわたされるでしょ!
そこで処理するので外部からの要件は満たしています。

前回に、「おちているソースや参考書などが読みにくいと感じた」と書いたのことがなんとなくわかった気がする。
要件と機能が明確に記述されていないからだ!
また、おいおいそれはそのクラスに必要ない機能だろうと思われるのに、そのクラスで実装している。
オブジェクト指向の前に、要件と機能を明確にしろよと、遠いむかしに部下へよく言ったもんだ・・・

カメラのビュークラス

クラスの記述

/**
 * CameraView カメラとサーフェイスホルダーをラップしたビュークラス
 * 
 * @author Ryan's Factory
 * @since 2014/09/10
 */
public class CameraView extends SurfaceView implements SurfaceHolder.Callback, Camera.PictureCallback {
	/**
	 * サーフェイスホルダー
	 */
	private SurfaceHolder holder = null;
	/**
	 * カメラ
	 */
	private Camera camera = null;

	/**
	 * コンストラクタ
	 * 
	 * @param context
	 */
	public CameraView(Context context) {
		super(context);
		// サーフェイスホルダーの生成
		holder = getHolder();
		// 実装したコールバックを設定
		holder.addCallback(this);
		// プッシュバッファの設定
		setSurfaceHolderPushBuffers();
	}

	/**
	 * プッシュバッファの設定
	 * This constant was deprecated in API level 11.
	 * this is ignored, this value is set automatically when needed.
	 */
	@SuppressWarnings("deprecation")
	private void setSurfaceHolderPushBuffers() {
		// OSバージョン判定
		if (Build.VERSION.SDK_INT < 11) {
			holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
		}
	}

コンストラクタを実装するだけで、いくつか問題が出てきた。

ひとつめの問題がこれ holder.setType (SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
OSのバージョンによっては例外が発生するらしい。
どこかのブログに「ワーニングが出るけどどうしたらいいだろう」なんて記述があったけど、@SuppressWarnings(“deprecation”) とすればよいだけだ。
急速にOSバージョンがあがり、しかもいろんな機種が存在しそれらに対応しなければいけないとは、厄介なものに手を出した気がする。

ふたつめは、カメラのオブジェクトをメンバーとして含んでいるが、自分としては気にくわない。
カメラはフロントやリアもしかしたらUSBで外付けのカメラなどが出てくるかもしれない。素のクラスをそのまま使うと、今までの経験上苦労するのはまちがいない。
早い時期に本格的なカメラのクラスを実装したほうが良いだろう。

みっつめがサーフェイスホルダー。
なにこれ?
よくわからん

Andriodの開発を始める

スマホやタブレットで写した写真をツイッターを介在してサイトに投稿するアプリケーションを開発することにしたが、わからないことだらけである。

ただ、落ちてるソースや参考書を見ていると25年ほど前にXWindowのXlibだけで開発していたころを思い出してしまった。

CPUパワーも開発環境もだいぶ進化したのだが、やっていることは同じだ。

参考書やおちているサンプルソースは、なぜ読みにくいのだろう・・・

GAE上でTiwtter4jを利用する OAuth認証(2)

Application Management でコールバックを設定する。

RequestToken から AccessToken を取得するさい、正常ルーチンは問題ないが認証ページでユーザがキャンセルした場合に例外が発生する。

GAEのログを確認したところ、

GET /CallBack?denied=XXXXXXXXXXXXX
java.lang.NullPointerException
	at java.net.URLEncoder.encode(URLEncoder.java:205)

となっていた。
Tiwtter4jのソースには、リクエストパラメータの oauth_tokenoauth_verifier に関する処理しかない。
そのため、そのパラメータが無い場合は、エンコードエラーで例外が発生してしまう。
今まで、SandBoxをラップするAPIを利用してきた中で、NullPointerException を簡単に吐き出すなんてと経験上まれだったので、何かしら前提条件が必要だと思いソースのすみからすみまで目を通すが、denied の記述が無い。
しかたが無いので、以下のように処理をする。

if ( req.getParameter("denied") != null ) {
         // キャンセルされた
     ;
} else {
        // RequestTokenからAccessTokenを取得
        accessToken = twitter.getOAuthAccessToken(requestToken, verifier);
}

はまらないようにするためには、経験は捨てろソースに書かれていることがすべてだ。

GAE上でTiwtter4jを利用する OAuth認証(1)

Twitter REST API v1.1 Resources を直接利用してアプリケーションを開発する予定だったが、やらなければいけないことが多いので、Twitter4jを使って開発速度重視を考えた。
POSTするだけなんで、はまるはずが無いと思っていたのだが、認証までに2日(実質28時間)かかってしまった。まず、ローカル上でテストができないことと、サンプルコードとWeb上におちてるコードでは動かないことだった。
OAuth認証で必要なconsumerKeyとconsumerSecretの配置の問題が起こる。

Web上の公式サイトには、システムパラメータとして引き渡せると記述があったので、
GAEの WEB-INF/appengine-web.xml に配置すればいいと思い下記のように記述する。
<system-properties>
<property name="twitter4j.oauth.consumerKey" value="*******"/>
<property name="twitter4j.oauth.consumerSecret" value="********"/>
</system-properties>

自動的に読み込んでいるかは不明で、例外が発生する。
それで、
String key = System.getProperty("twitter4j.oauth.consumerKey");
String secret = System.getProperty("twitter4j.oauth.consumerSecret");

として、twitter.setOAuthConsumer(key,secret); を追加するが変わらない・・・
試行錯誤の結果、WEB-INF/appengine-web.xmlを変更すると、あらま処理できた。

以下が正常に処理できたソース
WEB-INF/appengine-web.xml
<system-properties>
<property name="consumerKey" value="*******"/>
<property name="consumerSecret" value="********"/>
</system-properties>


String key = System.getProperty("consumerKey");
String secret = System.getProperty("consumerSecret");
Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(key,secret);
RequestToken reqToken = twitter.getOAuthRequestToken();
String strUrl = reqToken.getAuthorizationURL();
response.sendRedirect(strUrl);

はまらないために、システムパラメータのKey名は別ものとする