<?xml version="1.0" encoding="UTF-8" ?><rdf:RDF 
  xmlns="http://purl.org/rss/1.0/"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xml:lang="ja">
  <channel rdf:about="http://w.atwiki.jp/tdnki/">
    <title>tdnki @ ウィキ</title>
    <link>http://w.atwiki.jp/tdnki/</link>
    <atom:link href="https://w.atwiki.jp/tdnki/rss10.xml" rel="self" type="application/rss+xml" />
    <atom:link rel="hub" href="https://pubsubhubbub.appspot.com" />
    <description>tdnki @ ウィキ</description>

    <dc:language>ja</dc:language>
    <dc:date>2014-02-05T11:20:39+09:00</dc:date>
    <utime>1391566839</utime>

    <items>
      <rdf:Seq>
                <rdf:li rdf:resource="https://w.atwiki.jp/tdnki/pages/51.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/tdnki/pages/2.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/tdnki/pages/49.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/tdnki/pages/40.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/tdnki/pages/48.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/tdnki/pages/47.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/tdnki/pages/16.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/tdnki/pages/45.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/tdnki/pages/42.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/tdnki/pages/44.html" />
              </rdf:Seq>
    </items>
	
		
    
  </channel>
    <item rdf:about="https://w.atwiki.jp/tdnki/pages/51.html">
    <title>Android/TIPS</title>
    <link>https://w.atwiki.jp/tdnki/pages/51.html</link>
    <description>
      ***&amp;spanclass(headline){ライブラリにjavadocを関連付ける}
[[Android]]プロジェクトのlibsフォルダに配置したライブラリは、自動的にビルドパス(Android Private Libraries)に追加されるが、ソースやjavadocのロケーションを変更できない。
回避策は以下。

1. ソースとjavadocを適当な場所に置く。

