最近项目提出新需求,要求在拍照后在图片上添加用户信息的水印,so,楼主重新整理封装了一个简单的拍照+水印的工具类,需求不同,仅供参考。文件操作及权限做了适配,支持androidN及以上版本,放心使用。

效果演示:

如何调用相机这里就不介绍了,因为小伙伴们基本都使用过,主要介绍下实现水印的方式,实现水印效果也很简单,利用paint及canvas操作bitmap在对应位置进行绘制文本即可,首先定义画笔,设置水印颜色、大小及文本:

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
paint.setTextSize(size);
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);

定义好画笔后新建canvas对bitmap进行绘制,这里应该注意的是在Android代码里是不允许直接修改Bitmap资源文件,如果不copy一份的话会抛异常

Caused by: java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor

绘制代码如下:

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

我们看下drawText的源码

/**
 * Draw the text, with origin at (x,y), using the specified paint. The
 * origin is interpreted based on the Align setting in the paint.
 *
 * @param text  The text to be drawn
 * @param x     The x-coordinate of the origin of the text being drawn
 * @param y     The y-coordinate of the baseline of the text being drawn
 * @param paint The paint used for the text (e.g. color, size, style)
 */
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {native_drawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
            paint.getNativeInstance(), paint.mNativeTypeface);
}

第一个和第四个参数显而易见,第二个和第三个看注释也能看懂,当然是对应的坐标轴x轴和y轴了,但是这个坐标轴到底原点在哪呢,楼主自己做了一张简单的图片供小伙伴参考,如图:

坐标原点为手机左上角,横向向右为X轴正极,纵向向下为Y轴正极,这样我们可以计算left及top来设置水印的不同位置了,楼主在util中封装了几个常用的位置,小伙伴可以查看demo中的代码。

绘制文本问题解决了,但是又一个新问题出现了,那就是如何实现多段落显示及段落间的换行,我们都知道每部手机的相机像素是不一样的,如果是把textSize写死的话,在不同分辨率的图片上显示效果很差,所以我定义了一个itemCount,每行显示的文字数,通过itemCount及图片宽度计算出textSize,比如一部手机拍出的图片宽为1080像素,itemCount为20,那么文字大小为54px,根据textSize计算总文本行数并分段,把每段文字放在list中,最后遍历List绘制水印,参考代码如下:

/**
 * 绘制水印
 *
 * @param context
 * @param bitmap
 * @param waterMaskParam
 * @return
 */
private static Bitmap drawTxt(Context context, Bitmap bitmap, WaterMaskParam waterMaskParam) {int maxHeight = 0;  //计算总行数
    List<List<String>> msg = new ArrayList<>(); //文本
    for (String str : waterMaskParam.txt) {int count = str.length() / waterMaskParam.itemCount;
        if (count == 0) {maxHeight++;
            List<String> list = new ArrayList<>();
            list.add(str);
            msg.add(list);
        } else {if (str.length() % waterMaskParam.itemCount != 0) {count++;
            }List<String> list = new ArrayList<>();
            for (int i = 0; i < count; i++) {String s = str.substring(i * waterMaskParam.itemCount,
                        i == count - 1 ? str.length() : (i + 1) * waterMaskParam.itemCount);
                list.add(s);
            }msg.add(list);
            maxHeight += count;
        }}int txtSize = bitmap.getWidth() / waterMaskParam.itemCount;
    int index = msg.size() - 1;
    bitmap = checkBackground(waterMaskParam.location, bitmap, msg.size() * txtSize * 2.0f);
    for (List<String> strings : msg) {for (int i = 0; i < strings.size(); i++) {bitmap = checkType(context, bitmap, strings.get(i), txtSize,
                    waterMaskParam, txtSize * (maxHeight--) + index * txtSize / 2);
        }index--;
    }return bitmap;
}

我在最后绘制水印时,list中嵌套了一个list,外循环用来处理多段落的换行问题,内循环用来处理每个段落中的换行问题,所以我在调用绘制文本外部类传值时用的是list而不是string,及list中每个元素为一个段落,model如下:

public static class WaterMaskParam {public List<String> txt = new ArrayList<>(); //水印文字
    public int itemCount = DefWaterMaskParam.ITEM_COUNT; //每行的文字数
    public int txtColor = DefWaterMaskParam.TEXT_COLOR; //文字颜色
    public int location = DefWaterMaskParam.Location.left_bottom; //水印位置

    public WaterMaskParam() {}public WaterMaskParam(List<String> txt) {this.txt = txt;
    }public WaterMaskParam(List<String> txt, int itemCount, int txtColor) {this.txt = txt;
        this.itemCount = itemCount;
        this.txtColor = txtColor;
    }
}

为了优化水印,我在水印背景处又加了印象效果,模拟器拍摄的图片,不美观见谅

背景色的实现方式与水印绘制方式大同小异:

/**
 * 绘制背景色
 *
 * @param bitmap
 * @param maxHeight 水印最大高度
 * @return
 */
private static Bitmap drawBackground(Bitmap bitmap, float maxHeight) {Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
            Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(newBitmap);
    Paint paint_b = new Paint();
    paint_b.setDither(true);
    paint_b.setFilterBitmap(true);
    paint_b.setColor(Color.BLACK);
    paint_b.setDither(true);
    paint_b.setFilterBitmap(true);
    paint_b.setAlpha(100);
    canvas.drawBitmap(bitmap, 0, 0, null);
    canvas.drawRect(0, newBitmap.getHeight() - maxHeight, newBitmap.getWidth(),
            newBitmap.getHeight(), paint_b);
    return newBitmap;
}

我封装了一个helper类用来简化调用:

public class WaterMaskHelper {private Context context;

    private PhotoListener photoListener;
    private WaterMask.WaterMaskListener waterMarkListener;

    public WaterMaskHelper(Context context, PhotoListener photoListener, WaterMask.WaterMaskListener waterMarkListener) {this.context = context;
        this.photoListener = photoListener;
        this.waterMarkListener = waterMarkListener;
    }public void startCapture() {context.startActivity(new Intent(context, PhotoCaptureActivity.class));
        PhotoCaptureActivity.setWaterListener(waterMarkListener);
        PhotoCaptureActivity.setPhotoListener(photoListener);
    }
}

在activity中实例helper并实现接口:

//初始化水印工具
waterMaskHelper = new WaterMaskHelper(this, this, this);

调用startCapture调用相机拍照并添加水印:

waterMaskHelper.startCapture();

暴露的接口有两个,都是在相机拍照后调用,选择照片:

//选择照片的uri,默认为下标1的元素
void onChoose(ArrayList<String> photos);

添加水印:

//拍照后调用,设置水印的基本参数
WaterMaskParam onDraw();

具体实现,注:txt为空或param为空时不绘制水印

@Override
public WaterMask.WaterMaskParam onDraw() {WaterMask.WaterMaskParam param = new WaterMask.WaterMaskParam();
    param.txt.add("我是一个小标题");
    param.txt.add(binding.edt.getText().toString().trim());
    param.location = maskLocation;
    param.itemCount = 30;
    return param;
}@Override
public void onChoose(ArrayList<String> photos) {uris = photos;
    Glide.with(MainActivity.this).load(photos.get(0)).placeholder(R.mipmap.ic_launcher).centerCrop().error(R.mipmap.ic_launcher).crossFade().into(binding.img);
}

相关Demo已上传至Github, Git地址

Demo中只支持文本绘制,添加图片的实现方式其实与文字差不多,有兴趣的小伙伴可以尝试。我结合了相机及水印,想单独调用的是可以代码分离的哦,耦合度较低。

Android调用相机拍照并添加水印相关推荐

  1. Android中拍照完就给图片加水印,Android调用相机拍照并添加水印

    最近项目提出新需求,要求在拍照后在图片上添加用户信息的水印,so,楼主重新整理封装了一个简单的拍照+水印的工具类,需求不同,仅供参考.文件操作及权限做了适配,支持androidN及以上版本,放心使用. ...

  2. android 调用相机拍照。适配到 Android 10

    Photograph 项目地址:donkingliang/Photograph 简介: android 调用相机拍照.适配到 Android 10 更多:作者   提 Bug 标签: android ...

  3. Android调用相机拍照高清原图(兼容7.0)

    在安卓更新7.0的版本后,要调用相机拍照获取原图则需要先把拍摄后的内容保存到目录,然后再借助provider调出来显示,相比以前可以说十分繁琐,但为了摆脱马赛克画质的困扰,为了更好的用户体验,还是硬着 ...

  4. Android 调用相机拍照并保存

    不知不觉已经两年多已经没有写文章了,转眼间大学都要毕业了,也是有些唏嘘,今后会定期发表些文章,应该会以Android为主,也会夹杂其他领域的一些文章. 话不多说,今天做了一个小demo,就是调用相机拍 ...

  5. android相机拍照代码,Android 调用相机拍照,适配到Android 10

    今天写的博客是关于Android调用手机相机拍照并显示图片.这是一个很常用的功能,并且这个功能在Android6.0.7.0.10.0等版本上实现都有所不同,需要对Android各个版本进行兼容适配, ...

  6. android 拍照换头像,Android调用相机拍照,裁剪及更换头像功能的实现

    1,点击弹出popwindow,选择相机或者相册 这个就不多说了,在OnclickListener里写弹出的窗口位置和样式. 2,选择拍照功能,调用手机相机. ``` //调用相机 Intent in ...

  7. Android 调用相机拍照,适配到Android 10,2021必看

    //拒绝权限,弹出提示框. Toast.makeText(this,"拍照权限被拒绝",Toast.LENGTH_LONG).show(); } } } 申请权限后,就可以调起相机 ...

  8. android调用相机拍照返回的照片大小太小,变得模糊

    1.使用相机拍照默认情况下调用相机的方式: Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);startActivityForRe ...

  9. android 调用系统相机拍照并返回路径,Android调用相机拍照并返回路径和…

    调用系统图库: Intent intent = new Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI); ...

最新文章

  1. java冒泡排序_Java中的经典算法之冒泡排序(Bubble Sort)
  2. [SCOI2008]奖励关(期望dp)
  3. shell学习之常用命令总结
  4. NodeJS开发环境配置
  5. Hadoop入门(二十一)Mapreduce的求和程序
  6. Android Ubuntu 安装问题FAQ
  7. Dubbo消费者代理的调用
  8. java 内存溢出分析_用一段时间后java内存溢出问题分析(转)
  9. 十面阿里Java程序员终拿下阿里P6offer
  10. PyTorch中hook函数的学习笔记
  11. c语言课程设计报告猜数字,猜数字游戏C语言课程设计报告书.docx
  12. 云服务器部署 Web 项目
  13. sql1428N错误
  14. Banner打造广告自动轮播图
  15. 新版Jenkins关闭CSRF“HTTP ERROR 403 No valid crumb was included in the request“
  16. 又是一个相当 带劲的招聘起事
  17. php怎么检测数据类型,PHP检测数据类型的几种方法总结和技巧
  18. 抓取淘宝司法拍卖数据
  19. java中构造方法的理解,super()与构造方法,无参,有参构造方法,this()与构造方法
  20. 编程实用工具大全(前后端皆可用,不来瞅瞅?)

热门文章

  1. 这些IT经典书都是蓝色封面,你读了吗?
  2. 早上9.10分左右上高架被拍了!
  3. ygo游戏王卡组_游戏王卡组第六弹:游戏衍生卡组最风光的卡组之一,完全碾压社长...
  4. android videoview 播放之前短暂黑屏
  5. 阿里云AI训练营-机器学习3:LightGBM
  6. 全局描述符表GDT-第一部分
  7. 设计模式剖析——抽象工厂模式Abstract Factory Pattern
  8. mailto 参数讲解
  9. 用Python解析WinMerge生成的Patch文件
  10. android华为和小米,同样是升安卓9.0,为何华为和小米的差距这么大?