Android画像のぼかしとビットマップ関連

Android画像のぼかしとビットマップ関連

序章

タスク:ウィンドウポップアップの背景がぼやけている

実装原理:ウィンドウポップアップウィンドウの内容を表示する前に、まず携帯電話の画面のスクリーンショットを撮り、それをぼかします。ポップアップウィンドウが表示されたら、ポップのサイズに合わせてスクリーンショットを調整します。 -ポップアップウィンドウを背景として設定し、ポップアップウィンドウの背景をぼかす効果を実現します。

困難:

  1. 私たちのアプリケーションにはアクティビティが常駐しておらず、システムアプリケーションであるため、通常のアプリケーションのスクリーンショットとは異なります。
  2. リフレクションメソッドを使用してシステムAPIを呼び出してスクリーンショットを撮る場合、画面の高さを取得する際に落とし穴があり、特別な注意が必要です。
  3. ネイティブAPIを直接使用してぼかしを行う場合、半径を最大25に設定するとビジネス要件が満たされないため、画像を追加で処理する必要があります。

スクリーンショット

スクリーンショットを撮る方法はたくさんありますが、一般的に使用される方法は次のとおりです。

  1. adbコマンドのスクリーンショット

    スクリーンショットを撮るadbコマンドにはroot権限が必要であり、ioが含まれるため、時間がかかります。

       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. リフレクション呼び出しシステムAPIのスクリーンショット

    この方法を使用してスクリーンショットを撮る場合は、システムアプリケーションである必要があります

       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;
      }
    

    この方法を使用してスクリーンショットを撮ったときに穴がありました。投げるのに長い時間がかかりました。一部のフルスクリーン携帯電話でdm.heightPixelsを直接使用して得られた画面の高さには、ステータスバーが含まれていません。ソースを参照してください。コードはdm.heightPixelsで、コメントは使用可能な表示サイズの絶対高さ(ピクセル単位)と記述されているため、ステータスバーを含めなくてもかまいません。現時点では、画面の高さを取得する別の方法があります。

      /**
        * 在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. 通常のアプリのスクリーンショット

    ルートレイアウトを取得するにはgetWindow()。getDecorView()を呼び出す必要があるため、このメソッドは通常、アクティビティまたはフラグメントで呼び出されます。したがって、スクリーンショットを撮る必要がある場合、アクティビティアクティビティがない場合は、この方法を使用できません。

       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;
      }
    

    同時に、このメソッドはビュー全体でスクリーンショットを撮るため、メソッド内のビューオブジェクトがスクリーンショットが必要なビューに置き換えられている限り、特定のビューの部分的なスクリーンショットを撮ることもできます。

ビットマップブラー

GitHubには既製のファジーコントロールがあり、github.com / mmin18/Realなどの多くのシナリオに対応できます。残念ながら、それは私のニーズを満たしていません。ここで取得したビットマップをぼかすだけで、ネイティブAndroidを直接使用できます。コードは次のとおりです。

/**
* 
* @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

おすすめ

転載: juejin.im/post/7116432182448488479