2. ライブラリのファイル名(拡張子.jarを含む)に.propertiesを付加したテキストファイルを作り、ライブラリと同階層に置く。
(ex.
libs
├　library.jar
├　library.jar.properties
├　src
│　└ library_sources.jar
└　doc
　　└ library_javadoc.jar

3. propertiesファイルに、ソースとjavadocのパスをそれぞれ&quot;src&quot;,&quot;doc&quot;で指定する。
library.jar.properties
 src=src/library_sources.jar
 doc=doc/library_javadoc.jar

4. プロジェクトを開きなおす。
反映されるタイミングがよくわからないが、プロジェクトを開きなおせば確実。    </description>
    <dc:date>2014-02-05T11:20:39+09:00</dc:date>
    <utime>1391566839</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/tdnki/pages/2.html">
    <title>メニュー</title>
    <link>https://w.atwiki.jp/tdnki/pages/2.html</link>
    <description>
      ***[[トップページ]]

----

Android
***[[漫画風カメラファインダーアプリ作成&gt;ComicFinder]]
***[[TIPS&gt;Android/TIPS]]

----

***[[Eclipse TIPS]]
***[[Android旧記事&gt;Android]]

----

***リンク
-[[@wiki&gt;&gt;http://atwiki.jp]]
-[[@wikiご利用ガイド&gt;&gt;http://atwiki.jp/guide/]]    </description>
    <dc:date>2014-01-09T12:34:41+09:00</dc:date>
    <utime>1389238481</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/tdnki/pages/49.html">
    <title>ComicFinder/OpenGLDrawing</title>
    <link>https://w.atwiki.jp/tdnki/pages/49.html</link>
    <description>
      ***&amp;spanclass(headline){7. OpenGLによる描画}
これまでの実装では、画像処理から描画までをメインスレッドでシーケンシャルに実行しているだけなので、FPSが安定しない。
描画をOpenGLに委譲することで、FPSの安定化を図っていく。

***&amp;spanclass(bold){7-1. }    </description>
    <dc:date>2013-10-27T13:42:12+09:00</dc:date>
    <utime>1382848932</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/tdnki/pages/40.html">
    <title>ComicFinder</title>
    <link>https://w.atwiki.jp/tdnki/pages/40.html</link>
    <description>
      目標：C++を利用した高速な[[Android]]カメラアプリを作る。

ポイント：
1. Cameraハードウェアを利用する。
2. JNIによりJavaからC++を呼び出す。
3. NEONを使用して高速化する。

目次：
[[0. 準備&gt;ComicFinder/Preparation]]
[[1. カメラプレビューの表示&gt;ComicFinder/CameraPreview]]
[[2. nativeコードへの移行&gt;ComicFinder/NativeCode]]
[[3. 描画の高速化&gt;ComicFinder/FasterDrawing]]
[[4. 画像処理&gt;ComicFinder/ImageProcessing]]
[[5. 画像処理の高速化&gt;ComicFinder/Optimization]]
[[6. 画像処理の高速化(NEON編)&gt;ComicFinder/OptimizationNeon]]

[[7. OpenGLによる描画(作成中)&gt;ComicFinder/OpenGLDrawing]]    </description>
    <dc:date>2013-10-24T14:12:35+09:00</dc:date>
    <utime>1382591555</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/tdnki/pages/48.html">
    <title>ComicFinder/OptimizationNeon</title>
    <link>https://w.atwiki.jp/tdnki/pages/48.html</link>
    <description>
      ***&amp;spanclass(headline){6. 画像処理の高速化(NEON編)}
NEON(ARMのCPU拡張命令)を使用して、さらなる高速化を図る。

***&amp;spanclass(bold){6-1. 準備}
NEONを有効にする。

[[Android]].mk
 LOCAL_ARM_NEON := true

これだけだと、NEONはv7aでしかサポートしていない、とビルドエラーが出るので
v7a向けのビルドを明示する。

Application.mk
 APP_ABI := armeabi-v7a

Android.mk
 ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
     LOCAL_ARM_NEON  := true
     LOCAL_CFLAGS += -DENABLE_NEON
 endif

もし APP_ABI をデフォルトの armeabi に戻してもビルドエラーとならないよう、TARGET_ARCH_ABI を見るようにした。
LOCAL_CFLAGS に渡したのは、今後、ビルド対象によってコード側も ifdef で切り分けるための define なので、
わかりやすい名前なら何でも良い。

ちなみに、これだけでかなり最適化がかかり、速くなってしまった。
40ms → 30ms

***&amp;spanclass(bold){6-2. 方針}
NEONでは64bitか128bitのベクタを使用できるので、unsigned charのピクセルデータは16個を並列処理できる。
ただし、今回はSobelフィルタの途中計算で16bit精度が必要となるので、8個ずつループを回して処理していく。

画像幅が8で割り切れない場合は端数処理が必要なので、水平方向のループカウンタjを外に出した。

[[ComicFinder]].cpp
 for (int i = 0; i &lt; height; i++) {
     int j = 0;
 #ifdef ENABLE_NEON
     for ( ; j &lt; width-7; j+=8) {
         // NEONコード
 
     }
 #endif
     for ( ; j &lt; width; j++) {
         // これまでのコード
 
     }
 }

本来はアセンブラを直で書かなければならないが、arm_neon.hで定義されているintrinsicsを使えば、
比較的高い可読性を持ちながら、NEON命令にコンパイルされるコードを書くことができる。
使用できる関数は[[ARM Complier Reference&gt;http://infocenter.arm.com/help/topic/com.arm.doc.dui0491c/DUI0491C_arm_compiler_reference.pdf]]を参照のこと。

ComicFinder.cpp
 #ifdef ENABLE_NEON
 #include &lt;arm_neon.h&gt;
 #endif

***&amp;spanclass(bold){6-3. NEONコード}
ラインの冒頭でスクリーントーンのパターンを定義する。座標の偶奇によって白黒を反転させる。
 uint8x8_t v_tone = vcreate_u8((i % 2) == 0 ? 0xff00ff00ff00ff00 : 0x00ff00ff00ff00ff);

三値化の置き換え。
ベクタの要素単位にif文を書くような事はできないので、vcge等の比較結果のビットマスクを返す関数を使用する。
黒、灰、白それぞれについて、各々の値を表すベクタとビットマスクの論理積を取れば、
これらの論理和が三値化後のベクタとなる。
     uint8x8_t v_src = vld1_u8(p_src+i*width+j);
     uint8x8_t v_mask_white = vcge_u8(v_src, vdup_n_u8(threshold_gray_and_white));
     uint8x8_t v_mask_gray = veor_u8(v_mask_white, vcge_u8(v_src, vdup_n_u8(threshold_black_and_gray)));
     uint8x8_t v_lum = vorr_u8(v_mask_white, vand_u8(v_tone, v_mask_gray));
比較が真なら全てのビットが1、偽なら全てのビットが0となるが、
これはそのまま白ピクセルおよび黒ピクセルと解釈できるので、不要な論理演算は排除した。
（例えば最後の行の v_mask_white は、正確には vand_u8(vdup_n_u8(0xff), v_mask_white) とするべきだが、
　白ピクセルの値が0xffであることから、この演算の結果は v_mask_white に一致する。
　同様の理由により、黒ピクセルに関してはマスクを作る必要すらない。）

Sobelフィルタの置き換え。
近傍8ピクセルをラインバッファからロードする。
この後で符号付き演算のオペランドに使うのでsignedに変換するが、wrap aroundが起きないよう16bitへの拡張も済ませておく。
     int16x8_t v_src00 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line0+j+0)));
     int16x8_t v_src01 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line0+j+1)));
     int16x8_t v_src02 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line0+j+2)));
     int16x8_t v_src10 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line1+j+0)));
     int16x8_t v_src12 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line1+j+2)));
     int16x8_t v_src20 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line2+j+0)));
     int16x8_t v_src21 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line2+j+1)));
     int16x8_t v_src22 = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(line2+j+2)));

