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:
-
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.
-
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.