Android如何做出带有复杂水印的图片
最近项目中存在图片加水印效果的需求,具体效果如下:
![](/assets/blank.gif)
然后做出来的效果如下:
原图 | 水印图 |
---|---|
![]() |
![]() |
点击可以查看大图:大图
那么针对这种比较复杂的水印图片,应该如何去做呢?下面我分享一下自己的思路。
如果没有使用到NDK,单纯的使用Android提供的Canvas画布,那么就有一下几个步骤:
- 获取原始的图片地址,转化成为 sourceBitmap;
- 获取水印图片的Bitmap;
- 使用Canvas,将sourceBitmap作为底片,然后将水印Bitmap画上去;
- 然后将二者合并的Bitmap,保存成文件即可。
那么按照这个步骤来:
1. 原始图片赚Bitmap
这个一般很简单,用代码表示为:
Bitmap sourceBitmap = BitmapFactory.decodeFile(sourcePath);
2. 获取水印图片的Bitmap
对于复杂的水印图片,我现在的做法是,把水印图片转化成一个View,然后把View转成Bitmap。
比如上图的复杂水印图片,我们可以先画一个XML:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="wrap_content"android:padding="@dimen/dp_18"><TextViewandroid:id="@+id/id_tv_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="40sp"style="@style/phone_water_mark_text_style"android:textStyle="bold"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><Viewandroid:layout_width="@dimen/dp_2"app:layout_constraintTop_toTopOf="@id/id_tv_time"app:layout_constraintBottom_toBottomOf="@id/id_tv_time"app:layout_constraintStart_toEndOf="@id/id_tv_time"android:layout_marginStart="@dimen/dp_8"android:background="@color/app_theme_color"android:id="@+id/id_center_view"android:layout_marginTop="@dimen/dp_10"android:layout_marginBottom="@dimen/dp_4"android:layout_height="0dp"/><TextViewandroid:layout_width="wrap_content"app:layout_constraintTop_toTopOf="@id/id_tv_time"app:layout_constraintStart_toEndOf="@id/id_center_view"android:layout_marginStart="@dimen/dp_8"android:textSize="14sp"style="@style/phone_water_mark_text_style"android:layout_marginTop="@dimen/dp_6"android:id="@+id/id_tv_date"android:layout_height="wrap_content"/><TextViewandroid:layout_width="wrap_content"app:layout_constraintBottom_toBottomOf="@id/id_tv_time"app:layout_constraintStart_toStartOf="@id/id_tv_date"style="@style/phone_water_mark_text_style"android:textSize="14sp"android:id="@+id/id_tv_week"android:layout_marginBottom="@dimen/dp_4"android:layout_height="wrap_content"/><LinearLayoutandroid:layout_width="0dp"android:orientation="vertical"app:layout_constraintTop_toBottomOf="@id/id_tv_time"android:layout_marginTop="@dimen/dp_8"app:layout_constraintStart_toStartOf="@id/id_tv_time"app:layout_constraintEnd_toEndOf="parent"android:id="@+id/id_external_info_layout"android:layout_height="wrap_content"><TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/id_tv_user_name"/><TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/id_tv_user_location" /></LinearLayout></androidx.constraintlayout.widget.ConstraintLayout>
XML解析之后,大致的轮廓我们可以看到:
我们可以看到,XML渲染的大致轮廓就出来了,那么应该如果将View转成Bitmap呢?其实Android早就提供了类似的方法:
fun getCurrentBitmap(): Bitmap? {invalidate()val density = resources.displayMetrics.densityBaseLog.d("density = $density")val bitmapWidth = (width * density).toInt()val bitmapHeight = (height * density).toInt()val resultBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)val canvas = Canvas(resultBitmap)canvas.scale(density, density)draw(canvas)return resultBitmap}
代码中我新增了 density,这是为了防止不同屏幕密度下UI效果不同做的一个缩放。
3.4. Bitmap上面画水印Bitmap, 然后保存
还是先上代码吧,注释也比较清晰:
/*** 添加水印** @param sourcePath 原始图片地址* @param watermarkBitmap 图片水印* @return 添加水印后的图片地址*/public String addWaterMarkLayout(String sourcePath, Bitmap watermarkBitmap) {// 检查文件是否存在if (!BaseFileUtils.isFileExist(sourcePath)) {return sourcePath;}// 检查水印图片是否存在if (null == watermarkBitmap){BaseLog.e("watermark bitmap is null");return sourcePath ;}int[] bitmapWidthHeight = BaseImageUtils.getBitmapWidthHeight(sourcePath);if (bitmapWidthHeight[0] <= 0 || bitmapWidthHeight[1] <= 0) {return sourcePath;}Bitmap sourceBitmap = BitmapFactory.decodeFile(sourcePath);if (null == sourceBitmap){BaseLog.e("source bitmap is null : " + sourcePath);return sourcePath;}//创建一个底仓BitmapBitmap copiedBitmap = Bitmap.createBitmap(sourceBitmap.getWidth(), sourceBitmap.getHeight(), Bitmap.Config.ARGB_8888);Canvas targetCanvas = new Canvas(copiedBitmap);targetCanvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));targetCanvas.drawBitmap(sourceBitmap, 0,0,null);// 获取watermarkBitmap 和 sourceBitmap之间的比例,好进行缩放int[] getSample = checkSample(sourceBitmap, watermarkBitmap, BaseImageUtils.getOrientation(sourcePath));float scaleWidth = getSample[0] * 1.0f / watermarkBitmap.getWidth();float scaleHeight = getSample[1] * 1.0f / watermarkBitmap.getHeight();// CREATE A MATRIX FOR THE MANIPULATIONMatrix matrix = new Matrix();// RESIZE THE BIT MAPmatrix.postScale(scaleWidth, scaleHeight);//true : 以启用双线性过滤 为了就是不产生锯齿Bitmap scaledBitmap = Bitmap.createBitmap(watermarkBitmap,0,0,watermarkBitmap.getWidth(),watermarkBitmap.getHeight(),matrix,true);Paint canvasPaint = new Paint();//抗锯齿canvasPaint.setAntiAlias(true);//双线性过滤canvasPaint.setFilterBitmap(true);// 画目标水印Bitmap// 因为是画的位置是 左下角,因此x为0,y为底座Bitmap的高度 - 缩放之后的水印Bitmap的高度 targetCanvas.drawBitmap(scaledBitmap, 0, copiedBitmap.getHeight() - getSample[1], canvasPaint);//创建一个新的临时地址,请来保存合并之后的BitmapString newWaterMarkPath = BaseIOUtils.getTempImageFilePath(context);//保存合并之后的Bitmap到临时文件LzMarkBitmapUtils.saveBitmapToFile(copiedBitmap, newWaterMarkPath,85);bitmapWidthHeight = BaseImageUtils.getBitmapWidthHeight(newWaterMarkPath);if (bitmapWidthHeight[0] > 0 && bitmapWidthHeight[1] > 0) {//不能删除原始图片,因为Android系统会拦截BaseIOUtils.renameUnusedFileData(sourcePath);return newWaterMarkPath;}BaseLog.e("bitmapWidthHeight error");return sourcePath;}private int[] checkSample(Bitmap sourceBitmap, Bitmap waterMarkBitmap, int getOrientation) {int sourceBitmapWidth = sourceBitmap.getWidth();int sourceBitmapHeight = sourceBitmap.getHeight();int waterMarkWidth = waterMarkBitmap.getWidth();int waterMarkHeight = waterMarkBitmap.getHeight();BaseLog.i("sample:" + sourceBitmapWidth + "," + sourceBitmapHeight+ "," + waterMarkWidth + "," + waterMarkHeight + "," + getOrientation);if (getOrientation == 90 || getOrientation == 270){ //横向sourceBitmapWidth = sourceBitmap.getHeight();if (waterMarkWidth > sourceBitmapWidth * 0.5f){waterMarkWidth = (int) (sourceBitmapWidth * 0.65f);waterMarkHeight = waterMarkBitmap.getHeight() * waterMarkWidth / waterMarkBitmap.getWidth();}} else {if (waterMarkWidth > sourceBitmapWidth * 0.8f) {waterMarkWidth = (int) (sourceBitmapWidth * 0.8f);waterMarkHeight = waterMarkBitmap.getHeight() * waterMarkWidth / waterMarkBitmap.getWidth();}}BaseLog.i("show the water mark width :" + waterMarkWidth + "," + waterMarkHeight);return new int[]{(int) (waterMarkWidth * 1.3f), (int) (waterMarkHeight * 1.3f)};}
使用过程中出现的问题
1. 压缩问题
生成的水印图片需要上传到服务器的,为了节省带宽和流量,因此压缩是必不可少少的。我在很长一段时间内,采取的步骤为:
原始Bitmap + 水印Bitmap -> 生成带有水印的图片 -> 压缩 -> 上传
发现效果比较差,水印比较模糊,尝试了很多种方法,比如使用Paint防锯齿,先放大水印图片等等,但是效果不是很好,后来采取了另外一种思路:
原始Bitmap -> 压缩 -> 保存成文件 -> 转化成新的Bitmap + 水印Bitmap -> 合成新的Bitmap -> 保存成JPG文件 -> 上传
和上面的思路换了之后,我只合成压缩之后的Bitmap,对水印的Bitmap不进行压缩,发现效果很好。
2. 字体比较小的TextView比较模糊
对于View上的TextView如果设置字体比较小,生成的Bitmap合并之后会发现比较模糊,这可能是因为View的分辨率和最终生成的Bitmap分辨率不一致所导致的。为了解决这个问题,可以从以下几个点考虑:
- 调整View的分辨率
当创建水印Bitmap时,确保View的尺寸与水印Bitmap的尺寸一致。你可以通过设置View的LayoutParams来调整View的尺寸
- 使用高质量的缩放方法
在使用Bitmap.createBitmap()时,确保使用高质量的缩放方法来生成带有水印的图片。使用Bitmap.createScaledBitmap()方法可以实现这一目的。
Bitmap originalBitmap = ...; // 原始图片
Bitmap watermarkBitmap = ...; // 水印图片
int watermarkWidth = watermarkBitmap.getWidth();
int watermarkHeight = watermarkBitmap.getHeight();// 缩放水印图片
int newWatermarkWidth = ...; // 新的水印宽度
int newWatermarkHeight = ...; // 新的水印高度
Bitmap scaledWatermarkBitmap = Bitmap.createScaledBitmap(watermarkBitmap, newWatermarkWidth, newWatermarkHeight, true);
- 使用适当的质量参数进行Bitmap保存
在将Bitmap保存为JPEG或PNG格式的图片时,确保使用适当的质量参数。一般情况下,JPEG的质量参数范围是0-100,PNG是无损压缩,质量参数对其没有影响。
Bitmap finalBitmap = ...; // 最终生成的带有水印的图片
FileOutputStream outputStream = ...; // 输出流
int quality = 100; // 质量参数finalBitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
基本上一个稍微负责的水印图片,我们基本上就成功了,如果有什么疑问或者有什么错误,请及时指出,或者可以联系我wx:javainstalling,备注:水印 即可。
Android如何做出带有复杂水印的图片相关推荐
- 新生研讨课:利用OpenCV处理带有水印的图片的调研报告
电子科技大学 格拉斯哥学院 2017级 2017200602038 席文骥 目录 1.引言 2.技术背景 3.具体问题 4.实现方法 5.结语 6.参考 1.引言 在我们学院去年开设的新生研讨课上,曾 ...
- android 涂鸦之图片叠加,android图像处理系列之七--图片涂鸦,水印-图片叠加...
图片涂鸦和水印其实是一个功能,实现的方式是一样的,就是一张大图片和一张小点图片叠加即可.前面在android图像处理系列之六--给图片添加边框(下)-图片叠加中也讲到了图片叠加,里面实现的原理是直接操 ...
- android水印控件,Android图片添加文字水印并保存水印文字图片到指定文件
Android图片添加文字水印并保存水印文字图片到指定文件package zhangphil.test;import android.graphics.Bitmap;import android.gr ...
- 不会ps可不可以html5开发,H5周边丨不用ps如何做出一张好看的图片
1. 创客贴 网址: https://www.chuangkit.com/dc.html 功能:如果你只是想制作一张由背景和文字构成的图片,放在H5里面,堂妹儿比较推荐"创客贴"这 ...
- Android 使用ViewPager 做的半吊子的图片轮播
Android 使用ViewPager 做的半吊子的图片轮播 效果图 虽然不咋样,但是最起码的功能是实现了,下面我们来一步步的实现它. 界面 下面我们来分析一下界面的构成 整体的布局: 因为我们要做出 ...
- css3 实现盒子四周光晕_使用CSS3做出带有光晕流星旋转光环的效果 -
...章给大家带来的内容是关于CSS3属性:text-shadow文本阴影的使用方法,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助.text-shadow还没有出现时,大家在网页设计中 ...
- Android:加载网图时精确获取图片格式
一.开始挖坑 项目中有一个点击查看大图的需求,并且在大图模式下支持手势缩放,所以,我们必然会用到 chrisbanes 大神的 PhotoView,主要使用的是其中的PhotoView 和 Photo ...
- Graphics2D 在一张图片上添加一个带有透明背景的图片或绘制透明图片
目录 代码实例 代码实例 // 读取原图片信息 底图//得到文件File file = new File("d:\\1.png");//文件转化为图片Image srcImg = ...
- 怎么消除图片中的水印?图片去水印工具
在工作中当我们在平台上找一些图片素材发现带有水印,那么如何获得一张无水印的图片呢? 第一步,打开浏览器搜索工具名称,进入官方网站上点击获取它,即可获取.然后我们点击启动,进入首页,我们可以看到它各种强 ...
最新文章
- 当前订单不支持只花呗支付是什么意思_1、(跑腿介绍篇)支付宝花呗分期线下推广...
- 机器学习实战读书笔记--决策树
- shiro表单认证(系统默认的form认证器)
- 【技术综述】深度学习中的数据增强(下)
- java collection api_Java Stream和Collection比较:何时以及如何从Java API返回?
- gson-2.2.api简单
- 你为什么喜欢VIM?
- Jmeter中JDBC Connection Configuration实现MySQL JDBC Request数据库处理
- STM32_ADC初始化参数说明以及常用的固件库
- Android 进行单元測试难在哪-part3
- 我国三大常用坐标系区别(北京54、西安80和WGS-84)
- 一个月通过软考中级软件设计师
- 使用Python的pandas库操作Excel
- Mybatis_select、insert、update、delete常用属性
- opencv warp(扭曲)球面投影的原理
- 论文阅读笔记 | Enemy At the Gateways:Censorship-Resilient Proxy Distribution Using Game Theory(NDSS 2019)
- 小知识--Windows10许可证即将过期
- 毕业论文查重软件如何论文查重?
- 一个有意思的echarts3D树状图
- druid的后台监控