エッジ判定。
     int16x8_t v_fx = vdupq_n_s16(0);
     v_fx = vsubq_s16(v_fx, v_src00);
     v_fx = vsubq_s16(v_fx, vshlq_n_s16(v_src10, 1));
     v_fx = vsubq_s16(v_fx, v_src20);
     v_fx = vaddq_s16(v_fx, v_src02);
     v_fx = vaddq_s16(v_fx, vshlq_n_s16(v_src12, 1));
     v_fx = vaddq_s16(v_fx, v_src22);
 
     int16x8_t v_fy = vdupq_n_s16(0);
     v_fy = vsubq_s16(v_fy, v_src00);
     v_fy = vsubq_s16(v_fy, vshlq_n_s16(v_src01, 1));
     v_fy = vsubq_s16(v_fy, v_src02);
     v_fy = vaddq_s16(v_fy, v_src20);
     v_fy = vaddq_s16(v_fy, vshlq_n_s16(v_src21, 1));
     v_fy = vaddq_s16(v_fy, v_src22);
 
     uint8x8_t v_edge = vclt_u8(vqmovun_s16(vaddq_s16(vabsq_s16(v_fx), vabsq_s16(v_fy))), vdup_n_u8(threshold_edge));

三値化の結果とマージ。
     v_lum = vand_u8(v_lum, v_edge);

RGBAの順にインターリーブする形でストア。
   　uint8x8x4_t v_dst;
   　v_dst.val[0] = v_lum;
     v_dst.val[1] = v_lum;
     v_dst.val[2] = v_lum;
     v_dst.val[3] = vdup_n_u8(0xff);
     vst4_u8(p_dst+(i*width+j)*4, v_dst);

NEON化により、画像処理時間は 30ms → 10ms となった。

ここまでのソース
#ref(ComicFinder.zip)    </description>
    <dc:date>2013-10-24T14:07:32+09:00</dc:date>
    <utime>1382591252</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/tdnki/pages/47.html">
    <title>ComicFinder/Optimization</title>
    <link>https://w.atwiki.jp/tdnki/pages/47.html</link>
    <description>
      ***&amp;spanclass(headline){5. 画像処理の高速化}
