Android Take Screenshot of Surface View Shows Black Screen

Android Take Screenshot of Surface View Shows Black Screen

There is a great deal of confusion about this, and a few correct answers.

Here's the deal:

  1. A SurfaceView has two parts, the Surface and the View. The Surface is on a completely separate layer from all of the View UI elements. The getDrawingCache() approach works on the View layer only, so it doesn't capture anything on the Surface.

  2. The buffer queue has a producer-consumer API, and it can have only one producer. Canvas is one producer, GLES is another. You can't draw with Canvas and read pixels with GLES. (Technically, you could if the Canvas were using GLES and the correct EGL context was current when you went to read the pixels, but that's not guaranteed. Canvas rendering to a Surface is not accelerated in any released version of Android, so right now there's no hope of it working.)

  3. (Not relevant for your case, but I'll mention it for completeness:) A Surface is not a frame buffer, it is a queue of buffers. When you submit a buffer with GLES, it is gone, and you can no longer read from it. So if you were rendering with GLES and capturing with GLES, you would need to read the pixels back before calling eglSwapBuffers().

With Canvas rendering, the easiest way to "capture" the Surface contents is to simply draw it twice. Create a screen-sized Bitmap, create a Canvas from the Bitmap, and pass it to your draw() function.

With GLES rendering, you can use glReadPixels() before the buffer swap to grab the pixels. There's a (less-expensive than the code in the question) implementation of the grab code in Grafika; see saveFrame() in EglSurfaceBase.

If you were sending video directly to a Surface (via MediaPlayer) there would be no way to capture the frames, because your app never has access to them -- they go directly from mediaserver to the compositor (SurfaceFlinger). You can, however, route the incoming frames through a SurfaceTexture, and render them twice from your app, once for display and once for capture. See this question for more info.

One alternative is to replace the SurfaceView with a TextureView, which can be drawn on like any other Surface. You can then use one of the getBitmap() calls to capture a frame. TextureView is less efficient than SurfaceView, so this is not recommended for all situations, but it's straightforward to do.

If you were hoping to get a composite screen shot containing both the Surface contents and the View UI contents, you will need to capture the Canvas as above, capture the View with the usual drawing cache trick, and then composite the two manually. Note this won't pick up the system parts (status bar, nav bar).

Update: on Lollipop and later (API 21+) you can use the MediaProjection class to capture the entire screen with a virtual display. There are some trade-offs with this approach, e.g. you're capturing the rendered screen, not the frame that was sent to the Surface, so what you get may have been up- or down-scaled to fit the window. In addition, this approach involves an Activity switch since you have to create an intent (by calling createScreenCaptureIntent on the ProjectionManager object) and wait for its result.

If you want to learn more about how all this stuff works, see the Android System-Level Graphics Architecture doc.

Surface view shows black screen

That's because you trying to instantiate new SnakeEngine, but you should find an instance of it by id because you already add it to XML file:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// snakeEngine = new SnakeEngine(this);
setContentView(R.layout.activity_main);
snakeEngine = findViewById(R.id.surfaceView);
}

Then, when you will fix it, you should move all your initiating logic from public SnakeEngine(Context context) constructor to separate method and call it from each constructor of SnakeEngine:

public SnakeEngine(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public SnakeEngine(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}

public SnakeEngine(Context context) {
super(context);
init(context);
}

private void init(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = null;
if (wm != null) {
display = wm.getDefaultDisplay();
}
Point size = new Point();
if (display != null) {
display.getSize(size);
}
screenX = size.x;
screenY = size.y;

surfaceHolder = getHolder();
setOnTouchListener(this);
paint = new Paint();
paint.setColor(Color.GREEN);
snakeXs = new int[200];
snakeYs = new int[200];
}

Taking screenshot programmatically doesnt capture the contents of surfaceVIew

I finally solved this . Below i give some code for anyone who wants to know how to take screenshots of a layout ,pictures from the camera without intent, screenshots(sort of) of the content of a surfaceView and save the screen shot in a folder :

public class Cam_View extends Activity implements SurfaceHolder.Callback {

protected static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 0;
private SurfaceView SurView;
private SurfaceHolder camHolder;
private boolean previewRunning;
final Context context = this;
public static Camera camera = null;
private RelativeLayout CamView;
private Bitmap inputBMP = null, bmp, bmp1;
private ImageView mImage;

@SuppressWarnings("deprecation")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera);

CamView = (RelativeLayout) findViewById(R.id.camview);//RELATIVELAYOUT OR
//ANY LAYOUT OF YOUR XML

SurView = (SurfaceView)findViewById(R.id.sview);//SURFACEVIEW FOR THE PREVIEW
//OF THE CAMERA FEED
camHolder = SurView.getHolder(); //NEEDED FOR THE PREVIEW
camHolder.addCallback(this); //NEEDED FOR THE PREVIEW
camHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//NEEDED FOR THE PREVIEW
camera_image = (ImageView) findViewById(R.id.camera_image);//NEEDED FOR THE PREVIEW

Button btn = (Button) findViewById(R.id.button1); //THE BUTTON FOR TAKING PICTURE

btn.setOnClickListener(new OnClickListener() { //THE BUTTON CODE
public void onClick(View v) {
camera.takePicture(null, null, mPicture);//TAKING THE PICTURE
//THE mPicture IS CALLED
//WHICH IS THE LAST METHOD(SEE BELOW)
}
});
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,//NEEDED FOR THE PREVIEW
int height) {
if(previewRunning) {
camera.stopPreview();
}
Camera.Parameters camParams = camera.getParameters();
Camera.Size size = camParams.getSupportedPreviewSizes().get(0);
camParams.setPreviewSize(size.width, size.height);
camera.setParameters(camParams);
try {
camera.setPreviewDisplay(holder);
camera.startPreview();
previewRunning=true;
} catch(IOException e) {
e.printStackTrace();
}
}

