Android给图片添加水印

作者:LiChengZe_Blog 时间:2022-12-02 20:25:44 

目录
  • 1. 前言

  • 2. 方法1 使用SurfaceView

  • 3. 方法2 给拍照下来的图片添加水印

    • 第一步:获取拍照权限

    • 第二步:拍照

      • 调用相机进行拍照

      • 接收拍照结果

      • 注意:

    • 第三步:给拍照后得到的图片添加水印

    • 4. 最终实现的效果如下

      • 5.总结

        1. 前言

        Android给图片添加水印

        PS:最近在项目执行过程中有这样一个需求,要求拍完照的图片必须达到以上的效果。需求分析:

        1. 使用用预览布局SurfaceView,在不局上方使用控件的方式来进行设计,最后通过截图的方式将画面进行保存。

        2. 使用图片添加水印的方式来完成。

        2. 方法1 使用SurfaceView

        我心想这不简单吗?于是开始一顿balabala的操作,结果到最后一步时发现,SurfaceView居然不能进行截图,截图下来的图片居然是一张黑色的。简单地说这是因为SurfaceView的特性决定的,我们知道安卓中唯一可以在子线程中进行绘制的view就只有Surfaceview了。他可以独立于子线程中绘制,不会导致主线程的卡顿,至于造成surfaceView黑屏的原因,可以移步这里 Android视图SurfaceView的实现原理分析。如果非要使用此方式时还是有三种思路来进行解决: 采用三种思路:

        1. 获取源头视频的截图作为SurfaceView的截图
        2. 获取SurfaceView的画布canvas,将canvas保存成Bitmap
        3. 直接截取整个屏幕,然后在截图SurfaceView位置的图

        但是我觉得这种方式太过繁琐,所以选择用添加水印的式来完成。

        3. 方法2 给拍照下来的图片添加水印

        第一步:获取拍照权限


        <!--相机权限-->
        <uses-permission android:name="android.permission.CAMERA" />
        <!--访问外部权限-->
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

        这里使用到郭霖大佬的开源库PermissionX获取权限:


        PermissionX.init(this)
           .permissions(Manifest.permission.CAMERA,  Manifest.permission.RECORD_AUDIO)
           .onExplainRequestReason { scope, deniedList ->
               val message = "需要您同意以下权限才能正常使用"
               scope.showRequestReasonDialog(deniedList, message, "确定", "取消")
           }
           .request { allGranted, grantedList, deniedList ->
               if (allGranted) {
                   openCamera()
               } else {
                   Toast.makeText(activity, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show()
               }
           }

        第二步:拍照

        android 6.0以后,相机权限需要动态申请。


        // 申请相机权限的requestCode
          private static final int PERMISSION_CAMERA_REQUEST_CODE = 0x00000012;

        /**
           * 检查权限并拍照。
           * 调用相机前先检查权限。
           */
          private void checkPermissionAndCamera() {
              int hasCameraPermission = ContextCompat.checkSelfPermission(getApplication(),
                      Manifest.permission.CAMERA);
              if (hasCameraPermission == PackageManager.PERMISSION_GRANTED) {
                  //有调起相机拍照。
                  openCamera();
              } else {
                  //没有权限,申请权限。
                  ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA},
                          PERMISSION_CAMERA_REQUEST_CODE);
              }
          }

        /**
           * 处理权限申请的回调。
           */
          @Override
          public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
              if (requestCode == PERMISSION_CAMERA_REQUEST_CODE) {
                  if (grantResults.length > 0
                          && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                      //允许权限,有调起相机拍照。
                      openCamera();
                  } else {
                      //拒绝权限,弹出提示框。
                      Toast.makeText(this,"拍照权限被拒绝",Toast.LENGTH_LONG).show();
                  }
              }
          }

        调用相机进行拍照

        申请权限后,就可以调起相机拍照了。调用相机只需要调用startActivityForResult传一个Intent就可以了,但是这个Intent需要传递一个uri,用于保存拍出来的图片,创建这个uri时,各个Android版本有所不同,需要进行版本兼容。


         //用于保存拍照图片的uri
           private Uri mCameraUri;

        // 用于保存图片的文件路径,Android 10以下使用图片路径访问图片
           private String mCameraImagePath;

        // 是否是Android 10以上手机
           private boolean isAndroidQ = Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q;

        /**
            * 调起相机拍照
            */
           private void openCamera() {
               Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
               // 判断是否有相机
               if (captureIntent.resolveActivity(getPackageManager()) != null) {
                   File photoFile = null;
                   Uri photoUri = null;

        if (isAndroidQ) {
                       // 适配android 10
                       photoUri = createImageUri();
                   } else {
                       try {
                           photoFile = createImageFile();
                       } catch (IOException e) {
                           e.printStackTrace();
                       }

        if (photoFile != null) {
                           mCameraImagePath = photoFile.getAbsolutePath();
                           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                               //适配Android 7.0文件权限,通过FileProvider创建一个content类型的Uri
                               photoUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile);
                           } else {
                               photoUri = Uri.fromFile(photoFile);
                           }
                       }
                   }

        mCameraUri = photoUri;
                   if (photoUri != null) {
                       captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
                       captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                       startActivityForResult(captureIntent, CAMERA_REQUEST_CODE);
                   }
               }
           }

        /**
            * 创建图片地址uri,用于保存拍照后的照片 Android 10以后使用这种方法
            */
           private Uri createImageUri() {
               String status = Environment.getExternalStorageState();
               // 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储
               if (status.equals(Environment.MEDIA_MOUNTED)) {
                  return getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new ContentValues());
               } else {
                   return getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, new ContentValues());
               }
           }

        /**
            * 创建保存图片的文件
            */
           private File createImageFile() throws IOException {
               String imageName = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
               File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
               if (!storageDir.exists()) {
                   storageDir.mkdir();
               }
               File tempFile = new File(storageDir, imageName);
               if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))) {
                   return null;
               }
               return tempFile;
           }

        接收拍照结果


         @Override
           protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
               super.onActivityResult(requestCode, resultCode, data);
               if (requestCode == CAMERA_REQUEST_CODE) {
                   if (resultCode == RESULT_OK) {
                       if (isAndroidQ) {
                           // Android 10 使用图片uri加载
                           ivPhoto.setImageURI(mCameraUri);
                       } else {
                           // 使用图片路径加载
                           ivPhoto.setImageBitmap(BitmapFactory.decodeFile(mCameraImagePath));
                       }
                   } else {
                       Toast.makeText(this,"取消",Toast.LENGTH_LONG).show();
                   }
               }
           }

        注意:

        这两需要说明一下,Android 10由于文件权限的关系,显示手机储存卡里的图片不能直接使用图片路径,需要使用图片uri加载。

        另外虽然我在这里对Android 10和10以下的手机使用了不同的方式创建uri 和加载图片,但其实Android 10创建uri的方式和使用uri加载图片的方式在10以下的手机是同样适用的。 android 7.0需要配置文件共享。


        <provider
           android:name="androidx.core.content.FileProvider"
           android:authorities="${applicationId}.fileprovider"
           android:exported="false"
           android:grantUriPermissions="true">
           <meta-data
               android:name="android.support.FILE_PROVIDER_PATHS"
               android:resource="@xml/file_paths" />
        </provider>

        在res目录下创建文件夹xml ,放置一个文件file_paths.xml(文件名可以随便取),配置需要共享的文件目录,也就是拍照图片保存的目录。


        <?xml version="1.0" encoding="utf-8"?>
        <resources>
           <paths>
               <!-- 这个是保存拍照图片的路径,必须配置。 -->
               <external-files-path
                   name="images"
                   path="Pictures" />
           </paths>
        </resources>

        第三步:给拍照后得到的图片添加水印


         @Override
           protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
               super.onActivityResult(requestCode, resultCode, data);
               if (requestCode == CAMERA_REQUEST_CODE) {
                   if (resultCode == RESULT_OK) {
                         Bitmap mp;          
                          if (isAndroidQ) {
                               // Android 10 使用图片uri加载
                               mp = MediaStore.Images.Media.getBitmap(this.contentResolver, t.uri);
                           } else {
                               // Android 10 以下使用图片路径加载
                               mp = BitmapFactory.decodeFile(uri);
                           }
                           //对图片添加水印 这里添加一张图片为示例:
                           ImageUtil.drawTextToLeftTop(this,mp,"示例文字",30,R.color.black,20,30)
                           } else {
                       Toast.makeText(this,"取消",Toast.LENGTH_LONG).show();
                   }
               }
           }

        这里使用到一个ImageUtil工具类,我在这里贴上。如果需要使用可以直接拿走~


        public class ImageUtil {
           /**
            * 设置水印图片在左上角
            *
            * @param context     上下文
            * @param src
            * @param watermark
            * @param paddingLeft
            * @param paddingTop
            * @return
            */
           public static Bitmap createWaterMaskLeftTop(Context context, Bitmap src, Bitmap watermark, int paddingLeft, int paddingTop) {
               return createWaterMaskBitmap(src, watermark,
                       dp2px(context, paddingLeft), dp2px(context, paddingTop));
           }

        private static Bitmap createWaterMaskBitmap(Bitmap src, Bitmap watermark, int paddingLeft, int paddingTop) {
               if (src == null) {
                   return null;
               }
               int width = src.getWidth();
               int height = src.getHeight();
               //创建一个bitmap
               Bitmap newb = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);// 创建一个新的和SRC长度宽度一样的位图
               //将该图片作为画布
               Canvas canvas = new Canvas(newb);
               //在画布 0,0坐标上开始绘制原始图片
               canvas.drawBitmap(src, 0, 0, null);
               //在画布上绘制水印图片
               canvas.drawBitmap(watermark, paddingLeft, paddingTop, null);
               // 保存
               canvas.save(Canvas.ALL_SAVE_FLAG);
               // 存储
               canvas.restore();
               return newb;
           }

        /**
            * 设置水印图片在右下角
            *
            * @param context       上下文
            * @param src
            * @param watermark
            * @param paddingRight
            * @param paddingBottom
            * @return
            */
           public static Bitmap createWaterMaskRightBottom(Context context, Bitmap src, Bitmap watermark, int paddingRight, int paddingBottom) {
               return createWaterMaskBitmap(src, watermark,
                       src.getWidth() - watermark.getWidth() - dp2px(context, paddingRight),
                       src.getHeight() - watermark.getHeight() - dp2px(context, paddingBottom));
           }

        /**
            * 设置水印图片到右上角
            *
            * @param context
            * @param src
            * @param watermark
            * @param paddingRight
            * @param paddingTop
            * @return
            */
           public static Bitmap createWaterMaskRightTop(Context context, Bitmap src, Bitmap watermark, int paddingRight, int paddingTop) {
               return createWaterMaskBitmap(src, watermark,
                       src.getWidth() - watermark.getWidth() - dp2px(context, paddingRight),
                       dp2px(context, paddingTop));
           }

        /**
            * 设置水印图片到左下角
            *
            * @param context
            * @param src
            * @param watermark
            * @param paddingLeft
            * @param paddingBottom
            * @return
            */
           public static Bitmap createWaterMaskLeftBottom(Context context, Bitmap src, Bitmap watermark, int paddingLeft, int paddingBottom) {
               return createWaterMaskBitmap(src, watermark, dp2px(context, paddingLeft),
                       src.getHeight() - watermark.getHeight() - dp2px(context, paddingBottom));
           }

        /**
            * 设置水印图片到中间
            *
            * @param src
            * @param watermark
            * @return
            */
           public static Bitmap createWaterMaskCenter(Bitmap src, Bitmap watermark) {
               return createWaterMaskBitmap(src, watermark,
                       (src.getWidth() - watermark.getWidth()) / 2,
                       (src.getHeight() - watermark.getHeight()) / 2);
           }

        /**
            * 给图片添加文字到左上角
            *
            * @param context
            * @param bitmap
            * @param text
            * @return
            */
           public static Bitmap drawTextToLeftTop(Context context, Bitmap bitmap, String text, int size, int color, int paddingLeft, int paddingTop) {
               Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
               paint.setColor(color);
               paint.setTextSize(dp2px(context, size));
               Rect bounds = new Rect();
               paint.getTextBounds(text, 0, text.length(), bounds);
               return drawTextToBitmap(context, bitmap, text, paint, bounds,
                       dp2px(context, paddingLeft),
                       dp2px(context, paddingTop) + bounds.height());
           }

        /**
            * 绘制文字到右下角
            *
            * @param context
            * @param bitmap
            * @param text
            * @param size
            * @param color
            * @return
            */
           public static Bitmap drawTextToRightBottom(Context context, Bitmap bitmap, String text, int size, int color, int paddingRight, int paddingBottom) {
               Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
               paint.setColor(color);
               paint.setTextSize(dp2px(context, size));
               Rect bounds = new Rect();
               paint.getTextBounds(text, 0, text.length(), bounds);
               return drawTextToBitmap(context, bitmap, text, paint, bounds,
                       bitmap.getWidth() - bounds.width() - dp2px(context, paddingRight),
                       bitmap.getHeight() - dp2px(context, paddingBottom));
           }

        /**
            * 绘制文字到右上方
            *
            * @param context
            * @param bitmap
            * @param text
            * @param size
            * @param color
            * @param paddingRight
            * @param paddingTop
            * @return
            */
           public static Bitmap drawTextToRightTop(Context context, Bitmap bitmap, String text, int size, int color, int paddingRight, int paddingTop) {
               Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
               paint.setColor(color);
               paint.setTextSize(dp2px(context, size));
               Rect bounds = new Rect();
               paint.getTextBounds(text, 0, text.length(), bounds);
               return drawTextToBitmap(context, bitmap, text, paint, bounds,
                       bitmap.getWidth() - bounds.width() - dp2px(context, paddingRight),
                       dp2px(context, paddingTop) + bounds.height());
           }

        /**
            * 绘制文字到左下方
            *
            * @param context
            * @param bitmap
            * @param text
            * @param size
            * @param color
            * @param paddingLeft
            * @param paddingBottom
            * @return
            */
           public static Bitmap drawTextToLeftBottom(Context context, Bitmap bitmap, String text, int size, int color, int paddingLeft, int paddingBottom) {
               Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
               paint.setColor(color);
               paint.setTextSize(dp2px(context, size));
               Rect bounds = new Rect();
               paint.getTextBounds(text, 0, text.length(), bounds);
               return drawTextToBitmap(context, bitmap, text, paint, bounds,
                       dp2px(context, paddingLeft),
                       bitmap.getHeight() - dp2px(context, paddingBottom));
           }

        /**
            * 绘制文字到中间
            *
            * @param context
            * @param bitmap
            * @param text
            * @param size
            * @param color
            * @return
            */
           public static Bitmap drawTextToCenter(Context context, Bitmap bitmap, String text, int size, int color) {
               Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
               paint.setColor(color);
               paint.setTextSize(dp2px(context, size));
               Rect bounds = new Rect();
               paint.getTextBounds(text, 0, text.length(), bounds);
               return drawTextToBitmap(context, bitmap, text, paint, bounds,
                       (bitmap.getWidth() - bounds.width()) / 2,
                       (bitmap.getHeight() + bounds.height()) / 2);
           }

        //图片上绘制文字
           private static Bitmap drawTextToBitmap(Context context, Bitmap bitmap, String text, Paint paint, Rect bounds, int paddingLeft, int paddingTop) {
               android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();

        paint.setDither(true); // 获取跟清晰的图像采样
               paint.setFilterBitmap(true);// 过滤一些
               if (bitmapConfig == null) {
                   bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
               }
               bitmap = bitmap.copy(bitmapConfig, true);
               Canvas canvas = new Canvas(bitmap);

        canvas.drawText(text, paddingLeft, paddingTop, paint);
               return bitmap;
           }

        /**
            * 缩放图片
            *
            * @param src
            * @param w
            * @param h
            * @return
            */
           public static Bitmap scaleWithWH(Bitmap src, double w, double h) {
               if (w == 0 || h == 0 || src == null) {
                   return src;
               } else {
                   // 记录src的宽高
                   int width = src.getWidth();
                   int height = src.getHeight();
                   // 创建一个matrix容器
                   Matrix matrix = new Matrix();
                   // 计算缩放比例
                   float scaleWidth = (float) (w / width);
                   float scaleHeight = (float) (h / height);
                   // 开始缩放
                   matrix.postScale(scaleWidth, scaleHeight);
                   // 创建缩放后的图片
                   return Bitmap.createBitmap(src, 0, 0, width, height, matrix, true);
               }
           }

        /**
            * dip转pix
            *
            * @param context
            * @param dp
            * @return
            */
           public static int dp2px(Context context, float dp) {
               final float scale = context.getResources().getDisplayMetrics().density;
               return (int) (dp * scale + 0.5f);
           }
        }

        4. 最终实现的效果如下

        Android给图片添加水印

        5.总结

        整体来说没有什么太大的问题,添加水印的原理就是通过Canvas绘制的方式将文字/图片添加到图片上。最后再将修改之后的图片呈现给用户。同时也记录下SurfaceView截图黑屏的问题。

        来源:https://juejin.cn/post/6960579316191068197

        标签:Android,水印
        0
        投稿

        猜你喜欢

      • Android自定义View新年烟花、祝福语横幅动画

        2022-01-24 21:31:27
      • Android 中 ActivityLifecycleCallbacks的实例详解

        2022-08-12 06:03:34
      • Android XListView下拉刷新和上拉加载更多

        2022-11-01 19:07:45
      • 如何在C#中使用指针

        2022-07-02 16:09:47
      • android在异步任务中关闭Cursor的代码方法

        2022-04-21 18:07:32
      • SpringBoot整合阿里云短信服务的方法

        2022-03-24 18:17:11
      • Spring Boot缓存实战之Redis 设置有效时间和自动刷新缓存功能(时间支持在配置文件中配置)

        2023-11-11 01:57:18
      • java操作elasticsearch的案例解析

        2021-06-27 19:17:13
      • java语言图形用户登录界面代码

        2021-09-11 23:19:34
      • Mybatis基础概念与高级应用小结

        2023-11-26 20:18:22
      • Java 逻辑运算符中&&与&,||与|的区别

        2021-05-30 15:24:26
      • JDBC自定义连接池过程详解

        2023-11-17 13:27:55
      • Object类toString()和equals()方法使用解析

        2022-10-28 08:48:43
      • java字节码框架ASM的深入学习

        2023-11-29 05:51:19
      • Spring七大事务传递机制深入分析实现原理

        2022-12-21 16:28:37
      • 分析JVM源码之Thread.interrupt系统级别线程打断

        2023-07-31 17:15:23
      • Android开发中判断手机是否安装了QQ或者微信

        2022-01-09 08:44:24
      • 利用java操作Excel文件的方法

        2021-12-13 03:03:49
      • SpringBoot2.6.x升级后循环依赖及Swagger无法使用问题

        2021-12-02 09:49:43
      • java 生成有序账号的实现方法

        2023-08-12 03:28:01
      • asp之家 软件编程 m.aspxhome.com