30fps(33ms)を目指して高速化していく。

現時点で1フレームの画像処理にかかる時間は80ms。
画像処理とは別に、描画にすでに10ms消費しているので、画像処理は23ms以下を目標とする。

***&amp;spanclass(bold){5-1. ループ統合}
画像の3値化と輪郭抽出で、2回に分けて全ピクセルをなめているが、
これは統合可能。

[[ComicFinder]].cpp
 int lum, fx, fy, im, ip, jm, jp;
 for (int i = 0; i &lt; height; i++) {
     im = i-1;
     ip = i+1;
     im = im &lt; 0 ? 0 : im;
     ip = ip &gt; height-1 ? height-1 : ip;
 
     for (int j = 0; j &lt; width; j++) {
         lum = p_src[i*width+j];
 
         if (lum &lt; threshold_black_and_gray) {
             lum = 0x00;
         }
         else if (lum &lt; threshold_gray_and_white) {
             lum = ((i+j) % 2) == 0 ? 0x00 : 0xff;
         }
         else {
             lum = 0xff;
         }
 
         if (lum != 0x00) {
             jm = j-1;
             jp = j+1;
             jm = jm &lt; 0 ? 0 : jm;
             jp = jp &gt; width-1 ? width-1 : jp;
 
             fx = 0;
             fx += p_src[im*width+jm] * -1;
             fx += p_src[i *width+jm] * -2;
             fx += p_src[ip*width+jm] * -1;
             fx += p_src[im*width+jp] *  1;
             fx += p_src[i *width+jp] *  2;
             fx += p_src[ip*width+jp] *  1;
             fy = 0;
             fy += p_src[im*width+jm] * -1;
             fy += p_src[im*width+j ] * -2;
             fy += p_src[im*width+jp] * -1;
             fy += p_src[ip*width+jm] *  1;
             fy += p_src[ip*width+j ] *  2;
             fy += p_src[ip*width+jp] *  1;
             lum = abs(fx) + abs(fy);
             lum = lum &lt; threshold_edge ? 0xff : 0x00;
         }
 
         p_dst[(i*width+j)*4+0] = p_dst[(i*width+j)*4+1] = p_dst[(i*width+j)*4+2] = lum;
         p_dst[(i*width+j)*4+3] = 0xff;
     }
 }

80ms → 50ms。

***&amp;spanclass(bold){5-2. ラインバッファ}
上下左右のピクセルを参照するSobelフィルタは、領域外を参照しないよう端点で分岐処理(jm, jp等の座標計算に三項演算を使用)しているので、
今後予定しているNEONコードを利用した並列処理に置き換えにくい。

そのため、入力画像の1ラインに対して、両端のピクセルをコピーした
長さ(width+2)のラインバッファを3本用意する形に変更し、端点の条件分岐を消しておく。

(NEON化しないとしても、条件分岐が消えることで高速化に繋がる可能性が高い。)

ComicFinder.cpp
 // ラインバッファの初期化
 unsigned char line_buf[(width+2)*3];
 unsigned char *line0 = line_buf;
 unsigned char *line1 = line0 + width+2;
 unsigned char *line2 = line1 + width+2;
 memcpy(line1+1, p_src, width);
 line1[0] = line1[1];
 line1[width+1] = line1[width];
 memcpy(line2+1, p_src+width, width);
 line2[0] = line2[1];
 line2[width+1] = line2[width];
 memcpy(line0, line1, width+2);

     // ラインバッファを参照する形に変更
     fx = 0;
     fx += line0[j+0] * -1;
     fx += line1[j+0] * -2;
     fx += line2[j+0] * -1;
     fx += line0[j+2] *  1;
     fx += line1[j+2] *  2;
     fx += line2[j+2] *  1;
     fy = 0;
     fy += line0[j+0] * -1;
     fy += line0[j+1] * -2;
     fy += line0[j+2] * -1;
     fy += line2[j+0] *  1;
     fy += line2[j+1] *  2;
     fy += line2[j+2] *  1;
     lum = abs(fx) + abs(fy);
     lum = lum &lt; threshold_edge ? 0xff : 0x00;

 // heightのループの最後でラインバッファを更新
 unsigned char *tmp = line0;
 line0 = line1;
 line1 = line2;
 line2 = tmp;
 memcpy(line2+1, p_src+(i+1 &gt; height-1 ? height-1 : i+1)*width, width);
 line2[0] = line2[1];
 line2[width+1] = line2[width];

