1. 前言

ps:最近在项目执行过程中有这样一个需求,要求拍完照的图片必须达到以上的效果。需求分析:
- 使用用预览布局surfaceview,在不局上方使用控件的方式来进行设计,最后通过截图的方式将画面进行保存。
- 使用图片添加水印的方式来完成。
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. 最终实现的效果如下

5.总结
整体来说没有什么太大的问题,添加水印的原理就是通过canvas绘制的方式将文字/图片添加到图片上。最后再将修改之后的图片呈现给用户。同时也记录下surfaceview截图黑屏的问题。
以上就是android实现添加水印功能的详细内容,更多关于android 添加水印的资料请关注其它相关文章!
女朋友专卖店