1. Introduction

PS: Recently, in the process of project implementation, there is a requirement that the pictures taken must achieve the above effect. Demand analysis:

  1. The use of preview layout SurfaceView, in the upper part of the use of controls to design, and finally through the screenshot of the way to save the screen.

  2. Use the image to add watermark way to complete.

2. Method 1 uses the SurfaceView

I thought, isn’t that easy? I started balabala, but at the last step, the SurfaceView couldn’t take a screenshot, and the screenshot turned out to be black. This is simply due to the nature of the SurfaceView, and we know that the only view in Android that can draw in child threads is the SurfaceView. It can be drawn independently from the child thread, and will not cause the main thread to freeze. As for the cause of the black screen of the surfaceView, you can move here to analyze the implementation principle of the Android view. If you must use this approach, there are three ways to solve the problem:

1. Obtain the screenshot of the source video as the screenshot of the SurfaceView 2. Get the Canvas of the SurfaceView and save the canvas as a Bitmap. Directly capture the entire screen, and then in the screenshot SurfaceView position diagramCopy the code

But I think this way is too tedious, so I choose to use the watermark to complete.

3. Method 2 Add watermarks to the pictures taken

Step 1: Get permission to take photos

<! -- Camera permission --> <uses-permission Android :name="android.permission.CAMERA"/ > <! --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Copy the code

Here use guo Lin’s open source library PermissionX to obtain permission:

PermissionX.init(this)
    .permissions(Manifest.permission.CAMERA,  Manifest.permission.RECORD_AUDIO)
    .onExplainRequestReason { scope, deniedList ->
        val message = "You need to agree with the following permissions to normal use."
        scope.showRequestReasonDialog(deniedList, message, "Sure"."Cancel")
    }
    .request { allGranted, grantedList, deniedList ->
        if (allGranted) {
            openCamera()
        } else {
            Toast.makeText(activity, "You denied the following permission: $deniedList", Toast.LENGTH_SHORT).show()
        }
    }
Copy the code

Step 2: Take pictures

After Android 6.0, camera permissions need to be applied dynamically.

 // requestCode for camera permissions
   private static final int PERMISSION_CAMERA_REQUEST_CODE = 0x00000012;

   /** * Check permissions and take photos. * Check permissions before calling the camera. * /
   private void checkPermissionAndCamera(a) {
       int hasCameraPermission = ContextCompat.checkSelfPermission(getApplication(),
               Manifest.permission.CAMERA);
       if (hasCameraPermission == PackageManager.PERMISSION_GRANTED) {
           // Set the camera to take photos.
           openCamera();
       } else {
           // No permission, apply for permission.
           ActivityCompat.requestPermissions(this.newString[]{Manifest.permission.CAMERA}, PERMISSION_CAMERA_REQUEST_CODE); }}/** * Handle permission request callback. * /
   @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) {
               // Set the camera to take photos.
               openCamera();
           } else {
               // Deny permission, a dialog box is displayed.
               Toast.makeText(this."Photo permission denied.",Toast.LENGTH_LONG).show(); }}}Copy the code

Call the camera to take a picture

After applying for permission, you can tune up your camera and take a picture. To call the camera, you simply call startActivityForResult and upload an Intent. However, this Intent needs to pass a URI that is used to save the image. Creating this URI varies from Android to Android and requires version-compatibility.

  // The URI used to save the photo taken
    private Uri mCameraUri;

    // The file path used to save images. Android 10 below uses the image path to access images
    private String mCameraImagePath;

    // Check whether the phone is Android 10 or higher
    private boolean isAndroidQ = Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q;

    /** * Set the camera to take photos */
    private void openCamera(a) {
        Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // Check whether there is a camera
        if(captureIntent.resolveActivity(getPackageManager()) ! =null) {
            File photoFile = null;
            Uri photoUri = null;

            if (isAndroidQ) {
                // For 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) {
                        // Apply Android 7.0 file permissions to create a Uri of the content type through the FileProvider
                        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); }}}/** * Create the image address URI, which is used to save the photo after the photo is taken
    private Uri createImageUri(a) {
        String status = Environment.getExternalStorageState();
        // Check whether there is an SD card. SD card storage is preferred. If there is no SD card, use mobile phone storage
        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, newContentValues()); }}/** * Create a file to save the image */
    private File createImageFile(a) 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;
    }
