Android給圖片添加水印
1. 前言

PS:最近在項目執(zhí)行過程中有這樣一個需求,要求拍完照的圖片必須達到以上的效果。需求分析:
- 使用用預覽布局SurfaceView,在不局上方使用控件的方式來進行設(shè)計,最后通過截圖的方式將畫面進行保存。
- 使用圖片添加水印的方式來完成。
2. 方法1 使用SurfaceView
我心想這不簡單嗎?于是開始一頓balabala的操作,結(jié)果到最后一步時發(fā)現(xiàn),SurfaceView居然不能進行截圖,截圖下來的圖片居然是一張黑色的。簡單地說這是因為SurfaceView的特性決定的,我們知道安卓中唯一可以在子線程中進行繪制的view就只有Surfaceview了。他可以獨立于子線程中繪制,不會導致主線程的卡頓,至于造成surfaceView黑屏的原因,可以移步這里 Android視圖SurfaceView的實現(xiàn)原理分析。如果非要使用此方式時還是有三種思路來進行解決: 采用三種思路:
1. 獲取源頭視頻的截圖作為SurfaceView的截圖
2. 獲取SurfaceView的畫布canvas,將canvas保存成Bitmap
3. 直接截取整個屏幕,然后在截圖SurfaceView位置的圖
但是我覺得這種方式太過繁瑣,所以選擇用添加水印的式來完成。
3. 方法2 給拍照下來的圖片添加水印
第一步:獲取拍照權(quán)限
<!--相機權(quán)限--> <uses-permission android:name="android.permission.CAMERA" /> <!--訪問外部權(quán)限--> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
這里使用到郭霖大佬的開源庫PermissionX獲取權(quán)限:
PermissionX.init(this)
.permissions(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
.onExplainRequestReason { scope, deniedList ->
val message = "需要您同意以下權(quán)限才能正常使用"
scope.showRequestReasonDialog(deniedList, message, "確定", "取消")
}
.request { allGranted, grantedList, deniedList ->
if (allGranted) {
openCamera()
} else {
Toast.makeText(activity, "您拒絕了如下權(quán)限:$deniedList", Toast.LENGTH_SHORT).show()
}
}
第二步:拍照
android 6.0以后,相機權(quán)限需要動態(tài)申請。
// 申請相機權(quán)限的requestCode
private static final int PERMISSION_CAMERA_REQUEST_CODE = 0x00000012;
/**
* 檢查權(quán)限并拍照。
* 調(diào)用相機前先檢查權(quán)限。
*/
private void checkPermissionAndCamera() {
int hasCameraPermission = ContextCompat.checkSelfPermission(getApplication(),
Manifest.permission.CAMERA);
if (hasCameraPermission == PackageManager.PERMISSION_GRANTED) {
//有調(diào)起相機拍照。
openCamera();
} else {
//沒有權(quán)限,申請權(quán)限。
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA},
PERMISSION_CAMERA_REQUEST_CODE);
}
}
/**
* 處理權(quán)限申請的回調(diào)。
*/
@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) {
//允許權(quán)限,有調(diào)起相機拍照。
openCamera();
} else {
//拒絕權(quán)限,彈出提示框。
Toast.makeText(this,"拍照權(quán)限被拒絕",Toast.LENGTH_LONG).show();
}
}
}
調(diào)用相機進行拍照
申請權(quán)限后,就可以調(diào)起相機拍照了。調(diào)用相機只需要調(diào)用startActivityForResult傳一個Intent就可以了,但是這個Intent需要傳遞一個uri,用于保存拍出來的圖片,創(chuàng)建這個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;
/**
* 調(diào)起相機拍照
*/
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文件權(quán)限,通過FileProvider創(chuàng)建一個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);
}
}
}
/**
* 創(chuàng)建圖片地址uri,用于保存拍照后的照片 Android 10以后使用這種方法
*/
private Uri createImageUri() {
String status = Environment.getExternalStorageState();
// 判斷是否有SD卡,優(yōu)先使用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());
}
}
/**
* 創(chuàng)建保存圖片的文件
*/
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;
}
接收拍照結(jié)果
@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由于文件權(quán)限的關(guān)系,顯示手機儲存卡里的圖片不能直接使用圖片路徑,需要使用圖片uri加載。
另外雖然我在這里對Android 10和10以下的手機使用了不同的方式創(chuàng)建uri 和加載圖片,但其實Android 10創(chuàng)建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目錄下創(chuàng)建文件夾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 {
/**
* 設(shè)置水印圖片在左上角
*
* @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();
//創(chuàng)建一個bitmap
Bitmap newb = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);// 創(chuàng)建一個新的和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;
}
/**
* 設(shè)置水印圖片在右下角
*
* @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));
}
/**
* 設(shè)置水印圖片到右上角
*
* @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));
}
/**
* 設(shè)置水印圖片到左下角
*
* @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));
}
/**
* 設(shè)置水印圖片到中間
*
* @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();
// 創(chuàng)建一個matrix容器
Matrix matrix = new Matrix();
// 計算縮放比例
float scaleWidth = (float) (w / width);
float scaleHeight = (float) (h / height);
// 開始縮放
matrix.postScale(scaleWidth, scaleHeight);
// 創(chuàng)建縮放后的圖片
return Bitmap.createBitmap(src, 0, 0, width, height, matrix, true);
}
}
/**
* dip轉(zhuǎn)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. 最終實現(xiàn)的效果如下

5.總結(jié)
整體來說沒有什么太大的問題,添加水印的原理就是通過Canvas繪制的方式將文字/圖片添加到圖片上。最后再將修改之后的圖片呈現(xiàn)給用戶。同時也記錄下SurfaceView截圖黑屏的問題。
以上就是Android實現(xiàn)添加水印功能的詳細內(nèi)容,更多關(guān)于Android 添加水印的資料請關(guān)注腳本之家其它相關(guān)文章!
- Android使用Opengl錄像時添加水印
- android實現(xiàn)文字水印效果 支持多行水印
- Android圖片添加水印圖片并把圖片保存到文件存儲的實現(xiàn)代碼
- Android給任何view添加全屏傾斜水印
- Android 給圖片加上水印的示例代碼(支持logo+文字)
- Android 圖片添加水印的實現(xiàn)方法
- Android給圖片加文字和圖片水印實例代碼
- Android實現(xiàn)分享長圖并且添加全圖水印
- android使用ItemDecoration給RecyclerView 添加水印
- Android實現(xiàn)為圖片添加水印
- Android添加水印的正確方法 只要三步!
- Android視頻處理之動態(tài)時間水印效果
相關(guān)文章
Android Compose自定義TextField實現(xiàn)自定義的輸入框
眾所周知Compose中默認的TextField和OutlineTextField樣式并不能滿足所有的使用場景,所以自定義TextField就成了必備技能。本文將自定義TextField實現(xiàn)自定義的輸入框,感興趣的可以了解一下2022-03-03

