YUV420_888のBitmap化


## CameraXつかってみた

とりあえず、最低限の利用のドキュメントで簡単にプレビューのデータが取れそうなのでやってみた。

とはいえ、取得できるImageデータはBitmapにすることすら標準的には存在していないぽい。

まあ、YUVのままもろもろ処理もいいですが、PreviewViewじゃなくてImageViewでプレビュー出してみたいからBitmap化しよう。

で、いろいろネットの海をさまよったが、とってもわかりにくかったのでまとめた。

## YUV420_888のこと
YUVそのものの詳細は説明しません。
Yが輝度でUVがカラー座標という映像情報のデジタル化手法という感じ。

YUV420は輝度はすべてのピクセルに対応した量があり、UVは近傍4ピクセルに一つのセットがあるそうです。
で、後ろの888はおそらくそれぞれ8bitってことかと。

## ImageProxyのこと

CameraXで受け取るImageProxyにどういう風に情報が格納されているのかはドキュメントを見つけられなかったのでここに今回得た情報を残すわけです。

PlanesというのがYUV各バッファ分存在する。
Planes[0]がY、[1]がU、[2]がVです。
ややこしいのはこれらのサイズ。

Yは画像サイズ分のPixel数分のサイズなのでいいのですが、UとVは(画像サイズ/2-1)というサイズが表示されます。

4分の1ではなく2分の1で、さらに-1されている。

結論からサクッというとvuvuvu.....vuと格納されてる**共通**のメモリの最初のvを指しているPlaneと、最初のuを指しているPlane。

それぞれvは最後のuは数えず、uは最初のvを数えないのが-1。

メモリは共通なので4分の1の倍である2分の1となり、それぞれ-1があることで上記のサイズになります。

PlanesのgetPixelStrideという関数は何バイトごとに読んで行けよっていう数字で、上記のサイズになったPixel4では2が返ってきます。

（vuが逆だったり、vとuの間のバイト数がちがったりなど機種によっていろいろありそうだし、-1を考慮してないサンプル等もよく見かけたので注意。）

## ざっくりコード
```java
    private static Bitmap YUV420_888toBitmap(ImageProxy image)
    {
        int w = image.getWidth();
        int h = image.getHeight();

        ImageProxy.PlaneProxy[] planes = image.getPlanes();

        ImageProxy.PlaneProxy yplane = planes[0];
        ImageProxy.PlaneProxy uplane = planes[1];
        ImageProxy.PlaneProxy vplane = planes[2];

        ByteBuffer ybuf = yplane.getBuffer();
        ByteBuffer ubuf = uplane.getBuffer();
        ByteBuffer vbuf = vplane.getBuffer();

        int uvx = vplane.getPixelStride();
        int uvw = vplane.getRowStride();

        int[] bmp = new int[w * h];
        int[] col = new int[(uvw / uvx) * 3];

        int bp = 0;
        for(int dy = 0;dy < h;++dy)
        {
            //4ピクセルブロック単位なので隔行、隔ピクセルで計算処理したものを4回使いまわす
            if((dy & 1) == 0)
            {
                int uvp = 0;
                int idx = uvw * (dy>>1);
                for(int dx = 0;dx < uvw;dx += uvx)
                {
                    vbuf.position(idx + dx);
                    ubuf.position(idx + dx);

                    int v = vbuf.get();
                    int u = ubuf.get();

                    v = (v & 0xFF) - 128; //0xFFでANDしないと暗黙Intでなのか変な値になる。
                    u = (u & 0xFF) - 128;

                    col[uvp++] = (int)(1634 * v);
                    col[uvp++] = -(int)((833 * v) + (400 * u));
                    col[uvp++] = (int)(2066 * u);
                }
            }

            //輝度情報は全pixel分あるので、そこに上であらかじめ計算しているカラーを乗せてBitmap化していく
            int p = 0;
            for(int dx = 0;dx < w;++dx)
            {
                int y = 1192 * ((ybuf.get() & 0xFF) - 16);

                int r = (y + col[p]);
                int g = (y + col[p+1]);
                int b = (y + col[p+2]);

                r = r < 0 ? 0 : r > 262143 ? 262143 : r;
                g = g < 0 ? 0 : g > 262143 ? 262143 : g;
                b = b < 0 ? 0 : b > 262143 ? 262143 : b;
                r >>= 10;
                g >>= 10;
                b >>= 10;

                bmp[bp++] = (0xFF000000 | ((r&0xFF)<<16) | ((g&0xFF)<<8) | ((b&0xFF)<<0));
                if((dx & 1) == 1) p += 3;
            }
        }

        return Bitmap.createBitmap(bmp, w , h, Bitmap.Config.ARGB_8888);
    }
```