50ms → 40ms。

ここまでのソース
#ref(ComicFinder.zip)    </description>
    <dc:date>2013-10-21T00:05:50+09:00</dc:date>
    <utime>1382281550</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/tdnki/pages/16.html">
    <title>Android</title>
    <link>https://w.atwiki.jp/tdnki/pages/16.html</link>
    <description>
      -[[Proguardについて&gt;Android/Proguardについて]]

-[[リンク&gt;Android/リンク]]    </description>
    <dc:date>2013-10-17T00:45:59+09:00</dc:date>
    <utime>1381938359</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/tdnki/pages/45.html">
    <title>ComicFinder/ImageProcessing</title>
    <link>https://w.atwiki.jp/tdnki/pages/45.html</link>
    <description>
      ***&amp;spanclass(headline){4. 画像処理}
漫画風の画像処理を実装していく。

***&amp;spanclass(bold){4-1. 三値化}
グレースケールの画像を、白、黒、グレーの三値に変換する。
閾値は動かしながら良いものを選ぶ。

[[ComicFinder]].cpp
 const int threshold_black_and_gray = 60;
 const int threshold_gray_and_white = 120;
 
 int lum;
 for (int k = 0; k &lt; width*height; k++) {
     lum = p_src[k];
     if (lum &lt; threshold_black_and_gray) {
         lum = 0x00;
     }
     else if (lum &lt; threshold_gray_and_white) {
         lum = 0x80;
     }
     else {
         lum = 0xff;
     }
 
     p_dst[k*4+0] = p_dst[k*4+1] = p_dst[k*4+2] = lum;
     p_dst[k*4+3] = 0xff;
 }

より漫画っぽくするために、グレー(128/255)を、白と黒の市松模様で表現する。
グレーと判定されたピクセルのx座標とy座標のパリティが一致したら黒、不一致なら白とした。

ComicFinder.cpp
 for (int i = 0; i &lt; height; i++) {
     for (int j = 0; j &lt; width; j++) {
         lum = p_src[i*width+j];
 
         if (lum &lt; threshold_black_and_gray) {
             lum = 0x00;
         }
         else if (lum &lt; threshold_gray_and_white) {
             lum = ((i+j) % 2) == 0 ? 0x00 : 0xff;
         }
         else {
             lum = 0xff;
         }
 
         p_dst[(i*width+j)*4+0] = p_dst[(i*width+j)*4+1] = p_dst[(i*width+j)*4+2] = lum;
         p_dst[(i*width+j)*4+3] = 0xff;
     }
 }

***&amp;spanclass(bold){4-2. 輪郭抽出}
三値化前の画像の輪郭を抽出し、主線として重ねあわせる。
輪郭抽出のアルゴリズムにはSobelフィルタを使用し、この結果を適当な閾値で二値化したものを主線とした。

