Сделайте скриншот SurfaceView
Разрабатываю простое приложение для камеры. У меня есть код, который делает скриншот всей активности и записывает его на 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);
}
}
3 ответа
Поверхность SurfaceView не зависит от поверхности, на которой нарисованы элементы View. Таким образом, захват содержимого View не будет включать SurfaceView.
Вам нужно захватить содержимое SurfaceView отдельно и выполнить свой собственный шаг композиции. Самый простой способ сделать захват, вероятно, состоит в том, чтобы просто повторно визуализировать содержимое, но использовать закадровое растровое изображение в качестве цели, а не поверхности. Если вы выполняете рендеринг с GLES за пределы экрана, вы можете использовать glReadPixels()
прежде чем поменять буфер.
Обновление: действие Grafika "текстура с камеры" демонстрирует обработку живого видео с камеры с помощью OpenGL ES. 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()));
}
Моя ситуация, связанная с 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?)
}
Если ваша ситуация позволяет это, используя TextureView
вместо SurfaceView
сделает эту проблему намного проще решить. У него есть метод getBitmap()
который возвращает Bitmap
текущего кадра на TextureView
,
Вот как я это делаю. Поместите этот метод в какой-нибудь класс Util
/**
* Pixel copy to copy SurfaceView/VideoView into BitMap
*/
fun usePixelCopy(videoView: SurfaceView, callback: (Bitmap?) -> Unit) {
val bitmap: Bitmap = Bitmap.createBitmap(
videoView.width,
videoView.height,
Bitmap.Config.ARGB_8888
);
try {
// Create a handler thread to offload the processing of the image.
val handlerThread = HandlerThread("PixelCopier");
handlerThread.start();
PixelCopy.request(
videoView, bitmap,
PixelCopy.OnPixelCopyFinishedListener { copyResult ->
if (copyResult == PixelCopy.SUCCESS) {
callback(bitmap)
}
handlerThread.quitSafely();
},
Handler(handlerThread.looper)
)
} catch (e: IllegalArgumentException) {
callback(null)
// PixelCopy may throw IllegalArgumentException, make sure to handle it
e.printStackTrace()
}
}
Использование:
usePixelCopy(videoView) { bitmap: Bitmap? ->
processBitMp(bitmap)
}
Примечание: Video View является подклассом SurfaceView, поэтому этот метод также может делать снимки экрана с видео View.
Для тех из нас, кто ищет решение для API <= 24, это то, что я сделал в качестве обходного пути. Когда пользователь нажимает кнопку для захвата, закройте предварительный просмотр captureSession и создайте новый captureSession для захвата неподвижного изображения.
mCaptureSession.stopRepeating();
mCaptureSession.abortCaptures();
mCaptureSession.close();
mCaptureSession = null;
mPreviewReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.JPEG, MAX_IMAGES);
mPreviewReader.setOnImageAvailableListener(mImageAvailableListener, mBackgroundHandler);
mCameraDevice.createCaptureSession(
Arrays.asList(mPreviewReader.getSurface()),
//Arrays.asList(mSurface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
mCaptureSession = cameraCaptureSession;
try {
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mPreviewReader.getSurface());
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(mOrientation));
Log.d(TAG, "Capture request created.");
mCaptureSession.capture(captureBuilder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException cae) {
Log.d(TAG, cae.toString());
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
}
},
mBackgroundHandler
);
Затем в onImageAvailableListener вы можете получить изображение с помощью imageReader.acquireNextImage()
чтобы получить неподвижное изображение.