Android給圖片添加水印

1. 前言

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. 最終實現的效果如下

5.總結

整體來說沒有什麼太大的問題,添加水印的原理就是通過Canvas繪制的方式將文字/圖片添加到圖片上。最後再將修改之後的圖片呈現給用戶。同時也記錄下SurfaceView截圖黑屏的問題。

以上就是Android實現添加水印功能的詳細內容,更多關於Android 添加水印的資料請關註WalkonNet其它相關文章!

推薦閱讀: