五百円貨幣潜像Androidアプリ

新しい五百円貨幣が、令和3年11月1日より発行されました。ほんの一ヶ月前ですね。
[解説！新しい500円貨：財務省] (https://www.mof.go.jp/policy/currency/bill/20210816.html)にて解説があります。
そうすぐに新貨を手にすることはないかもしれませんが、
　　そうか、<ruby>君<rt>旧貨</rt></ruby>は　いずれ　居なくなるのか…。
そういえば白銀色に輝く初代五百円貨幣（昭和57年4月1日発行）もいつしか見かけることも少なくなり、
平成12年8月1日にやや黄み<ruby>掛<rt>が</rt></ruby>かった輝きを持つ二代目が登場して。
しかして今回の三代目は<ruby>二色三層構造<rt>バイカラー・クラッド</rt></ruby>。

まるで源氏将軍の賴朝、賴家、實朝のようではないですか。源氏を退け、栄華を誇った平氏一門。しかれどその平氏もまた壇ノ浦にて滅亡。鎌倉幕府も源氏が将軍職に就けたのはこの三代まで。

ああ諸行無常。盛者必衰。

えっと。なんの話でしたっけ？そうだ五百円貨幣についてでした。
そうそう！お菓子を買ったおつりに五百円貨幣もらったので見たら「令和三年」ヤッタ！キタッ！新貨だ！と思いきや、二代目ェ…。

それではみなさん、お手元に二代目五百円貨幣をお持ちください。
真正面から見たり、ちょっと傾けて下から見上げてください。
注目箇所は2つの０（ゼロ）の中。ほらほら、縦書きで「５００円」の文字が見えてくるじゃありませんか！
これぞ、前述の財務省のWebページにも掲載されている偽造防止技術の一つ「**潜像**」です。

![500yen.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100612/8fe95494-432a-f083-3149-779c2143aa48.jpeg)

この二代目のニッケル黄銅製の「潜像」を実現するAndroidアプリを作ります。
最新の三代目のじゃないところが私の天邪鬼っぽいところ。だってまだ入手できてないんだもん。

# 完成した五百円貨幣潜像アプリ（二代目版）

実機端末にインストールしてみました。これぞまさしく電子マネー。偽造防止の潜像も施されてます。

![PXL_20211105_064406654-_online-video-cutter.com_-_1_.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100612/853311f9-d157-b575-02ba-64421fbb0704.gif)

この動画では見づらいでしょうか。

では実機端末とAndroid Studioを繋げてLogcatの動画撮影機能で撮った動画がこちらです。少しは分かりやすいでしょうが、傾けずとも単に点滅しているだけのアプリにしか見えないかもしれませんね（それならそれとて、真のクソアプリですが）。

![coin500LatentImage.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100612/c14ac54d-b260-49fa-3bbc-bdc6313d17bb.gif)

なのでやはり皆様のお手元でもぜひこのアプリを作って実機を手に持ち傾けて遊んでいただきたいと思います。

そういえば[クソアプリ Advent Calendar 2021] (https://qiita.com/advent-calendar/2021/kuso-app)に掲げられた口上に、

> 今年も役に立たない、世の中に貢献しないアプリとかサービスを出しあって遊ぼうぜ！

とあります。見事に果たせた思いで感無量です。
これぞ500円の価値も無い、まさにプライスレス[^priceless]ですらレスなアプリ。
[^priceless]: 広辞苑で「プライスレス」は「値段をつけられないほど貴重なこと」とのこと。ですが当クソアプリは「値段をつけるほどでもないし、貴重でもない」しろものです。

# 技術要素

こんなクソアプリに箔を付けるためにも一丁前に技術面からの解説をしたいと思います。
題材がAndroidアプリなので、それにおける以下の2点がポイントです。

- センサーを使っての傾斜角（勾配）値の取得方法
- 画像（`ImageView`）の透明化

## 全リソースと作り方

技術要素の説明の前に、全コードを掲載します。そして作り方もお伝えしておきます。

まずご用意いただきたい環境ですが、以下の通りです。

- Android Studio
- Android端末実機（やはりエミュレータでは面白くありません）

Android Studioを起動したら、新規プロジェクトを作ってください。パッケージ名やアプリ名は任意で構いません。
そうしたら、MainActivity.javaやactivity_main.xmlなど存在していると思いますので、以下のコードを丸ごとコピー＆ペーストしてください。

### 画像ファイル

2枚あります。透過PNGです。両方とも、/res/drawableフォルダに入れておいてください。ファイル名もこの通りにしてください（大文字小文字にも注意です）。
**ただし、この画像ファイルをダウンロードしないでください。なぜなら[イラストAC] (https://www.ac-illust.com/)から私がダウンロードしたファイルはインターネット上で素材としての再配布が禁止されているからです。なので皆さんがこの画像を入手したい場合は、[イラストAC] (https://www.ac-illust.com/)からダウンロードしてください。なお、[イラストAC] (https://www.ac-illust.com/)からダウンロードした画像の加工は認められております。**

- ファイル名：coin500.png

![coin500.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100612/da33b660-b348-cc96-259d-e7bd404845a9.png)

- ファイル名：coin500latentimage.png

![coin500latentimage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100612/84ab290a-c9a8-7984-f7db-d1187db55496.png)

後者のほうには縦書きで「５００円」が2つあるのがポイントです。

### activity_main.xml

レイアウトリソースファイルです。

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/coin500LatentImage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:contentDescription="潜像付き(透明化なんてしないでこのまま放置)"
        android:src="@drawable/coin500latentimage" />

    <ImageView
        android:id="@+id/coin500"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:contentDescription="こちらを透明化します"
        android:src="@drawable/coin500" />

</FrameLayout>
```

`FrameLayout`で前出の2枚のPNG画像をピッチリと重ねているのがポイントです。

### MainActivity.java

パッケージ宣言と`import`文は省略していますので、~~自力で~~Android Studioに補完してもらって書き足しください。

```java:MainActivity.java
public class MainActivity extends AppCompatActivity {
    SensorManager sensorManager;
    SensorEventListener sensorEventListener;

    // 行列数
    static final int MATRIX_SIZE = 16;
    // 三次元なので3(XとYとZで、3)
    static final int AXIS_NUM = 3;

    // 回転
    float[] rotationMatrix = new float[MATRIX_SIZE];
    // 傾斜
    float[] inclinationMatrix = new float[MATRIX_SIZE];
    // [0]方位角、[1]傾斜角、[2]回転角
    float[] attitude = new float[AXIS_NUM];

    // 地磁気
    float[] geomagnetic;
    // 加速度
    float[] accelerometer;

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

        ImageView coin500 = findViewById(R.id.coin500);

        // センサーマネージャの取得
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

        // センサーリスナーの生成（匿名オブジェクトで作りました）
        sensorEventListener = new SensorEventListener() {
            // センサーの値が変更されたときに呼ばれるメソッド
            @Override
            public void onSensorChanged(SensorEvent sensorEvent) {
                switch (sensorEvent.sensor.getType()) {
                    case Sensor.TYPE_MAGNETIC_FIELD: // 地磁気センサー
                        geomagnetic = sensorEvent.values.clone();
                        break;
                    case Sensor.TYPE_ACCELEROMETER: // 加速度センサー
                        accelerometer = sensorEvent.values.clone();
                        break;
                }

                if (geomagnetic != null && accelerometer != null) {
                    float[] remaped = new float[MATRIX_SIZE];
                    // 加速度センサーと地磁気センサーから回転行列を取得
                    SensorManager.getRotationMatrix(rotationMatrix, inclinationMatrix, accelerometer, geomagnetic);
                    SensorManager.remapCoordinateSystem(rotationMatrix, SensorManager.AXIS_X, SensorManager.AXIS_Z, remaped);
                    SensorManager.getOrientation(remaped, attitude);

                    // 傾きの値だけが欲しい（[0]は方位、[2]は回転）
                    float pitch = attitude[1];
                    if (pitch > 0.0F && pitch <= 1.5F) { // 「下から見上げた時」に限りたい
                        // 透明度の値を算出
                        float alpha = 1.5F - pitch;
                        coin500.setAlpha(alpha);
                    }
                }
            }

            @Override
            public void onAccuracyChanged(Sensor sensor, int i) {
                // センサーの精度が変更されたときに呼び出されるメソッドだが、今回は出番なし。
            }
        };
    }

    /**
     * onResumeでイベントリスナーの登録をする。
     */
    public void onResume() {
        super.onResume();
        // 地磁気センサー登録
        sensorManager.registerListener(
                sensorEventListener,
                sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
                SensorManager.SENSOR_DELAY_FASTEST);
        // 加速度センサー登録
        sensorManager.registerListener(
                sensorEventListener,
                sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
                SensorManager.SENSOR_DELAY_FASTEST);
        // いちばん速いSENSOR_DELAY_FASTESTを選択しちゃった（バッテリーの消費具合に注意）
    }

    /**
     * onPauseでイベントリスナーの解除をする（これを忘れるとバッテリーの無駄な消費になる）
     */
    public void onPause() {
        super.onPause();
        sensorManager.unregisterListener(sensorEventListener);
    }
}
```

Javaでやりました。Kotlinがいいな、と思う方はAndroid Studioにコンバートしてもらってください。

### AndroidManifest.xml

パッケージ名などは各自差し替えてくださいね。

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="jp.co.casareal.coin500latentimage">
    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.Coin500LatentImage"
        tools:ignore="LockedOrientationActivity">
        <!-- android:screenOrientation="portrait"すると
            「どの向きでも使えるようにしたほうがユーザ体験がいいのでunspecifiedかfullSensorを使うべし」
             というWarningを抑制するためにtools:ignore="LockedOrientationActivity"する。 -->
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
```

ポイントは、`android:screenOrientation="portrait"`にすることで縦長画面に固定させています。
なんせこのアプリ、手に持ってゆっさゆっさと振り回すので、その際に端末が縦長画面に横長画面にと自動で切り替わると<ruby>埒<rt>らち</rt></ruby>が明かないので。

## センサーを使っての傾斜角（勾配）値の取得方法

上記のコードにコメントを付記したので、それでお察しいただければ幸いですし、
そもそも[開発者向け公式サイト] (https://developer.android.com/guide/topics/sensors)に掲載されている情報を参考にし、サンプルコードを見よう見まねで作れてしまうので、私が殊更説明するほどのことでもないのですが、一応説明します。

### SensorManagerとSensorEventListener

まず主役はやっぱり`SensorManager`です。これをまずは取得しなければ話が始まりません。

```java:センサーマネジャーの取得
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
```

そして端末の傾きに応じて画像を透明化するという処理をさせたいので、`SensorEventListener`オブジェクトも生成しておきます。今回は、`SensorEventListener`インタフェースのオブジェクト生成は、匿名クラスでコーディングしました。2つのメソッドをオーバーライドしなければなりませんが、今回`onAccuracyChanged`は出番なしです。

```java:SensorEventListenerオブジェクト生成
sensorEventListener = new SensorEventListener() {
    @Override
    public void onSensorChanged(SensorEvent sensorEvent) {
        // センサーの値が変更されたときに呼ばれるメソッド
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int i) {
         // センサーの精度が変更されたときに呼び出されるメソッドだが、今回は出番なし。
    }
};
```

こうして取得した`SensorManager`に、生成した`SensorEventListener`を登録するタイミングは、`onResume`メソッド内とする、というのがお作法のようです。かつ、解除するタイミングは`onPause`メソッド内とする、というのもお作法のようです。

### TYPE_ORIENTATIONを使うべからず

`TYPE_ORIENTATION`はAPIレベル15にて非推奨となってしまいました。もう10年前のはなしです。
傾斜角の値は、`TYPE_ACCELEROMETER`（加速度センサー）と`TYPE_MAGNETIC_FIELD`（磁気センサー）を組み合わせて取得すべしとのことです。

### 情報通知頻度

| 定数 | 遅延時間（目安） |
|:-----------|------------:|
| `SENSOR_DERAY_FASTEST` | 0ms |
| `SENSOR_DERAY_GAME` | 20ms |
| `SENSOR_DERAY_NORMAL` | 60ms |
| `SENSOR_DERAY_UI` | 200ms |

今回、いちばん速い`SENSOR_DERAY_FASTEST`にしちゃいました。クソアプリなのになんてまあ贅沢な。でもそのせいでバッテリーの減りが顕著であれば、それならそれでクソアプリ冥利に尽きます。バッテリーも尽きます。それでは困る方は遅いGAMEやNORMALにでも差し替えてください。

## 画像（ImageView）の透明化

このコード中では`pitch`と名付けた変数が傾斜角の値なのですが、今回「下から見上げた時」だけ潜像を表示させたかったので`if`文で具合を調整してみました。

| スマホの状態 | 変数`pitch`の値 |
|:-----------|------------:|
| 垂直に立たせた | `0.0F` |
| 水平に仰向け | `1.5F` |

私の持っているASUS製の端末ではこんな数値でした。でも端末によっては数値が異なるらしいです…。ちょっとそこまでは検証できませんでした。

そして透明度は`alpha`と名付けた変数にしたのですが、以下のようにしたかったので自分なりに工夫して算出してみました。

| スマホの状態 | 変数`alpha`の値 |
|-----------|------------|
| 垂直に立たせた | `1.0F`にして完全に**不**透明度にしたい |
| 水平に仰向け | `0.0F`にして完全に透明にしたい |

この`alpha`値を`ImageView`の`setAlpha`メソッドに渡します。
センサーから取得した傾斜角の値の型は`float`、`setAlpha`メソッドの引数の型も`float`と、そこんところはなんか都合がよかったです。

- 上に重ねたcoin500.pngが透明度ゼロならば、下敷きのcoin500latentimage.pngはユーザーにはまったく見えません。
- 端末をちょっと傾けて上に重ねたcoin500.pngをちょっと透明化させます。すると下敷きのcoin500latentimage.pngがちょっと見えてきます。ちょっとちょっとちょっと。
- 端末を水平に置いたらcoin500.pngは完全に透明になり、下敷きのcoin500latentimage.pngだけが目視できます。

そういうトリックで実現を果たしました。

## 実現はしましたが…

ふと思うことがあります。

coin500latentimage.pngは、縦書きの「５００円」だけ描画した画像でよかったんじゃないか説。そしてそれを透過対象とすればよかったんじゃないか説。

そんなことよりも！

**机に仰向けに置いたスマホを、ユーザーが真正面から覗き込んでも縦書きの「５００円」が見えちゃう！**

![position_boy_shita.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100612/cacc13c0-09c3-b012-190e-bbbf8a63eaa8.png)

いやもう、「ユーザーが真正面から」でなくとも、とにかく**スマホを水平に仰向けに置いただけで見えちゃう！**下から見上げた時にこそ見える潜像なのに！

とうとうバレてしまいましたね、このアプリの不完全さ。そういうことのも含めて「クソアプリだな」と感嘆していただければ幸いです。

# 次なる野望

次回は三代目の五百円貨幣にチャンレンジしてみたいですね。前述の財務省のWebページを今一度確認してみます。

## 潜像がパワーアップ

潜像は上から見たら「`JAPAN`」、下から見たら「`500YEN`」と二種類です。`if`構文でなんとかなるかな。

## 微細技術

微細文字と微細点と微細線ですが、Impress Watchの「[新500円玉を入手した。スマホでは撮れない微細技術] (https://www.watch.impress.co.jp/docs/topic/1363850.html)」という記事にて、こうあります。

> iPhoneでも撮影は厳しい? 微細な偽造防止技術の数々

> 実際撮影をしようと思っても、筆者のiPhone 12 Pro Maxでは旨く穴がうつりませんでした。他のスマホなら旨く撮れるものもあるかもしれません。一眼レフを持ち出し、マクロレンズを使ってようやく撮影できました。

スマホのカメラですら無理な表現を、スマホの画面上で実現してみようという挑戦です。

## 異形斜めギザ

えっとですね。五百円貨幣を斜めに表示させたスマホの画面を指で触って（なぞって）みたら、おおっ！ギザギザを感じる！しかも異形！というのを実現したいです。「異形斜めギザ」というネーミングもなんかカッコイイし。

![500_edge.jpg](https://www.mof.go.jp/policy/currency/coin/general_coin/images/500_edge.jpg)

…などと申し上げましたが、すみません、スマホの画面でどう三次元の<ruby>凹凸<rt>おうとつ</rt></ruby>を実現すればいいのか分からないくせに勢いで<ruby>宣<rt>のたま</rt></ruby>ってしまいました。[点字ディスプレイ] (https://blitab.com/)でもあるまいし。

## 三代目の五百円貨幣と見せかけて、実は初代のだったというドッキリ

あれ～。傾けても潜像が出てこないぞ～。と思ったらなんだよ！これ、初代のじゃんか！というドッキリ。
このアプリを起動したら数回に１度の割合で初代五百円貨幣にしてユーザーを困惑させてウッシッシ。そんな仕掛けを施してみたいですね。

それならそれで、昭和62年銘・64年銘のであれば発行数が極めて少ないため<ruby>蒐集家<rt>しゅうしゅうか</rt></ruby>の間で人気が高く、古銭商の買取価格も1,000円前後になるなどプレミア価値がついていますので、どうせ表示するなら昭和62年銘・64年銘のにしたいと思います。

だから、なに？

## 三代目のバイカラー・クラッドの五百円貨幣と見せかけて、実は記念貨幣だったというドッキリ

私、[2020年東京オリンピック・パラリンピック競技大会記念500円バイカラー・クラッド貨幣] (https://www.mof.go.jp/policy/currency/coin/commemorative_coin/2020_olyparagames/20191129.html)を持っているんです。雷神・風神のデザインが格好良くて気に入ってます。


| オリンピック | パラリンピック |
|:-----------:|:------------:|
| <img width="200" alt="2020oly_500o_thundergod.gif" src="https://www.mof.go.jp/policy/currency/coin/commemorative_coin/2020_olyparagames/20191129/2020oly_500o_thundergod.gif"> | <img width="200" alt="2020par_500o_WindGod.gif" src="https://www.mof.go.jp/policy/currency/coin/commemorative_coin/2020_olyparagames/20191129/2020par_500o_WindGod.gif"> |




この記念貨幣もバイカラー・クラッド。なのでアプリを起動したら「すわ！今度こそ三代目のか！」と期待させておいて、潜像のない記念貨幣でしたーハイ残念！でもレアキャラ登場でむしろラッキー！というドッキリも仕掛けてみたいですね。

だから、なに？
もういいよ。この記事、もう終わらせようよ。

# 最後に。今日は何の日？

ちなみに今日は12月3日。征夷大将軍に任じられる2年前の建久元年(1190年)のこの日、源頼朝は任じられていた権大納言そして右近衛大将を両官とも辞任した日でした。

だから、なに？冒頭の源氏が云々の伏線を回収してヤッたゼ！と見せかけたいだけの牽強付会。

そんなことより、メリークリスマス。

## （追記：後日談）一万円紙幣すきいれ（すかし）Androidアプリを作りました

この記事を参考にして、今度は一万円紙幣版を作ってみました。

![bill10000yenwatermark.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100612/f89a0340-ac3e-e49d-6338-d8ba737bb811.gif)
実機にインストールして、仰視（頭上に持ち上げて仰ぎ見ること）したり、傾けたりしてこの動画を撮りました。

[国立印刷局の「偽造防止技術～現在発行されているお札～」](https://www.npb.go.jp/ja/intro/gizou/genzai.html)にて説明されている、すき入れ（白黒すかし）、3本のすき入れバーパターン、ホログラム、潜像模様、左右両端パールインキ、を実装してみました。
太陽やライトに向けて仰視するので紙幣全体が透けるような演出も施しています。（とは言っても、別に太陽やライトに向けなくてもすかしは出るんですけどね）
[野良アプリとしてここに置いておきました。](https://youngreiri.github.io/android/banknote10000yenwatermark.apk)Androidスマホでこの記事をブラウザで閲覧し、このリンクをクリックしてください。
野良アプリなため、スマホが「本当にインストールするの？」と再三にわたり尋ねてきますが、よろしければどうぞお楽しみください。