java.lang.IllegalStateException: Software rendering doesn't support hardware bitmaps

I need to get the screenshot of a View. I have tried two methods to do this work. Unfortunately, both result in the same bug.

Here is the log:

java.lang.IllegalArgumentException: Software rendering doesn't support hardware bitmaps at android.graphics.BaseCanvas.null onHwBitmapInSwMode(null)(BaseCanvas.java:550) at android.graphics.BaseCanvas.null throwIfHwBitmapInSwMode(null)(BaseCanvas.java:557) at android.graphics.BaseCanvas.null throwIfCannotDraw(null)(BaseCanvas.java:69) at android.graphics.BaseCanvas.null drawBitmap(null)(BaseCanvas.java:127) at android.graphics.Canvas.null drawBitmap(null)(Canvas.java:1504) at android.graphics.drawable.BitmapDrawable.null draw(null)(BitmapDrawable.java:545) at android.widget.ImageView.null onDraw(null)(ImageView.java:1355) at android.view.View.null draw(null)(View.java:20248) at android.view.View.null draw(null)(View.java:20118) at android.view.ViewGroup.null drawChild(null)(ViewGroup.java:4336) at android.view.ViewGroup.null dispatchDraw(null)(ViewGroup.java:4115) at android.view.ViewOverlay$OverlayViewGroup.null dispatchDraw(null)(ViewOverlay.java:251) at android.view.View.null draw(null)(View.java:20251) at android.view.View.null buildDrawingCacheImpl(null)(View.java:19516) at android.view.View.null buildDrawingCache(null)(View.java:19379) at android.view.View.null getDrawingCache(null)(View.java:19215) at android.view.View.null getDrawingCache(null)(View.java:19166) at com.omnipotent.free.videodownloader.pro.utils.ViewUtils.android.graphics.Bitmap captureView(android.view.View)(ViewUtils.java:70) at com.omnipotent.free.videodownloader.pro.ui.main.MainActivity.com.omnipotent.free.videodownloader.pro.data.bean.TabBean getCurrentTabsData()(MainActivity.java:325) at com.omnipotent.free.videodownloader.pro.ui.main.MainActivity.com.omnipotent.free.videodownloader.pro.data.bean.TabBean access$getCurrentTabsData(com.omnipotent.free.videodownloader.pro.ui.main.MainActivity)(MainActivity.java:84) at com.omnipotent.free.videodownloader.pro.ui.main.MainActivity$onAddTab$1.void run()(MainActivity.java:628) at android.os.Handler.null handleCallback(null)(Handler.java:873) at android.os.Handler.null dispatchMessage(null)(Handler.java:99) at android.os.Looper.null loop(null)(Looper.java:193) at android.app.ActivityThread.null main(null)(ActivityThread.java:6936) at java.lang.reflect.Method.null invoke(null)(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.null run(null)(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.null main(null)(ZygoteInit.java:870) 

I have examined the code carefully and looked up related articles on the Internet. I, however, have not solved it yet, which really make me feel frustrating. This bug has only happened above android O.

Here are two methods that I have tried:

Method1:

public static Bitmap captureView(View view) { Bitmap tBitmap = Bitmap.createBitmap( view.getWidth(), view.getHeight(), Bitmap.Config.RGB_565); Canvas canvas = new Canvas(tBitmap); view.draw(canvas); canvas.setBitmap(null); return tBitmap; } 

Method2:

public static Bitmap captureView(View view) { if (view == null) return null; boolean drawingCacheEnabled = view.isDrawingCacheEnabled(); boolean willNotCacheDrawing = view.willNotCacheDrawing(); view.setDrawingCacheEnabled(true); view.setWillNotCacheDrawing(false); final Bitmap drawingCache = view.getDrawingCache(); Bitmap bitmap; if (null == drawingCache) { view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); view.buildDrawingCache(); bitmap = Bitmap.createBitmap(view.getDrawingCache()); } else { bitmap = Bitmap.createBitmap(drawingCache); } view.destroyDrawingCache(); view.setWillNotCacheDrawing(willNotCacheDrawing); view.setDrawingCacheEnabled(drawingCacheEnabled); return bitmap; } 

What need to be mentioned is that I have set android:hardwareAccelerated="true" for my Activity, where I invoked captureView method.

7 Answers

Read Glide hardware bitmap docs, and find PixelCopy class, which may solve this bug.

Use PixelCopy to convert view to Bitmap above Android O, and use previous method below Android O.

Here is my code:

fun captureView(view: View, window: Window, bitmapCallback: (Bitmap)->Unit) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Above Android O, use PixelCopy val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888) val location = IntArray(2) view.getLocationInWindow(location) PixelCopy.request(window, Rect(location[0], location[1], location[0] + view.width, location[1] + view.height), bitmap, { if (it == PixelCopy.SUCCESS) { bitmapCallback.invoke(bitmap) } }, Handler(Looper.getMainLooper()) ) } else { val tBitmap = Bitmap.createBitmap( view.width, view.height, Bitmap.Config.RGB_565 ) val canvas = Canvas(tBitmap) view.draw(canvas) canvas.setBitmap(null) bitmapCallback.invoke(tBitmap) } } 

