シンプルなカメラアプリを開発しています。アクティビティ全体のスクリーンショットを撮り、SDカードに書き込むコードがあります。問題は、Surfaceviewが黒い画面を返すことです。
サーフェスビューのみのスクリーンショットを個別に撮影する方法を知りたいのですが。これは、アクティビティ全体のスクリーンショットを撮るコードです。
findViewById(R.id.screen).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final RelativeLayout layout = (RelativeLayout) findViewById(R.id.RelativeLayout1);
layout.setVisibility(RelativeLayout.GONE);
Bitmap bitmap = takeScreenshot();
Toast.makeText(getApplicationContext(),"Please Wait", Toast.LENGTH_LONG).show();
saveBitmap(bitmap);
}
});
}
public Bitmap takeScreenshot() {
View rootView = findViewById(Android.R.id.content).getRootView();
rootView.setDrawingCacheEnabled(true);
return rootView.getDrawingCache();
}
public void saveBitmap(Bitmap bitmap) {
final MediaPlayer cheer = MediaPlayer.create(PicShot.this, R.raw.shutter);
cheer.start();
Random generator = new Random();
int n = 10000;
n = generator.nextInt(n);
String fname = "Image-"+ n +".png";
final RelativeLayout layout = (RelativeLayout) findViewById(R.id.RelativeLayout1);
File imagePath = new File(Environment.getExternalStorageDirectory() + "/" + fname);
FileOutputStream fos;
try {
fos = new FileOutputStream(imagePath);
bitmap.compress(CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
layout.setVisibility(RelativeLayout.VISIBLE);
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/*");
Uri uri = Uri.fromFile(imagePath);
share.putExtra(Intent.EXTRA_STREAM,uri);
startActivity(Intent.createChooser(share, "Share Image"));
} catch (FileNotFoundException e) {
Log.e("GREC", e.getMessage(), e);
} catch (IOException e) {
Log.e("GREC", e.getMessage(), e);
}
}
SurfaceViewのサーフェスは、View要素が描画されるサーフェスから独立しています。したがって、ViewコンテンツのキャプチャにはSurfaceViewは含まれません。
SurfaceViewのコンテンツを個別にキャプチャし、独自の構成手順を実行する必要があります。キャプチャを行う最も簡単な方法は、おそらくコンテンツを再レンダリングすることですが、ターゲットとして表面ではなくオフスクリーンビットマップを使用します。 GLESを使用して画面外のpbufferにレンダリングする場合は、バッファーを交換する前にglReadPixels()
を使用できます。
更新:Grafika's 「カメラからのテクスチャ」アクティビティは、OpenGLESを使用したカメラからのライブビデオの処理を示しています。 EglSurfaceBase#saveFrame()
は、GLESレンダリングをビットマップにキャプチャする方法を示しています。
更新:この回答 も参照してください。これはもう少し背景を提供します。
コードをコピーして貼り付けるだけ
注:APIレベル> = 24の場合のみ
private void takePhoto() {
// Create a bitmap the size of the scene view.
final Bitmap bitmap = Bitmap.createBitmap(surfaceView.getWidth(), surfaceView.getHeight(),
Bitmap.Config.ARGB_8888);
// Create a handler thread to offload the processing of the image.
final HandlerThread handlerThread = new HandlerThread("PixelCopier");
handlerThread.start();
// Make the request to copy.
PixelCopy.request(holder.videoView, bitmap, (copyResult) -> {
if (copyResult == PixelCopy.SUCCESS) {
Log.e(TAG,bitmap.toString());
String name = String.valueOf(System.currentTimeMillis() + ".jpg");
imageFile = ScreenshotUtils.store(bitmap,name);
} else {
Toast toast = Toast.makeText(getViewActivity(),
"Failed to copyPixels: " + copyResult, Toast.LENGTH_LONG);
toast.show();
}
handlerThread.quitSafely();
}, new Handler(handlerThread.getLooper()));
}
状況が許せば、TextureView
の代わりにSurfaceView
を使用すると、この問題の解決がはるかに簡単になります。これには、Bitmap
上の現在のフレームのTextureView
を返すメソッド getBitmap()
があります。
ExoPlayerに関連する私の状況では、現在のフレームのビットマップを取得する必要があります。
API> = 24で作業します。
private val copyFrameHandler = Handler()
fun getFrameBitmap(callback: FrameBitmapCallback) {
when(val view = videoSurfaceView) {
is TextureView -> callback.onResult(view.bitmap)
is SurfaceView -> {
val bitmap = Bitmap.createBitmap(
videoSurfaceView.getWidth(),
videoSurfaceView.getHeight(),
Bitmap.Config.ARGB_8888
)
copyFrameHandler.removeCallbacksAndMessages(null)
PixelCopy.request(view, bitmap, { copyResult: Int ->
if (copyResult == PixelCopy.SUCCESS) {
callback.onResult(bitmap)
} else {
callback.onResult(null)
}
}, copyFrameHandler)
}
else -> callback.onResult(null)
}
}
fun onDestroy() {
copyFrameHandler.removeCallbacksAndMessages(null)
}
interface FrameBitmapCallback {
fun onResult(bitmap: Bitmap?)
}