There are two ways to capture and crop a screen: early capture and late capture. Early screenshots, is the first interception of the full screen, and then let the user to intercept the picture to modify; On the other hand, late screenshots allow users to delimit a region on the screen first, and then take screenshots and cropping. In fact, there is no big difference between the two, this article is about how to achieve late screenshots.

Late screenshots can be divided into three steps:

  1. Mark the rectangular area of the screenshot on the screen
  2. Call system interface screenshot
  3. Crop the screenshot

The renderings are as follows:





Screenshot of rectangle area

Step 1: Mark the screenshot area on the screen

First determine the functionality needed to identify the screenshot area:

  1. Drag the finger to form a rectangular area;
  2. You can drag the delimited rectangular area to move;
  3. You can resize the border of the rectangle by dragging it.
  4. After the selection is complete, there are “Confirm” and “cancel” functions, “confirm” can obtain the location of the selected area. It is important to note that the position of the button should be adaptive, such as placing the button inside the marquee if the marquee takes up almost all of the screen.

The easiest way to do this is to write a custom View that performs different functions depending on the location of the touch. The implementation is very simple, as long as you carefully put each state on the line, the code see Bigbang project MarkSizeView class.

The second step is to call the system interface to take a screenshot

The screenshot must be taken in the Activity because startActivityForResult() is called. However, you can also pass the mMediaProjectionManager to a service for subsequent processing. Also note that the Activity itself should be transparent during screenshots and should not affect the content being captured. Look directly at the code:

public class ScreenCaptureActivity extends Activity { private static final String TAG = ScreenCaptureActivity.class.getName(); private MediaProjectionManager mMediaProjectionManager; private int REQUEST_MEDIA_PROJECTION = 1; private SimpleDateFormat dateFormat; private String pathImage; private WindowManager mWindowManager; private ImageReader mImageReader; private MediaProjection mMediaProjection; private int mResultCode; private Intent mResultData; private VirtualDisplay mVirtualDisplay; private String strDate; private int windowWidth; private int windowHeight; private String nameImage; private int mScreenDensity; @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mMediaProjectionManager = (MediaProjectionManager) getApplication().getSystemService(Context.MEDIA_PROJECTION_SERVICE); createVirtualEnvironment(); startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_MEDIA_PROJECTION) { if (resultCode ! = Activity.RESULT_OK) { return; } else if (data ! = null && resultCode ! = 0) { mResultCode = resultCode; mResultData = data; startVirtual(); new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { startCapture(); }}, 100); } } } @RequiresApi(api = Build.VERSION_CODES.KITKAT) private void createVirtualEnvironment() { dateFormat = new SimpleDateFormat("yyyy_MM_dd_hh_mm_ss"); strDate = dateFormat.format(new Date()); pathImage = Environment.getExternalStorageDirectory().getPath() + "/Pictures/"; nameImage = pathImage + strDate + ".png"; mMediaProjectionManager = (MediaProjectionManager) getApplication().getSystemService(Context.MEDIA_PROJECTION_SERVICE); mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE); windowWidth = mWindowManager.getDefaultDisplay().getWidth(); windowHeight = mWindowManager.getDefaultDisplay().getHeight(); DisplayMetrics metrics = new DisplayMetrics(); mWindowManager.getDefaultDisplay().getMetrics(metrics); mScreenDensity = metrics.densityDpi; mImageReader = ImageReader.newInstance(windowWidth, windowHeight, 0x1, 2); //ImageFormat.RGB_565 Log.i(TAG, "prepared the virtual environment"); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void startVirtual() { if (mMediaProjection ! = null) { Log.i(TAG, "want to display virtual"); virtualDisplay(); } else { Log.i(TAG, "start screen capture intent"); Log.i(TAG, "want to build mediaprojection and display virtual"); setUpMediaProjection(); virtualDisplay(); } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void setUpMediaProjection() { mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData); Log.i(TAG, "mMediaProjection defined"); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void virtualDisplay() { mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror", windowWidth, windowHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mImageReader.getSurface(), null, null); Log.i(TAG, "virtual displayed"); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void startCapture() { strDate = dateFormat.format(new java.util.Date()); nameImage = pathImage + strDate + ".png"; Image image = mImageReader.acquireLatestImage(); int width = image.getWidth(); int height = image.getHeight(); final Image.Plane[] planes = image.getPlanes(); final ByteBuffer buffer = planes[0].getBuffer(); int pixelStride = planes[0].getPixelStride(); int rowStride = planes[0].getRowStride(); int rowPadding = rowStride - pixelStride * width; Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height); image.close(); Log.i(TAG, "image data captured"); // Save the screenshot result. If you want to crop the image, process the bitmap here if (bitmap! = null) { try { File fileImage = new File(nameImage); if (! fileImage.exists()) { fileImage.createNewFile(); Log.i(TAG, "image file created"); } FileOutputStream out = new FileOutputStream(fileImage); if (out ! = null) { bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); out.flush(); out.close(); Intent media = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); Uri contentUri = Uri.fromFile(fileImage); media.setData(contentUri); this.sendBroadcast(media); Log.i(TAG, "screen image saved"); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void tearDownMediaProjection() { if (mMediaProjection ! = null) { mMediaProjection.stop(); mMediaProjection = null; } Log.i(TAG, "mMediaProjection undefined"); }}Copy the code

The third step is to crop the screenshot

According to the screenshot area mRect obtained in the first step, clip the screenshot result bitmap obtained in the second step:

if (mRect ! = null) { if (mRect.left < 0) mRect.left = 0; if (mRect.right < 0) mRect.right = 0; if (mRect.top < 0) mRect.top = 0; if (mRect.bottom < 0) mRect.bottom = 0; int cut_width = Math.abs(mRect.left - mRect.right); int cut_height = Math.abs(mRect.top - mRect.bottom); if (cut_width > 0 && cut_height > 0) { Bitmap cutBitmap = Bitmap.createBitmap(bitmap, mRect.left, mRect.top, cut_width, cut_height); }Copy the code

Note that the windowHeight value does not include the height of the NavigationBar when invoking the system’s windowHeight function, which, if left unadjusted, will result in the screen capture being compressed. NavigationBar displays the true height of the screen.

NavigationBar also causes the screenshot to be bordered with a transparent color because of the rowPadding in step 2! =0, screenshot as shown below:





Results with NavigationBar using system screenshots

So if we want to save or crop the screenshot result, we must remove the border and find the real content area, that is, the content between the first opaque pixel and the last opaque pixel, and then perform the third step of crop the obtained area, the code is as follows:

int[] pixel=new int[width]; Bitmap. GetPixels (pixel, 0, width, 0, 0, width, 1); int leftPadding=0; int rightPadding=width; for (int i=0; i<pixel.length; i++){ if (pixel[i]! =0){ leftPadding=i; break; } } for (int i=pixel.length-1; i>=0; i--){ if (pixel[i]! =0){ rightPadding=i; break; } } bitmap=Bitmap.createBitmap(bitmap,leftPadding, 0, rightPadding-leftPadding, height);Copy the code

The screenshot after processing is as follows:





Get the content section of the screenshot result

You might think that since it’s rowPadding! =0 causes the border to appear, and the border is only on the right. Why not just truncate the right rowPadding width? The reason for using the above method is that if you don’t adjust the windowHeight window, it will create a box on the left side.

The complete code is available for referenceBigbangThe project’s MarkSizeView class, ScreenCaptureActivity class, and ScreenCapture class.