Copy the code

Receiving photo result

  @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 uses image URIs to load
                    ivPhoto.setImageURI(mCameraUri);
                } else {
                    // Use the image path to loadivPhoto.setImageBitmap(BitmapFactory.decodeFile(mCameraImagePath)); }}else {
                Toast.makeText(this."Cancel",Toast.LENGTH_LONG).show(); }}}Copy the code

Note:

In Android 10, due to file permissions, images displayed on the phone’s storage card cannot be directly loaded using the image path, but need to be loaded using the image URI.

In addition, although I use different methods to create URIs and load images for Android 10 and phones under 10 here, the way to create URIs for Android 10 and the way to load images using URIs for phones under 10 work equally well. Android 7.0 requires file sharing to be configured.

<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>
Copy the code

Create the folder XML in the res directory, place a file named file_paths.xml(you can choose the name of the file), and configure the file directory to be shared, that is, the directory to save the photos.

<? xml version="1.0" encoding="utf-8"? > <resources> <paths> <! -- This is the path to save the photo, must be configured. --> <external-files-path name="images"
            path="Pictures" />
    </paths>
</resources>
Copy the code

Step 3: Add watermark to the picture after taking a photo

  @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 uses image URIs to load
                        mp = MediaStore.Images.Media.getBitmap(this.contentResolver, t.uri);
                    } else {
                        // Android 10 uses the image path
                        mp = BitmapFactory.decodeFile(uri);
                    }
                    // Add a watermark to an image as an example:
                    ImageUtil.drawTextToLeftTop(this,mp,"Sample text".30,R.color.black,20.30)}else {
                Toast.makeText(this."Cancel",Toast.LENGTH_LONG).show(); }}}Copy the code

There is an ImageUtil utility class that I have pasted here. You can take it away if you need it

public class ImageUtil {
    /** * set the watermark image in the upper left corner **@paramContext *@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();
        // Create a bitmap
        Bitmap newb = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);// Create a new bitmap with the same length and width as SRC
        // Use the image as a canvas
        Canvas canvas = new Canvas(newb);
        // start drawing the original image at coordinates 0,0 on the canvas
        canvas.drawBitmap(src, 0.0.null);
        // Paint a watermarked image on the canvas
        canvas.drawBitmap(watermark, paddingLeft, paddingTop, null);
        / / save
        canvas.save(Canvas.ALL_SAVE_FLAG);
        / / store
        canvas.restore();
        return newb;
    }

    /** * set the watermark image in the lower right corner **@paramContext *@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));
    }

    /** * set the watermark image to the upper right corner **@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));
    }

    /** * set the watermark image to the lower left corner **@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));
    }

    /** * set the watermark image to the middle **@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);
    }

    /** * add text to the image in the upper left corner **@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());
    }

    /** * draw the text to the lower right corner **@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));
    }

    /** * draw text to the upper right **@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());
    }

    /** * draw text to the lower left **@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));
    }

    /** * draw text to middle **@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);
    }

    // Draw text on the picture
    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); // Get a clear image sample
        paint.setFilterBitmap(true);// Filter some
        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;
    }

    /** * Zoom the image **@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 {
            // Record the width and height of SRC
            int width = src.getWidth();
            int height = src.getHeight();
            // Create a matrix container
            Matrix matrix = new Matrix();
            // Calculate the scale
            float scaleWidth = (float) (w / width);
            float scaleHeight = (float) (h / height);
            // Start scaling
            matrix.postScale(scaleWidth, scaleHeight);
            // Create a scaled image
            return Bitmap.createBitmap(src, 0.0, width, height, matrix, true); }}/** * dip to 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.5 f); }}Copy the code

4. The final effect is as follows:

5. To summarize

Generally speaking, there is no big problem. The principle of adding watermarks is to add text/pictures to pictures by drawing on Canvas. Finally, the modified picture is presented to the user. Also record the SurfaceView screenshot black screen problem.