The shortage is that I have to use a callback, which I don't like very much.

Hope that it works.

1

you can try copying hardware type bitmap to your desired type bitmap,e.g:

Bitmap targetBmp = originBmp.copy(Bitmap.Config.ARGB_8888, false); 
1

If you are converting a view that has an ImageView in it and you are loading its image using an Image loading library like Glide or coil then you have to disable the use of hardware bitmaps. These libraries store pixel data only in graphics memory and are optimal for cases where the Bitmap is only drawn to the screen.

If you are using coil then you have to do something like this

val imageLoader = ImageLoader.Builder(context).allowHardware(false).build() imageView.load(imageUrl, imageLoader) 
0

Based on @wang willway response, I created this Kotlin extension of View.

Use it in Compose as:

LocalView.current.toBitmap( onBitmapReady = { bitmap: Bitmap -> // TODO - use generated bitmap }, onBitmapError = { exception: Exception -> // TODO - handle exception } ) // start of extension. fun View.toBitmap(onBitmapReady: (Bitmap) -> Unit, onBitmapError: (Exception) -> Unit) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val temporalBitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888) // Above Android O, use PixelCopy due // val window: Window = (this.context as Activity).window val location = IntArray(2) this.getLocationInWindow(location) val viewRectangle = Rect(location[0], location[1], location[0] + this.width, location[1] + this.height) val onPixelCopyListener: PixelCopy.OnPixelCopyFinishedListener = PixelCopy.OnPixelCopyFinishedListener { copyResult -> if (copyResult == PixelCopy.SUCCESS) { onBitmapReady(temporalBitmap) } else { error("Error while copying pixels, copy result: $copyResult") } } PixelCopy.request(window, viewRectangle, temporalBitmap, onPixelCopyListener, Handler(Looper.getMainLooper())) } else { val temporalBitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.RGB_565) val canvas = android.graphics.Canvas(temporalBitmap) this.draw(canvas) canvas.setBitmap(null) onBitmapReady(temporalBitmap) } } catch (exception: Exception) { onBitmapError(exception) } } 
2

I used it for screenshoting Composables but you can use this without Compose. It returns every exception you might counter as wrapped error inside ImageResult.

fun View.screenshot( bitmapCallback: (ImageResult) -> Unit) {

try { val bitmap = Bitmap.createBitmap( width, height, Bitmap.Config.ARGB_8888, ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { PixelCopy.request( (this.context as Activity).window, bounds.toAndroidRect(), bitmap, { when (it) { PixelCopy.SUCCESS -> { bitmapCallback.invoke(ImageResult.Success(bitmap)) } PixelCopy.ERROR_DESTINATION_INVALID -> { bitmapCallback.invoke( ImageResult.Error( Exception( "The destination isn't a valid copy target. " + "If the destination is a bitmap this can occur " + "if the bitmap is too large for the hardware to " + "copy to. " + "It can also occur if the destination " + "has been destroyed" ) ) ) } PixelCopy.ERROR_SOURCE_INVALID -> { bitmapCallback.invoke( ImageResult.Error( Exception( "It is not possible to copy from the source. " + "This can happen if the source is " + "hardware-protected or destroyed." ) ) ) } PixelCopy.ERROR_TIMEOUT -> { bitmapCallback.invoke( ImageResult.Error( Exception( "A timeout occurred while trying to acquire a buffer " + "from the source to copy from." ) ) ) } PixelCopy.ERROR_SOURCE_NO_DATA -> { bitmapCallback.invoke( ImageResult.Error( Exception( "The source has nothing to copy from. " + "When the source is a Surface this means that " + "no buffers have been queued yet. " + "Wait for the source to produce " + "a frame and try again." ) ) ) } else -> { bitmapCallback.invoke( ImageResult.Error( Exception( "The pixel copy request failed with an unknown error." ) ) ) } } }, Handler(Looper.getMainLooper()) ) } else { val canvas = Canvas(bitmap) .apply { translate(-bounds.left, -bounds.top) } this.draw(canvas) canvas.setBitmap(null) bitmapCallback.invoke(ImageResult.Success(bitmap)) } } catch (e: Exception) { bitmapCallback.invoke(ImageResult.Error(e)) } 

}

sealed class ImageResult { object Initial : ImageResult() data class Error(val exception: Exception) : ImageResult() data class Success(val data: Bitmap) : ImageResult() } 

Usage

This may help somebody in future In my case It was due to giving fixed height to rating bar and at the same time setting style as below

style="@style/Widget.AppCompat.RatingBar.Small"

What worked for me is just wrapping the height of it instead of giving it fixed size.

Use this to capture a view (Works on android 8.0):

class CaptureView{ static Bitmap capture(View view){ view.setDrawingCacheEnabled(true); view.buildDrawingCache(); return view.getDrawingCache(); //Returns a bitmap } } Bitmap bitmap = CaptureView.capture(yourView) // Pass in the view to capture 
0

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

You Might Also Like