IT関連雑記帳

IT関連の話をつらつらと

スクリーンショットの画像サイズがおかしかった件

Androidの技術書を読み終わったので、5月の末から週末を利用してコツコツとアプリの開発に取り掛かっています。

作りたいのはスクリーンショットを撮影するアプリです。

ボタンを押したらスクリーンショットを撮影して保存するところまで作ったのですが、自分のXperiaで動作を確認したらスクリーンショットのサイズがおかしい……。

プログラムはこんな感じ。長くなりすぎるので、ところどころ省略しています。

DisplayMetrics metrics = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getRealMetrics(metrics);
mScreenDensity = (int) metrics.density;
mDisplayWidth = metrics.widthPixels;
mDisplayHeight = metrics.heightPixels;

mImageReader = ImageReader.newInstance(mDisplayWidth, mDisplayHeight, PixelFormat.RGBA_8888, 2);

mVirtualDisplay = mMediaProjection.createVirtualDisplay("Screenshot",
        mDisplayWidth, mDisplayHeight, mScreenDensity,
        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
        mImageReader.getSurface(), null, null);


画面のサイズを取得して、ImageReader、createVirtualDisplay()を使用。
この時点で、

mScreenDensity = 3
mDisplayWidth = 1080
mDisplayHeight = 1920

が入っていました。その後、取得したImageを元にcreateBitmap()を実施します。

Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();

int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * mDisplayWidth;

// バッファからBitmapを生成する
Bitmap bitmap = Bitmap.createBitmap(mDisplayWidth + rowPadding / pixelStride, mDisplayHeight, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);


この時点での値はこんな感じ。

pixelStride = 4
rowStride = 4352
rowPadding = 32

というものが入っていました。この値をセットして画像ファイルに落とし込むと、1088 x 1920の画像サイズとなり、以下のように右側に8バイトのゴミが入っているように見えました。


f:id:ta9mi3:20200719201254j:plain:w320


いまいち理解に苦しむのは、画面中央よりやや右側の画像が表示されているっぽいところ。考えても仕方ないとは思いますが、なんなんでしょうか。

createBitmapの第1引数を以下のように変更し、1080を指定してみました。

// バッファからBitmapを生成する
Bitmap bitmap = Bitmap.createBitmap(mDisplayWidth, mDisplayHeight, Bitmap.Config.ARGB_8888);


そうしたらこんな画像になりました。


f:id:ta9mi3:20200719201859j:plain:w320


スクランブルかよ(笑)


APIの呼び出し方が悪いのではないかと思い、いろいろ試しましたがどうしても直らず。仕方が無いのでImageReaderで取得した画像のうち、paddingと思われるところを除外したバイト列を作成し、それをファイルに落とし込むという対応をしました。


f:id:ta9mi3:20200719202358j:plain:w320


いちおうゴミは無くなり、1080 x 1920のJPEG画像が作成されるようになりましたが、この対応でいいんでしょうか? 誰か原因を知っていたら教えて欲しいです。

ちなみに余計なpaddingを消す処理はこんな感じ。1080バイトを取得し、残りの8バイトを捨て、また1080バイトを取り出してという処理。ARGBなので各サイズは4倍で計算しています。

byte[] tempJPEGByte = new byte[mDisplayWidth * 4];
byte[] tempJPEGByte2 = new byte[buffer.limit()];

// イメージから余計なPaddingを消す
int destPos = 0;
int deleteSize = rowPadding / pixelStride;
long imageSize = buffer.limit();

for (int counter = 0; counter < imageSize;){
    buffer.position(counter);
    buffer.get(tempJPEGByte, 0, mDisplayWidth * 4);
    System.arraycopy(tempJPEGByte, 0, tempJPEGByte2, destPos, mDisplayWidth * 4);
    counter += (mDisplayWidth + deleteSize) * 4;
    destPos += mDisplayWidth * 4;
}

// バッファからBitmapを生成する
Bitmap bitmap = Bitmap.createBitmap(mDisplayWidth, mDisplayHeight, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(tempJPEGByte2));


もっときれいな書き方があるような気がしなくも無いですが、よわよわエンジニアなのでご容赦を。