Сделайте скриншот 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() чтобы получить неподвижное изображение.

Другие вопросы по тегам