ComicFinder.cpp
 const int threshold_edge = 32;
 int fx, fy, im, ip, jm, jp;
 for (int i = 0; i &lt; height; i++) {
     im = i-1;
     ip = i+1;
     im = im &lt; 0 ? 0 : im;
     ip = ip &gt; height-1 ? height-1 : ip;
 
     for (int j = 0; j &lt; width; j++) {
         jm = j-1;
         jp = j+1;
         jm = jm &lt; 0 ? 0 : jm;
         jp = jp &gt; width-1 ? width-1 : jp;
 
         fx = 0;
         fx += p_src[im*width+jm] * -1;
         fx += p_src[i *width+jm] * -2;
         fx += p_src[ip*width+jm] * -1;
         fx += p_src[im*width+jp] *  1;
         fx += p_src[i *width+jp] *  2;
         fx += p_src[ip*width+jp] *  1;
         fy = 0;
         fy += p_src[im*width+jm] * -1;
         fy += p_src[im*width+j ] * -2;
         fy += p_src[im*width+jp] * -1;
         fy += p_src[ip*width+jm] *  1;
         fy += p_src[ip*width+j ] *  2;
         fy += p_src[ip*width+jp] *  1;
         lum = abs(fx) + abs(fy);
         lum = lum &lt; threshold_edge ? 0xff : 0x00;
 
         p_dst[(i*width+j)*4+0] = p_dst[(i*width+j)*4+1] = p_dst[(i*width+j)*4+2] = lum &amp; p_dst[(i*width+j)*4+0];
         p_dst[(i*width+j)*4+3] = 0xff;
     }
 }

#ref(comicfinder_04_01.png)
アップロードする際に画像縮小したため、スクリーントーン部分が補間処理によってグレーに戻ってしまった。
アプリ内でも拡縮により同様のことが起き得るので、コード上でトーンを適用する位置については再考の余地がある。

ここまでのソース
#ref(ComicFinder.zip)    </description>
    <dc:date>2013-09-04T01:51:39+09:00</dc:date>
    <utime>1378227099</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/tdnki/pages/42.html">
    <title>ComicFinder/CameraPreview</title>
    <link>https://w.atwiki.jp/tdnki/pages/42.html</link>
    <description>
      ***&amp;spanclass(headline){1. カメラプレビューの表示}
プレビュー画像に使用されるNV21フォーマットは、輝度のみのデータを取り出しやすいこと、
最終的な画像処理にも輝度しか使用しない予定であることから、モノクロのプレビュー表示をゴールとする。

***&amp;spanclass(bold){1-1. Permissionを付加する}
AndroidManifest.xml
 &lt;uses-permission android:name=&quot;android.permission.CAMERA&quot;/&gt;

***&amp;spanclass(bold){1-2. Activityの向きを固定する}
端末の回転を考慮したくないので、Orientationはlandscape固定とする。

AndroidManifest.xml
 &lt;activity
     android:name=&quot;com.example.comicfinder.MainActivity&quot;
     android:label=&quot;@string/app_name&quot;
     android:screenOrientation=&quot;landscape&quot; &gt;
     &lt;intent-filter&gt;
         &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;
 
         &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
     &lt;/intent-filter&gt;
 &lt;/activity&gt;

***&amp;spanclass(bold){1-3. フルスクリーン}
MainActivity.java
 @Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
 
     // setContentView()より先に呼ぶこと。
     getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
     requestWindowFeature(Window.FEATURE_NO_TITLE);
 
     setContentView(R.layout.activity_main);
 }

***&amp;spanclass(bold){1-4. Cameraオブジェクトの取得と破棄}
MainActivity.java
 @Override
 protected void onResume() {
     super.onResume();
 
     camera = Camera.open();
     if (camera == null) {
         finish();
         return;
     }
 }
 
 @Override
 protected void onPause() {
     super.onPause();
 
     if (camera != null) {
         camera.release();
     }
 }

***&amp;spanclass(bold){1-5. プレビュー表示用Surfaceの準備}
activity_main.xml
 &lt;SurfaceView
     android:id=&quot;@+id/surface&quot;
     android:layout_width=&quot;match_parent&quot;
     android:layout_height=&quot;match_parent&quot; /&gt;

MainActivity.java
 @Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
 
     getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
     requestWindowFeature(Window.FEATURE_NO_TITLE);
 
     setContentView(R.layout.activity_main);
 
     surface = (SurfaceView)findViewById(R.id.surface);
 }