public void surfaceCreated(SurfaceHolder holder) { //NEEDED FOR THE PREVIEW
try {
camera=Camera.open();
} catch(Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(),"Error",Toast.LENGTH_LONG).show();
finish();
}
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) { //NEEDED FOR THE PREVIEW
camera.stopPreview();
camera.release();
camera=null;
}

public void TakeScreenshot(){ //THIS METHOD TAKES A SCREENSHOT AND SAVES IT AS .jpg
Random num = new Random();
int nu=num.nextInt(1000); //PRODUCING A RANDOM NUMBER FOR FILE NAME
CamView.setDrawingCacheEnabled(true); //CamView OR THE NAME OF YOUR LAYOUR
CamView.buildDrawingCache(true);
Bitmap bmp = Bitmap.createBitmap(CamView.getDrawingCache());
CamView.setDrawingCacheEnabled(false); // clear drawing cache
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bmp.compress(CompressFormat.JPEG, 100, bos);
byte[] bitmapdata = bos.toByteArray();
ByteArrayInputStream fis = new ByteArrayInputStream(bitmapdata);

String picId=String.valueOf(nu);
String myfile="Ghost"+picId+".jpeg";

File dir_image = new File(Environment.getExternalStorageDirectory()+//<---
File.separator+"Ultimate Entity Detector"); //<---
dir_image.mkdirs(); //<---
//^IN THESE 3 LINES YOU SET THE FOLDER PATH/NAME . HERE I CHOOSE TO SAVE
//THE FILE IN THE SD CARD IN THE FOLDER "Ultimate Entity Detector"

try {
File tmpFile = new File(dir_image,myfile);
FileOutputStream fos = new FileOutputStream(tmpFile);

byte[] buf = new byte[1024];
int len;
while ((len = fis.read(buf)) > 0) {
fos.write(buf, 0, len);
}
fis.close();
fos.close();
Toast.makeText(getApplicationContext(),
"The file is saved at :SD/Ultimate Entity Detector",Toast.LENGTH_LONG).show();
bmp1 = null;
camera_image.setImageBitmap(bmp1); //RESETING THE PREVIEW
camera.startPreview(); //RESETING THE PREVIEW
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

private PictureCallback mPicture = new PictureCallback() { //THIS METHOD AND THE METHOD BELOW
//CONVERT THE CAPTURED IMAGE IN A JPG FILE AND SAVE IT

@Override
public void onPictureTaken(byte[] data, Camera camera) {

File dir_image2 = new File(Environment.getExternalStorageDirectory()+
File.separator+"Ultimate Entity Detector");
dir_image2.mkdirs(); //AGAIN CHOOSING FOLDER FOR THE PICTURE(WHICH IS LIKE A SURFACEVIEW
//SCREENSHOT)

File tmpFile = new File(dir_image2,"TempGhost.jpg"); //MAKING A FILE IN THE PATH
//dir_image2(SEE RIGHT ABOVE) AND NAMING IT "TempGhost.jpg" OR ANYTHING ELSE
try { //SAVING
FileOutputStream fos = new FileOutputStream(tmpFile);
fos.write(data);
fos.close();
//grabImage();
} catch (FileNotFoundException e) {
Toast.makeText(getApplicationContext(),"Error",Toast.LENGTH_LONG).show();
} catch (IOException e) {
Toast.makeText(getApplicationContext(),"Error",Toast.LENGTH_LONG).show();
}

String path = (Environment.getExternalStorageDirectory()+
File.separator+"Ultimate EntityDetector"+
File.separator+"TempGhost.jpg");//<---

BitmapFactory.Options options = new BitmapFactory.Options();//<---
options.inPreferredConfig = Bitmap.Config.ARGB_8888;//<---
bmp1 = BitmapFactory.decodeFile(path, options);//<--- *********(SEE BELOW)
//THE LINES ABOVE READ THE FILE WE SAVED BEFORE AND CONVERT IT INTO A BitMap
camera_image.setImageBitmap(bmp1); //SETTING THE BitMap AS IMAGE IN AN IMAGEVIEW(SOMETHING
//LIKE A BACKGROUNG FOR THE LAYOUT)

tmpFile.delete();
TakeScreenshot();//CALLING THIS METHOD TO TAKE A SCREENSHOT
//********* THAT LINE MIGHT CAUSE A CRASH ON SOME PHONES (LIKE XPERIA T)<----(SEE HERE)
//IF THAT HAPPENDS USE THE LINE "bmp1 =decodeFile(tmpFile);" WITH THE METHOD BELOW

}
};

public Bitmap decodeFile(File f) { //FUNCTION BY Arshad Parwez
Bitmap b = null;
try {
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;

FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
int IMAGE_MAX_SIZE = 1000;
int scale = 1;
if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
scale = (int) Math.pow(
2,
(int) Math.round(Math.log(IMAGE_MAX_SIZE
/ (double) Math.max(o.outHeight, o.outWidth))
/ Math.log(0.5)));
}

// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
return b;
}
}

If you want to take a simple screenshot(no camera feed is needed) the you can use the TakeScreenshot method alone.

If you want to take a screenshot of a surfaceView with is not possible to do from the surfaceview directly the use the mPicture, set the picture you capture as backgroung , and then call TakeScreenshot to take your screenshot.(as seen above)

If you want to take a picture with the camera without calling an other camera app with an intent the use the takePicture with the mPicture and the surfaceView stuff from the code above.

What the previous code does if used "as is" is to take a screenshot of the layout contents(buttons,imageviews etc) and set as backgroung an image from the camera.

Below i also provide a basic layout xml for the previous code :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/camview">

<SurfaceView
android:id="@+id/sview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />

<ImageView
android:id="@+id/camera_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/app_name" />



<Button
android:id="@+id/button1"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />


</RelativeLayout>

Dont forget to import what needs to be imported

SurfaceView shows black screen except icon

You gave black color in drawSomething method, If you change the color your background will change.

protected void drawSomething(Canvas canvas) {
//canvas.drawColor(Color.BLACK);
canvas.drawColor(Color.RED); // here i changed black to red(add you expected color here)
if (bmpIcon != null) {
canvas.drawBitmap(bmpIcon,
getWidth() / 2, getHeight() / 2, null);
}

}

As your drawble is vector drawable so use the following function to convert into bitmap

public static Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) {
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
drawable = (DrawableCompat.wrap(drawable)).mutate();
}

Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);

