Android image blur and bitmap related

Android image blur and bitmap related

Introduction

Task: The background of the window popup is blurred

Implementation principle: before the content of the window pop-up window is displayed, first take a screenshot of the mobile phone screen, and then blur it. After the pop-up window comes out, adapt the screenshot according to the size of the pop-up window and set it as the background, so as to achieve the effect of blurring the background of the pop-up window.

difficulty:

  1. Since our application has no activity resident and is a system application, it is different from the screenshot of ordinary applications;
  2. When using the reflection method to call the system api to take a screenshot, there is a pit in obtaining the screen height, which requires special attention;
  3. Using the native api directly for blurring, setting the radius to a maximum of 25 does not meet the business requirements, so the image needs to be processed in addition.

screenshot

There are many ways to take screenshots, here are some commonly used ones:

  1. adb command screenshot

    The adb command to take screenshots requires root privileges, and because it involves io, it is time-consuming

       private Bitmap getScreenByAdb(String localImgPath) {
    //       adb命令获取屏幕截图,与实际屏幕尺寸相同,但是涉及IO所以耗时
           Process process = null;
           try {
               process = Runtime.getRuntime().exec("screencap " +  localImgPath);
               process.waitFor();//比较耗时,尤其屏幕色彩比较复杂时,耗时甚至会到3s
          } catch (Exception e) {
               e.printStackTrace();
          } finally {
               if (process != null) {
                   process.destroy();
              }
          }
           FileInputStream fis = null;
           try {
               fis = new FileInputStream(localImgPath);
          } catch (FileNotFoundException e) {
               e.printStackTrace();
          }
           Bitmap originalBgBitmap = BitmapFactory.decodeStream(fis);
           return originalBgBitmap;
      }
    
  2. Screenshot of reflection calling system api

    When using this method to take screenshots, it needs to be a system application

       private Bitmap getScreenBySysMethod() {
           DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
           Bitmap originalBgBitmap = null;
           try {
               Class<?> mClassType = Class.forName("android.view.SurfaceControl");
               Method nativeScreenshotMethod = mClassType.getDeclaredMethod("screenshot", Rect.class, int.class, int.class, int.class);
               nativeScreenshotMethod.setAccessible(true);
               Bitmap sBitmap = (Bitmap) nativeScreenshotMethod.invoke(mClassType, new Rect(), dm.widthPixels,
                       dm.heightPixels, Surface.ROTATION_0);
               originalBgBitmap = sBitmap.copy(Bitmap.Config.ARGB_8888, true);
          } catch (NoSuchMethodException e) {
               e.printStackTrace();
          } catch (ClassNotFoundException e) {
               e.printStackTrace();
          } catch (InvocationTargetException e) {
               e.printStackTrace();
          } catch (IllegalAccessException e) {
               e.printStackTrace();
          }
           return originalBgBitmap;
      }
    

    There was a pit when I used this method to take screenshots. It took me a long time to toss. The screen height obtained by using dm.heightPixels directly on some full-screen mobile phones does not include the status bar. Look at the source code of dm.heightPixels. The comment is written as The absolute height of the available display size in pixels, so it's okay not to include the status bar. At this time, there is another way to get the screen height:

      /**
        * 在activity中获取屏幕的真实高度,由于在部分全面屏手机上,
        * 直接使用DisplayMetrics heightPixels获取屏幕高度没有包含状态栏或者虚拟按键
        * 所以会比实际的小几十个像素
        * @param context
        * @return
        */
       public static int getScreenHeight(Activity context) {
           DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
           context.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
           return displayMetrics.heightPixels;
      }
    
  3. Screenshots of normal apps

    This method is generally called in an activity or fragment, because you need to call getWindow().getDecorView() to get the root layout. Therefore, if you need to take a screenshot, if there is no activity activity, you cannot use this method.

       private Bitmap getScreenshotView() {
           View view = getWindow().getDecorView();
           view.setDrawingCacheEnabled(true); // 设置缓存,可用于实时截图
           Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
           Canvas canvas = new Canvas(bitmap);
           view.draw(canvas);
           view.setDrawingCacheEnabled(false); // 清空缓存,可用于实时截图
           return bitmap;
      }
    

    At the same time, since this method takes screenshots through the view, it is also possible to take a partial screenshot of a certain view, as long as the view object in the method is replaced with the view that needs to be screenshotted.

Bitmap blur

There are ready-made fuzzy controls on GitHub, which can meet many scenarios, such as github.com/mmin18/Real…etc . Unfortunately, it does not meet my needs. I only need to blur the bitmap I get here, and I can use the native Android directly. The code is as follows:

/**
* @param context 上下文
* @param src 原图
* @param radius 模糊半径,有效范围0-25
* @return
*/
private Bitmap blur(Context context, Bitmap src, float radius) {
  RenderScript rs = RenderScript.create(context);
       final Allocation input = Allocation.createFromBitmap(rs, src);
       final Allocation output = Allocation.createTyped(rs, input.getType());
       final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
       script.setRadius(radius);
       script.setInput(input);
       script.forEach(output);
       output.copyTo(src);
       return src;
}

如果这个方法模糊度不够的话,还可以这样来做:先将bitmap缩小,再进行模糊,然后再放大,这样得到的模糊度一般是可以满足需求的。

缩小bitmap

BitmapTransformUtil.scaleBitmap(originalBgBitmap, 0.2f, 0.2f, true);

放大bitmap

BitmapTransformUtil.scaleBitmap(blurredBgBitmap, 5.0f, 5.0f, true);

其中scaleBitmap方法如下所示:

   /**
    * 根据指定的宽度比例值和高度比例值进行缩放
    *
    * @param srcBitmap
    * @param scaleWidth
    * @param scaleHeight
    * @param recycleSrc 是否回收Bitmap
    * @return
    */
   public static Bitmap scaleBitmap(Bitmap srcBitmap, float scaleWidth, float scaleHeight, boolean recycleSrc) {
       int width = srcBitmap.getWidth();
       int height = srcBitmap.getHeight();
       Matrix matrix = new Matrix();
       matrix.postScale(scaleWidth, scaleHeight);
       Bitmap bitmap = Bitmap.createBitmap(srcBitmap, 0, 0, width, height, matrix, true);
       if (bitmap != null) {
           /**回收*/
           if (recycleSrc && srcBitmap != null && !srcBitmap.equals(bitmap) && !srcBitmap.isRecycled()) {
               GlideBitmapPool.putBitmap(srcBitmap);
          }
           return bitmap;
      } else {
           return srcBitmap;
      }
  }

Bitmap截取

拿到整个手机屏幕的截图后,需要借去部分作为背景,截取bitmap方法如下:

   /**
    * 裁剪一定高度保留上半部分
    *
    * @param srcBitmap 原图
    * @param x         起始坐标x
    * @param y         起始坐标y
    * @param width     目标宽度
    * @param height     目标高度
    * @param recycleSrc 是否回收原图
    * @return
    */
   public static Bitmap cropBitmapTop(Bitmap srcBitmap, int x, int y, int width, int height, boolean recycleSrc) {
       /**裁剪关键步骤*/
       Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, x, y, width, height);
       /**回收之前的Bitmap*/
       if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) {
           GlideBitmapPool.putBitmap(srcBitmap);
      }
       return cropBitmap;
  }

Bitmap圆角设置

   /**
    * 设置圆角
    *
    * @param srcBitmap
    * @param recycleSrc 是否回收原图
    * @return
    */
   public static Bitmap getRoundedCornerBitmap(Bitmap srcBitmap, float radius, boolean recycleSrc) {
       Bitmap output = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.ARGB_8888);
       Paint paint = new Paint();
       Rect rect = new Rect(0, 0, srcBitmap.getWidth(), srcBitmap.getHeight());
       RectF rectF = new RectF(rect);
       paint.setAntiAlias(true);
       Canvas canvas = new Canvas(output);
       canvas.save();
       canvas.drawARGB(0, 0, 0, 0);
       canvas.drawRoundRect(rectF, radius, radius, paint);
       paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
       canvas.drawBitmap(srcBitmap, rect, rect, paint);
       canvas.restore();
       /**回收之前的Bitmap*/
       if (recycleSrc && srcBitmap != null && !srcBitmap.equals(output) && !srcBitmap.isRecycled()) {
           GlideBitmapPool.putBitmap(srcBitmap);
      }
       return output;
  }

Bitmap保存

    /**
    * 保存bitmap到本地
    * @param bitmap 
    * @param filePath 
    */
   private void saveBitmap(Bitmap bitmap, String filePath){
       FileOutputStream fos = null;
       try {
           fos = new FileOutputStream(filePath);
           bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
           fos.flush();
      } catch (IOException e) {
           e.printStackTrace();
      } finally {
           try {
               if (fos != null) {
                   fos.close();
              }
          } catch (IOException e) {
               e.printStackTrace();
          }
      }
  }

推荐一个bitmap复用池:github.com/amitshekhar…,毕竟bitmap是内存消耗大户,合理使用bitmap可以避免内存抖动和OOM。具体使用方法直接点进去看吧。

最后贴一个实现效果图吧

image.png

Guess you like

Origin juejin.im/post/7116432182448488479