***&amp;spanclass(bold){1-6. プレビュー画像の取得}
プレビューを表示する方法は大きく2通り。

A) Camera#setPreviewDisplay()で描画用のSurfaceを渡して、描画までの一切を依頼する。プレビュー画像には触れない。
B) 用意したバッファにCamera#addCallbackBuffer()でプレビュー画像を書き込んでもらい、(任意の加工をして)描画する。

今回はプレビュー画像を編集したいため、Bを選択する。
この場合は、描画処理まで自分で責任を持たなければいけない。
プレビュー画像書き込み依頼は非同期であり、Camera#setPreviewCallbackWithBuffer()により指定したcallbackが呼ばれるのを待つことになる。

MainActivity.java
 @Override
 protected void onResume() {
     super.onResume();
 
     camera = Camera.open();
     if (camera == null) {
         finish();
         return;
     }
 
     Camera.Parameters params = camera.getParameters();
 
     // プレビューフォーマットをNV21に設定
     if (ImageFormat.NV21 != params.getPreviewFormat()) {
         if (params.getSupportedPreviewFormats().contains(ImageFormat.NV21)) {
             finish();
             return;
         }
 
         params.setPreviewFormat(ImageFormat.NV21);
     }
 
     // プレビューサイズを(横幅が)最も大きいものに設定
     for (Size s : params.getSupportedPreviewSizes()) {
         if (params.getPreviewSize().width &lt; s.width) {
             params.setPreviewSize(s.width, s.height);
         }
     }
 
     camera.setParameters(params);
     previewSize = params.getPreviewSize();
 
     // プレビューのrawデータ。8bit * (画サイズ) * 1.5
     byte[] previewBuffer = new byte[previewSize.width * previewSize.height * 3 / 2];
     // 描画用バッファ。32bit * (画サイズ)
     previewBufferRgba = new int[previewSize.width * previewSize.height];
 
     camera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            updateFrame(data);
        }
     });
     camera.startPreview();
     camera.addCallbackBuffer(previewBuffer);
 }
 
 @Override
 protected void onPause() {
     super.onPause();
 
     if (camera != null) {
         camera.stopPreview();
         camera.release();
     }
 }

***&amp;spanclass(bold){1-7. 描画画像の作成}
プレビュー画像のbyte列(NV21)をもとに、intの配列(RGBA)を書き込んで描画する。
NV21には、初めのwidth*heightピクセルに輝度が詰まっているので、これをそのまま読み出してRGBにコピーすればよい。

MainActivity.java
 private void updateFrame(byte[] data) {
     for (int k = 0; k &lt; previewSize.width * previewSize.height; k++) {
         int lum = data[k] &amp; 0xff;
         previewBufferRgba[k] = (0xff &lt;&lt; 24) | (lum &lt;&lt; 16) | (lum &lt;&lt; 8) | lum;
     }
 
     Canvas c = surface.getHolder().lockCanvas();
     if (c != null) {
         try {
             c.drawBitmap(previewBufferRgba, 0, previewSize.width, 0, 0, previewSize.width, previewSize.height, true, null);
         }
         finally {
             surface.getHolder().unlockCanvasAndPost(c);
         }
     }
 
     camera.addCallbackBuffer(data);
 }

***&amp;spanclass(bold){1-8. ディスプレイサイズに合わせてストレッチ}
 float scale = Math.max((float)surface.getWidth() / previewSize.width, (float)surface.getHeight() / previewSize.height);
 c.scale(scale, scale);
 c.drawBitmap(previewBufferRgba, 0, previewSize.width, 0, 0, previewSize.width, previewSize.height, true, null);
変化していない公算が高い値を、除算を使って毎フレーム求めるのもどうかと思うが、
プレビューサイズ決定とSurfaceのサイズ確定を待ち合わせてcacheするのも面倒なので、この実装とする。
scaleの計算自体は1msecを切っており、パフォーマンスへの影響はほぼない。