return bitmap;
}

and create your bitmap like this

bmpIcon = getBitmapFromVectorDrawable(getContext(), R.drawable.icon);

it will work fine.

and your final code will be like this,

public class MySurface extends SurfaceView {

private SurfaceHolder surfaceHolder;
private Bitmap bmpIcon;

public MySurface(Context context) {
super(context);
init();
}

public MySurface(Context context,
AttributeSet attrs) {
super(context, attrs);
init();
}

public MySurface(Context context,
AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}

public static Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) {
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
drawable = (DrawableCompat.wrap(drawable)).mutate();
}

Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);

return bitmap;
}

private void init() {
surfaceHolder = getHolder();

bmpIcon = getBitmapFromVectorDrawable(getContext(), R.drawable.icon);
surfaceHolder.addCallback(new SurfaceHolder.Callback() {

@Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas canvas = holder.lockCanvas(null);
drawSomething(canvas);
holder.unlockCanvasAndPost(canvas);
}

@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
// TODO Auto-generated method stub

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub

}
});
}

protected void drawSomething(Canvas canvas) {
canvas.drawColor(Color.RED);
if (bmpIcon != null) {
canvas.drawBitmap(bmpIcon,
getWidth() / 2, getHeight() / 2, null);
}

}

}

Screenshot



Related Topics



Leave a reply



Submit