Android Surface View 截图显示黑屏
Android Take Screenshot of Surface View Shows Black Screen
我正在尝试通过代码截取我的游戏的屏幕截图并通过 Intent 分享。我可以做这些事情,但是屏幕截图总是显示为黑色。这是与共享屏幕截图相关的代码:
View view = MainActivity.getView();
view.setDrawingCacheEnabled(true);
Bitmap screen = Bitmap.createBitmap(view.getDrawingCache(true));
.. save Bitmap
这是在 MainActivity 中:
view = new GameView(this);
view.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.FILL_PARENT,
RelativeLayout.LayoutParams.FILL_PARENT));
public static SurfaceView getView() {
return view;
}
以及视图本身:
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private static SurfaceHolder surfaceHolder;
...etc
这就是我绘制所有内容的方式:
Canvas canvas = surfaceHolder.lockCanvas(null);
if (canvas != null) {
Game.draw(canvas);
...
好的,根据一些答案,我构建了这个:
public static void share() {
Bitmap screen = GameView.SavePixels(0, 0, Screen.width, Screen.height);
Calendar c = Calendar.getInstance();
Date d = c.getTime();
String path = Images.Media.insertImage(
Game.context.getContentResolver(), screen, "screenShotBJ" + d
+ ".png", null);
System.out.println(path + " PATH");
Uri screenshotUri = Uri.parse(path);
final Intent emailIntent = new Intent(
android.content.Intent.ACTION_SEND);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
emailIntent.setType("image/png");
Game.context.startActivity(Intent.createChooser(emailIntent,
"Share High Score:"));
}
游戏视图包含以下方法:
public static Bitmap SavePixels(int x, int y, int w, int h) {
EGL10 egl = (EGL10) EGLContext.getEGL();
GL10 gl = (GL10) egl.eglGetCurrentContext().getGL();
int b[] = new int[w * (y + h)];
int bt[] = new int[w * h];
IntBuffer ib = IntBuffer.wrap(b);
ib.position(0);
gl.glReadPixels(x, 0, w, y + h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);
for (int i = 0, k = 0; i < h; i++, k++) {
for (int j = 0; j < w; j++) {
int pix = b[i * w + j];
int pb = (pix >> 16) & 0xff;
int pr = (pix << 16) & 0x00ff0000;
int pix1 = (pix & 0xff00ff00) | pr | pb;
bt[(h - k - 1) * w + j] = pix1;
}
}
Bitmap sb = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
return sb;
}
屏幕截图仍然是黑色。是不是我保存的方式有问题?
我尝试了几种不同的方法来截取屏幕截图,但都没有奏效:
上面代码中显示的是最常建议的一种。但这似乎不起作用。
这是使用 SurfaceView 的问题吗?如果是这样,如果我不能使用它,为什么 view.getDrawingCache(true) 甚至存在,我该如何解决这个问题?
我的代码:
public static void share() {
// GIVES BLACK SCREENSHOT:
Calendar c = Calendar.getInstance();
Date d = c.getTime();
Game.update();
Bitmap.Config conf = Bitmap.Config.RGB_565;
Bitmap image = Bitmap.createBitmap(Screen.width, Screen.height, conf);
Canvas canvas = GameThread.surfaceHolder.lockCanvas(null);
canvas.setBitmap(image);
Paint backgroundPaint = new Paint();
backgroundPaint.setARGB(255, 40, 40, 40);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(),
backgroundPaint);
Game.draw(canvas);
Bitmap screen = Bitmap.createBitmap(image, 0, 0, Screen.width,
Screen.height);
canvas.setBitmap(null);
GameThread.surfaceHolder.unlockCanvasAndPost(canvas);
String path = Images.Media.insertImage(
Game.context.getContentResolver(), screen, "screenShotBJ" + d
+ ".png", null);
System.out.println(path + " PATH");
Uri screenshotUri = Uri.parse(path);
final Intent emailIntent = new Intent(
android.content.Intent.ACTION_SEND);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
emailIntent.setType("image/png");
Game.context.startActivity(Intent.createChooser(emailIntent,
"Share High Score:"));
}
谢谢。
这是因为SurfaceView使用OpenGL线程进行绘图,直接绘制到硬件缓冲区。您必须使用 glReadPixels(可能还有 GLWrapper)。
查看主题:Android OpenGL Screenshot
对此有很多困惑,还有一些correct answers。
这是交易:
SurfaceView 有两部分,Surface 和 View。 Surface 位于与所有 View UI 元素完全不同的层上。 getDrawingCache()
方法仅适用于视图层,因此它不会捕获表面上的任何内容。
缓冲队列有一个生产者-消费者API,它只能有一个生产者。 Canvas 是一个制作人,GLES 是另一个。您不能使用 Canvas 绘制并使用 GLES 读取像素。 (从技术上讲,如果 Canvas 使用 GLES 并且当您读取像素时正确的 EGL 上下文是最新的,您 可以 ,但这不能保证。Canvas在 Android 的任何发布版本中都没有加速到 Surface 的渲染,所以现在没有希望它能工作。)
(与您的情况无关,但为了完整起见我会提到它:)Surface 不是帧缓冲区,它是缓冲区队列。当您使用 GLES 提交缓冲区时,它就消失了,您无法再从中读取。因此,如果您使用 GLES 进行渲染并使用 GLES 进行捕获,则需要在调用 eglSwapBuffers()
.
之前读取像素
使用 Canvas 渲染,"capture" 表面内容的最简单方法是简单地绘制两次。创建一个屏幕大小的位图,从位图中创建一个 Canvas,并将其传递给您的 draw()
函数。
使用 GLES 渲染,您可以在缓冲区交换之前使用 glReadPixels()
来抓取像素。在 Grafika; see saveFrame()
in EglSurfaceBase.
中有一个(比问题中的代码便宜)抓取代码的实现
如果您将视频直接发送到 Surface(通过 MediaPlayer),则无法捕获帧,因为您的应用永远无法访问它们——它们直接从媒体服务器发送到合成器 (SurfaceFlinger)。但是,您可以通过 SurfaceTexture 路由传入的帧,并从您的应用程序中渲染它们两次,一次用于显示,一次用于捕获。有关详细信息,请参阅 this question。
一种替代方法是将 SurfaceView 替换为 TextureView,它可以像任何其他 Surface 一样在其上绘制。然后,您可以使用 getBitmap()
调用之一来捕获帧。 TextureView 的效率低于 SurfaceView,因此不建议在所有情况下都这样做,但这样做很简单。
如果您希望获得包含 Surface 内容和 View UI 内容的复合屏幕截图,您将需要像上面那样捕获 Canvas,使用通常的方法捕获 View绘图缓存技巧,然后手动将两者合成。请注意,这不会拾取系统部件(状态栏、导航栏)。
更新: 在 Lollipop 及更高版本 (API 21+) 上,您可以使用 MediaProjection class 来捕获整个屏幕虚拟显示器。这种方法有一些权衡,例如您正在捕获渲染的屏幕,而不是发送到 Surface 的帧,因此您得到的可能已经按比例放大或缩小以适合 window。此外,此方法涉及一个 Activity 开关,因为您必须创建一个意图(通过在 ProjectionManager 对象上调用 createScreenCaptureIntent)并等待其结果。
如果您想了解更多有关这些东西如何工作的信息,请参阅 Android System-Level Graphics Architecture 文档。
我知道回复晚了,但对于那些面临同样问题的人,
我们可以使用 PixelCopy 来获取快照。它适用于 API level 24
及
以上版本
PixelCopy.request(surfaceViewObject,BitmapDest,listener,new Handler());
其中,
surfaceViewObject为surface view的对象
BitmapDest 是保存图片的位图对象,不能为空
侦听器是 OnPixelCopyFinishedListener
有关更多信息,请参阅 - https://developer.android.com/reference/android/view/PixelCopy
2020 年更新 view.setDrawingCacheEnabled(true) 在 API 28
中已弃用
如果您使用的是普通视图,那么您可以创建一个canvas,其中包含要绘制到的指定位图。然后要求视图绘制由 Canvas
填充的 canvas 和 return 位图
/**
* Copy View to Canvas and return bitMap
*/
fun getBitmapFromView(view: View): Bitmap? {
var bitmap =
Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
view.draw(canvas)
return bitmap
}
或者您可以在使用视图绘制之前用默认颜色填充 canvas:
/**
* Copy View to Canvas and return bitMap and fill it with default color
*/
fun getBitmapFromView(view: View, defaultColor: Int): Bitmap? {
var bitmap =
Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
var canvas = Canvas(bitmap)
canvas.drawColor(defaultColor)
view.draw(canvas)
return bitmap
}
上述方法不适用于他们将在屏幕截图中绘制为孔的表面视图。
从Android 24开始的表面视图,您需要使用像素复制。
/**
* 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()
}
}
这种方法可以对Surface Vie的任何子类进行截图,例如VideoView
Screenshot.usePixelCopy(videoView) { bitmap: Bitmap? ->
processBitMap(bitmap)
}
这是使用 PixelCopy 拍摄表面视图屏幕截图的完整方法。它需要 API 24(Android N).
@RequiresApi(api = Build.VERSION_CODES.N)
private void capturePicture() {
Bitmap bmp = Bitmap.createBitmap(surfaceView.getWidth(), surfaceView.getHeight(), Bitmap.Config.ARGB_8888);
PixelCopy.request(surfaceView, bmp, i -> {
imageView.setImageBitmap(bmp); //"iv_Result" is the image view
}, new Handler(Looper.getMainLooper()));
}
我正在尝试通过代码截取我的游戏的屏幕截图并通过 Intent 分享。我可以做这些事情,但是屏幕截图总是显示为黑色。这是与共享屏幕截图相关的代码:
View view = MainActivity.getView();
view.setDrawingCacheEnabled(true);
Bitmap screen = Bitmap.createBitmap(view.getDrawingCache(true));
.. save Bitmap
这是在 MainActivity 中:
view = new GameView(this);
view.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.FILL_PARENT,
RelativeLayout.LayoutParams.FILL_PARENT));
public static SurfaceView getView() {
return view;
}
以及视图本身:
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private static SurfaceHolder surfaceHolder;
...etc
这就是我绘制所有内容的方式:
Canvas canvas = surfaceHolder.lockCanvas(null);
if (canvas != null) {
Game.draw(canvas);
...
好的,根据一些答案,我构建了这个:
public static void share() {
Bitmap screen = GameView.SavePixels(0, 0, Screen.width, Screen.height);
Calendar c = Calendar.getInstance();
Date d = c.getTime();
String path = Images.Media.insertImage(
Game.context.getContentResolver(), screen, "screenShotBJ" + d
+ ".png", null);
System.out.println(path + " PATH");
Uri screenshotUri = Uri.parse(path);
final Intent emailIntent = new Intent(
android.content.Intent.ACTION_SEND);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
emailIntent.setType("image/png");
Game.context.startActivity(Intent.createChooser(emailIntent,
"Share High Score:"));
}
游戏视图包含以下方法:
public static Bitmap SavePixels(int x, int y, int w, int h) {
EGL10 egl = (EGL10) EGLContext.getEGL();
GL10 gl = (GL10) egl.eglGetCurrentContext().getGL();
int b[] = new int[w * (y + h)];
int bt[] = new int[w * h];
IntBuffer ib = IntBuffer.wrap(b);
ib.position(0);
gl.glReadPixels(x, 0, w, y + h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);
for (int i = 0, k = 0; i < h; i++, k++) {
for (int j = 0; j < w; j++) {
int pix = b[i * w + j];
int pb = (pix >> 16) & 0xff;
int pr = (pix << 16) & 0x00ff0000;
int pix1 = (pix & 0xff00ff00) | pr | pb;
bt[(h - k - 1) * w + j] = pix1;
}
}
Bitmap sb = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
return sb;
}
屏幕截图仍然是黑色。是不是我保存的方式有问题?
我尝试了几种不同的方法来截取屏幕截图,但都没有奏效: 上面代码中显示的是最常建议的一种。但这似乎不起作用。 这是使用 SurfaceView 的问题吗?如果是这样,如果我不能使用它,为什么 view.getDrawingCache(true) 甚至存在,我该如何解决这个问题?
我的代码:
public static void share() {
// GIVES BLACK SCREENSHOT:
Calendar c = Calendar.getInstance();
Date d = c.getTime();
Game.update();
Bitmap.Config conf = Bitmap.Config.RGB_565;
Bitmap image = Bitmap.createBitmap(Screen.width, Screen.height, conf);
Canvas canvas = GameThread.surfaceHolder.lockCanvas(null);
canvas.setBitmap(image);
Paint backgroundPaint = new Paint();
backgroundPaint.setARGB(255, 40, 40, 40);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(),
backgroundPaint);
Game.draw(canvas);
Bitmap screen = Bitmap.createBitmap(image, 0, 0, Screen.width,
Screen.height);
canvas.setBitmap(null);
GameThread.surfaceHolder.unlockCanvasAndPost(canvas);
String path = Images.Media.insertImage(
Game.context.getContentResolver(), screen, "screenShotBJ" + d
+ ".png", null);
System.out.println(path + " PATH");
Uri screenshotUri = Uri.parse(path);
final Intent emailIntent = new Intent(
android.content.Intent.ACTION_SEND);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
emailIntent.setType("image/png");
Game.context.startActivity(Intent.createChooser(emailIntent,
"Share High Score:"));
}
谢谢。
这是因为SurfaceView使用OpenGL线程进行绘图,直接绘制到硬件缓冲区。您必须使用 glReadPixels(可能还有 GLWrapper)。
查看主题:Android OpenGL Screenshot
对此有很多困惑,还有一些correct answers。
这是交易:
SurfaceView 有两部分,Surface 和 View。 Surface 位于与所有 View UI 元素完全不同的层上。
getDrawingCache()
方法仅适用于视图层,因此它不会捕获表面上的任何内容。缓冲队列有一个生产者-消费者API,它只能有一个生产者。 Canvas 是一个制作人,GLES 是另一个。您不能使用 Canvas 绘制并使用 GLES 读取像素。 (从技术上讲,如果 Canvas 使用 GLES 并且当您读取像素时正确的 EGL 上下文是最新的,您 可以 ,但这不能保证。Canvas在 Android 的任何发布版本中都没有加速到 Surface 的渲染,所以现在没有希望它能工作。)
(与您的情况无关,但为了完整起见我会提到它:)Surface 不是帧缓冲区,它是缓冲区队列。当您使用 GLES 提交缓冲区时,它就消失了,您无法再从中读取。因此,如果您使用 GLES 进行渲染并使用 GLES 进行捕获,则需要在调用
eglSwapBuffers()
. 之前读取像素
使用 Canvas 渲染,"capture" 表面内容的最简单方法是简单地绘制两次。创建一个屏幕大小的位图,从位图中创建一个 Canvas,并将其传递给您的 draw()
函数。
使用 GLES 渲染,您可以在缓冲区交换之前使用 glReadPixels()
来抓取像素。在 Grafika; see saveFrame()
in EglSurfaceBase.
如果您将视频直接发送到 Surface(通过 MediaPlayer),则无法捕获帧,因为您的应用永远无法访问它们——它们直接从媒体服务器发送到合成器 (SurfaceFlinger)。但是,您可以通过 SurfaceTexture 路由传入的帧,并从您的应用程序中渲染它们两次,一次用于显示,一次用于捕获。有关详细信息,请参阅 this question。
一种替代方法是将 SurfaceView 替换为 TextureView,它可以像任何其他 Surface 一样在其上绘制。然后,您可以使用 getBitmap()
调用之一来捕获帧。 TextureView 的效率低于 SurfaceView,因此不建议在所有情况下都这样做,但这样做很简单。
如果您希望获得包含 Surface 内容和 View UI 内容的复合屏幕截图,您将需要像上面那样捕获 Canvas,使用通常的方法捕获 View绘图缓存技巧,然后手动将两者合成。请注意,这不会拾取系统部件(状态栏、导航栏)。
更新: 在 Lollipop 及更高版本 (API 21+) 上,您可以使用 MediaProjection class 来捕获整个屏幕虚拟显示器。这种方法有一些权衡,例如您正在捕获渲染的屏幕,而不是发送到 Surface 的帧,因此您得到的可能已经按比例放大或缩小以适合 window。此外,此方法涉及一个 Activity 开关,因为您必须创建一个意图(通过在 ProjectionManager 对象上调用 createScreenCaptureIntent)并等待其结果。
如果您想了解更多有关这些东西如何工作的信息,请参阅 Android System-Level Graphics Architecture 文档。
我知道回复晚了,但对于那些面临同样问题的人,
我们可以使用 PixelCopy 来获取快照。它适用于 API level 24
及
PixelCopy.request(surfaceViewObject,BitmapDest,listener,new Handler());
其中,
surfaceViewObject为surface view的对象
BitmapDest 是保存图片的位图对象,不能为空
侦听器是 OnPixelCopyFinishedListener
有关更多信息,请参阅 - https://developer.android.com/reference/android/view/PixelCopy
2020 年更新 view.setDrawingCacheEnabled(true) 在 API 28
中已弃用如果您使用的是普通视图,那么您可以创建一个canvas,其中包含要绘制到的指定位图。然后要求视图绘制由 Canvas
填充的 canvas 和 return 位图 /**
* Copy View to Canvas and return bitMap
*/
fun getBitmapFromView(view: View): Bitmap? {
var bitmap =
Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
view.draw(canvas)
return bitmap
}
或者您可以在使用视图绘制之前用默认颜色填充 canvas:
/**
* Copy View to Canvas and return bitMap and fill it with default color
*/
fun getBitmapFromView(view: View, defaultColor: Int): Bitmap? {
var bitmap =
Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
var canvas = Canvas(bitmap)
canvas.drawColor(defaultColor)
view.draw(canvas)
return bitmap
}
上述方法不适用于他们将在屏幕截图中绘制为孔的表面视图。
从Android 24开始的表面视图,您需要使用像素复制。
/**
* 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()
}
}
这种方法可以对Surface Vie的任何子类进行截图,例如VideoView
Screenshot.usePixelCopy(videoView) { bitmap: Bitmap? ->
processBitMap(bitmap)
}
这是使用 PixelCopy 拍摄表面视图屏幕截图的完整方法。它需要 API 24(Android N).
@RequiresApi(api = Build.VERSION_CODES.N)
private void capturePicture() {
Bitmap bmp = Bitmap.createBitmap(surfaceView.getWidth(), surfaceView.getHeight(), Bitmap.Config.ARGB_8888);
PixelCopy.request(surfaceView, bmp, i -> {
imageView.setImageBitmap(bmp); //"iv_Result" is the image view
}, new Handler(Looper.getMainLooper()));
}