***&amp;spanclass(bold){1-9. 目標達成}
モノクロのプレビューを表示することができた。

#ref(comicfinder_01.png)
プレビュー表示に成功したエリエールタワー

ここまでのソース
#ref(ComicFinder.zip)    </description>
    <dc:date>2013-09-04T00:20:48+09:00</dc:date>
    <utime>1378221648</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/tdnki/pages/44.html">
    <title>ComicFinder/FasterDrawing</title>
    <link>https://w.atwiki.jp/tdnki/pages/44.html</link>
    <description>
      ***&amp;spanclass(headline){3. 描画の高速化}
前回、Canvas#drawBitmap()が意外に遅いことがわかってしまった。

***&amp;spanclass(bold){3-1. drawBitmap(Bitmap, float, float, Paint)を使う}
このメソッドに、今まではintの配列を渡していたが、Bitmapを渡すオーバーロードを呼んでみたところ、int[]に比べて3倍ほど高速であった。
そこで、描画データはBitmapで持つように修正する。

MainActivity.java
 @Override
 protected void onResume() {
     ：
     ：
     // 描画用Bitmap
     previewBitmap = Bitmap.createBitmap(previewSize.width, previewSize.height, Bitmap.Config.ARGB_8888);
     ：
     ：
 }
 
 @Override
 protected void onPause() {
     super.onPause();
 
     if (camera != null) {
         camera.stopPreview();
         camera.release();
     }
 
     if (previewBitmap != null) {
         previewBitmap.recycle();
     }
 }

***&amp;spanclass(bold){3-2. nativeコードでBitmapを扱う}
int[]からBitmapを生成する処理もそこそこ重く、描画データをBitmapに変更することで浮くであろうマージンを食い潰してしまう。

幸いnativeからBitmapのピクセルデータに触る方法があるので、Bitmapをそのまま渡し、書き換える形に変更する。
C++がJavaのクラスを意識するのは気持ち悪いが、速度にはかえられない。

[[ComicFinder]].hpp
 JNIEXPORT bool JNICALL Java_com_example_comicfinder_MainActivity_processImage
   (JNIEnv *, jobject ,jbyteArray, jobject, jint, jint);

ComicFinder.cpp
 #include &lt;stdlib.h&gt;
 #include &lt;android/bitmap.h&gt;
 
 #include &quot;ComicFinder.hpp&quot;
 
 JNIEXPORT bool JNICALL Java_com_example_comicfinder_MainActivity_processImage
     (JNIEnv *env, jobject me, jbyteArray src, jobject dst, jint width, jint height)
 {
     int ret;
     unsigned char *p_dst;
     if ((ret = AndroidBitmap_lockPixels(env, dst, reinterpret_cast&lt;void **&gt;(&amp;p_dst))) &lt; 0) {
         return false;
     }
 
     unsigned char *p_src = reinterpret_cast&lt;unsigned char *&gt;(env-&gt;GetPrimitiveArrayCritical(src, NULL));
     if (NULL == p_src) {
         AndroidBitmap_unlockPixels(env, dst);
         return false;
     }
 
     for (int k = 0; k &lt; width*height; k++) {
         p_dst[k*4+0] = p_dst[k*4+1] = p_dst[k*4+2] = p_src[k];
         p_dst[k*4+3] = 0xff;
     }
 
     env-&gt;ReleasePrimitiveArrayCritical(src, p_src, 0);
     AndroidBitmap_unlockPixels(env, dst);
 
     return true;
 }

jnigraphicsをリンクする。

[[Android]].mk
 LOCAL_LDLIBS := -ljnigraphics

***&amp;spanclass(bold){3-3. まとめ}
描画が10msで回るようになった。

ここまでのソース
#ref(ComicFinder.zip)    </description>
    <dc:date>2013-08-31T23:32:14+09:00</dc:date>
    <utime>1377959534</utime>
  </item>
  </rdf:RDF>
