Take Screenshot of SurfaceView

25,931

Solution 1

The SurfaceView's surface is independent of the surface on which View elements are drawn. So capturing the View contents won't include the SurfaceView.

You need to capture the SurfaceView contents separately and perform your own composition step. The easiest way to do the capture is probably to just re-render the contents, but use an off-screen bitmap as the target rather than the surface. If you're rendering with GLES to an off-screen pbuffer, you can use glReadPixels() before you swap buffers.

Update: Grafika's "texture from camera" activity demonstrates handling live video from the camera with OpenGL ES. EglSurfaceBase#saveFrame() shows how to capture GLES rendering to a Bitmap.

Update: See also this answer, which provides a bit more background.

Solution 2

Just copy and past code

Note: only For API level >= 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()));
    }

Solution 3

My situation related with ExoPlayer, need get bitmap of current frame.

Work on 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?)
}

Solution 4

This is how I do it. Put this method in some Util class

/**
 * 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()
    }
}

Usage:

usePixelCopy(videoView) { bitmap: Bitmap? ->
            processBitMp(bitmap)
        }

Note: Video View is a Subclass of SurfaceView so this method can take screenshot of video View as well

Solution 5

If your situation allows it, using a TextureView instead of a SurfaceView will make this problem a lot easier to solve. It has a method getBitmap() that returns a Bitmap of the current frame on the TextureView.

Share:
25,931

Related videos on Youtube

Donnie Ibiyemi
Author by

Donnie Ibiyemi

Updated on April 30, 2020

Comments

  • Donnie Ibiyemi
    Donnie Ibiyemi about 4 years

    Am developing a simple camera app. I have code that takes screenshot of the whole activity and writes it to the Sd card. the problem is that the Surfaceview returns a black screen.

    I would like to know how to independently take a screenshot of the surfaceview only. here is the code that takes the screenshot of the Whole activity.

        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);
        }
    }
    
    • urs32
      urs32 almost 6 years
      Yes, the last answer works for me, but >=API level 24
  • Donnie Ibiyemi
    Donnie Ibiyemi over 9 years
    Am an android newbie. please kindly post sample code on how to do that.thanks
  • fadden
    fadden over 9 years
    Assuming all of your rendering is done to a Canvas you get from the Surface, just create a Canvas backed by a Bitmap (see e.g. developer.android.com/guide/topics/graphics/2d-graphics.html‌​). Depending on how the surfaces are composed, you can either blit one onto the other, or composite the pair onto a third.
  • ocramot
    ocramot over 9 years
    Hi, the article you linked does not seem to cover the case of saving to bitmap a surfaceview showing a frame from a Camera . Do you have some sample code for us?
  • fadden
    fadden over 9 years
    The pixel data source doesn't matter -- once it's on the surface, it's just pixels.
  • fadden
    fadden over 9 years
    Added a link to Grafika.
  • user1767754
    user1767754 about 9 years
    ... code samples or "what is needed to do" would be interesting as well. +1 for the hint to grafika
  • ND1010_
    ND1010_ almost 6 years
    Yup This function will work for 24 and greater version Please see this:-->developer.android.com/reference/android/view/PixelCo‌​py
  • David
    David almost 6 years
    Any ideas below API 24? Because none of the answers worked for me.
  • ND1010_
    ND1010_ almost 6 years
    yes you can use MediaProjection class to capture the entire screen with a virtual display, but it can not provide any pacific area for capturing . only work for entire screen
  • ND1010_
    ND1010_ almost 6 years
    if you want to capture for a specified view you should have to use PixelCopy class
  • chia yongkang
    chia yongkang over 4 years
    why is the holder in holder.videoView an unresolved symbol?
  • ND1010_
    ND1010_ over 4 years
    @chiayongkang You have just need to pass your view instead of my holder.videoView
  • chia yongkang
    chia yongkang over 4 years
    @ND1010_ is there the code for the Screenshot.utils class for the store function? Curious to see how you store the bitmap as well.
  • chia yongkang
    chia yongkang over 4 years
    sometimes the object results in the image to have white parts. can it be solved with glReadPixels? if not how can i solve that?
  • chia yongkang
    chia yongkang over 4 years
    how would you use glReadPixels with surface view. It is kinda confusing, can you provide code sample?
  • Karthikkumar
    Karthikkumar over 3 years
    Hi @chiayongkang did you get any solution for this case. If you have please post here
  • Ayyappa
    Ayyappa over 2 years
    Any reason why Canvas canvas = new Canvas(bitmap); surfaceView.draw(canvas); won't work?