Android Image Crop
|

Android Crop Image Using UCrop Library

Almost in every Android Application, we ask users to add profile picture when signing up and after that, we simply upload the avatar to our backend. But, before uploading that avatar, sometimes, we need to crop the selected image.

So, today I would like to talk about how we can select an image from Gallery or take Picture from the camera and crop that avatar. The application that we’re going to build in this article will work on all versions of Android from Honeycomb (API 11) to Pie (API 28).

For cropping the image we’re going to use UCrop libraryUCrop library aims to provide an ultimate and flexible image cropping experience. You can read more about UCrop library in this article.

Before start coding, I want to show you guys the demo of our application.

Get Started

In Android Studio go to Create Android Project, press next twice and select Empty Activity, then finish. After the Android Studio gradle builds successfully you’re ready to do the code. You can see MainActivity like below.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Include Dependencies

We’re gonna need to add the UCrop dependency in our app-level build.gradle file. Add the below dependency in the build.gradle file.

implementation 'com.github.yalantis:ucrop:2.2.2'

Now add the maven dependency in a top-level build.gradle file.

repositories {
       google()
       jcenter()
       maven { url "https://jitpack.io" }
   }

Once you’ve done these dependencies build your android project and add the following permissions in the Android Manifest file.

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

You may be curious why the hack I add the Storage permission in order to pick an image from Gallery or take a picture from Camera. Actually, when we select the image from the gallery or take pictures from the camera we need to store an image inside the device memory because of the UCrop library take the Uri instance in order to crop the image. And it would be good because we need to store the cropped image somewhere inside the memory and after that show the image inside the application.

Actually, there’s another thing which we need to discuss before start making our application. As I said at the start of this article, that our application will work on all existing Android devices. So, we need to handle the FileUriExposedException and for that, we need to implement FileProvider in our application. You can read more about FileUriExposedException in this link.

In order to implement FileProvider in your application. First, you need to add a FileProvider <provider/> tag in a AndroidManifest.xml file under the <application/> tag.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    <application
        ...
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>
    </application>
</manifest>

The ${applicationId} automatically gets the package name of your Android application and concat the .provider with it.

The @xml/provider_paths we need to add it separately in the resource directory. First, you need to create an xml named directory inside the res folder and then create a file named provider_paths inside the previously created directory. Now paste the following code inside the provider_paths file.

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
</paths>

Done! FileProvider is now declared and be ready to use.

1. Open Camera And Take Picture

First of all, we need to query for all the activities in the device which will handle the  CAPTURE_REQUEST intent. Now let’s open the camera when the user clicks on openCamera dialog action.

Note: I’m not gonna ask for camera or storage permission in this article but you can get the complete code of the above demo application from the Github.

private void openCamera() {
      Intent pictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
      File file = getImageFile(); // 1
      Uri uri;
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) // 2
           uri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID.concat(".provider"), file);
      else
           uri = Uri.fromFile(file); // 3
      pictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri); // 4
      startActivityForResult(pictureIntent, CAMERA_ACTION_PICK_REQUEST_CODE); 
}

Here what’s going on in the openCamera method.

  1. Creating an image File with the getImageFile method. This method simply creates a new File in the external storage directory. We’ll see the implementation of the method in a couple of minutes.
  2.  If the SDK version of the device is API level 23+ then we simply create the Uri with the help of FileProvider in order to prevent the FileUriExposedException. The BuildConfig.APPLICATION_ID simply give us the package name of our application and concat the .provider with it as we did it in the AndroidManifest.xml file.
  3. Simply creates the Uri fromFile utility method if the SDK version is less than 24 API level.
  4. Sets the path of where you want to store the selected image so, that we can read the image in the onActivityResult method from the uri path. We’ll see how to do that.

Now let’s create the getImageFile method inside the Activity class.

String currentPhotoPath = ""; 
private File getImageFile() {
     String imageFileName = "JPEG_" + System.currentTimeMillis() + "_";
     File storageDir = new File(
         Environment.getExternalStoragePublicDirectory(
             Environment.DIRECTORY_DCIM
         ), "Camera"
     );
     File file = File.createTempFile(
         imageFileName, ".jpg", storageDir  
     );
     currentPhotoPath = "file:" + file.getAbsolutePath();
     return file;
 }

This method simply creates a random File name with .jpg extension inside the external storage directory. Also, we’re storing the file path inside the currentPhotoPath variable so that we can read the image from specified photo path.

After taking the picture from Camera our image will be stored inside the data extra’s which we pass when creating the Intent for Camera. Now let’s retrieve our image in onActivityResult method and crop it.

Crop the Taken Picture

First, you need to override the onActivityResult method inside your Activity class and add the following code.

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    if(requestCode == CAMERA_ACTION_PICK_REQUEST_CODE && resultCode == RESULT_OK) {
          Uri uri = Uri.parse(currentPhotoPath);
          openCropActivity(uri, uri);
    }
}

If you observe the code carefully you may have noticed that we’re not using the data.getData() method. It always gives us null because we’re providing a file uri, so load with the currentPhotoPath and call the openCropActivity method.

Note: Our newly taken image will be replaced from the cropped image because I pass the same Uri’s as the sourceUri and destinationUri. You can change this logic by passing a different Uri for a destination.

Add the below openCropActivity method inside your Activity class.

private void openCropActivity(Uri sourceUri, Uri destinationUri) {
        UCrop.of(sourceUri, destinationUri)
        .withMaxResultSize(maxWidth, maxHeight)
        .withAspectRatio(5f, 5f)
        .start(context);
}

The UCrop configuration is created using the builder pattern. The UCrop.of method takes the first parameter as the sourceUri where the image file actually stored and the second parameter as the destinationUri where you want to store the cropped image.

After calling the openCropActivity method the UCrop simply opens up the image cropping activity with the startActivityForResult method and send the cropped image result in the onActivityResult method. Add the updated code inside the onActivityResult method.

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    if(requestCode == CAMERA_ACTION_PICK_REQUEST_CODE && resultCode == RESULT_OK) {
          Uri uri = Uri.parse(currentPhotoPath);
          openCropActivity(uri, uri);
    } else if (requestCode == UCrop.REQUEST_CROP && resultCode == RESULT_OK) {
          Uri uri = UCrop.getOutput(data);
          showImage(uri);
    }
}

In the updated code we simply get our cropped image Uri and call the showImage method.

Next, add the following method inside your Activity class.

private void showImage(Uri imageUri) {
    File file = FileUtils.getFile(context, imageUri);
    InputStream inputStream = new FileInputStream(file);
    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
    imageView.setImageBitmap(bitmap);
}

Here we’ve completed our first part of an article where we take the picture from a camera and show the cropped image inside the ImageView.

2. Select Image From Gallery

In order to allow the user to select an image, we need to create an Intent which opens up the Documents app and only shows the images. Now let’s open the Documents app when the user clicks on selectImage dialog action.

Let’s create a new method to open the images.

private void openImagesDocument() {
    Intent pictureIntent = new Intent(Intent.ACTION_GET_CONTENT);
    pictureIntent.setType("image/*");  // 1 
    pictureIntent.addCategory(Intent.CATEGORY_OPENABLE);  // 2
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
         String[] mimeTypes = new String[]{"image/jpeg", "image/png"};  // 3
         pictureIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
    }
    startActivityForResult(Intent.createChooser(pictureIntent,"Select Picture"), PICK_IMAGE_GALLERY_REQUEST_CODE);  // 4
}

The following shows the explanation of the above code.

  1. By setting the type we specify we only need images no videos or anything else.
  2. The CATEGORY_OPENABLE used to indicate that Intent only wants URI’s that can be opened. You can read more category openable here in this link.
  3. The mimeTypes specify that we are only interested in jpeg or png type images.
  4. The chooser only shows if there are multiple options available else we simply open up the Documents app.

Crop The Selected Picture

Once you’ve selected the image the onActivityResult method will get hit and we only need to update that method. Add the update method code inside the onActivityResult method.

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    if(requestCode == CAMERA_ACTION_PICK_REQUEST_CODE && resultCode == RESULT_OK) {
          Uri uri = Uri.parse(currentPhotoPath);
          openCropActivity(uri, uri);
    } else if (requestCode == UCrop.REQUEST_CROP && resultCode == RESULT_OK) {
          Uri uri = UCrop.getOutput(data);
          showImage(uri);
    } else if (requestCode == PICK_IMAGE_GALLERY_REQUEST_CODE && resultCode == RESULT_OK && data != null) {
          Uri sourceUri = data.getData(); // 1 
          File file = getImageFile(); // 2
          Uri destinationUri = Uri.fromFile(file);  // 3
          openCropActivity(sourceUri, destinationUri);  // 4 
    }
}

The below number points tell you what’s happening inside the updated onActivityResult method.

  1. Get the selected image Uri from data.
  2. Create the image File where you want to store the cropped image result.
  3. Simply creates the destinationUri fromFile utility method.
  4. Calls the openCropActivity method. Here we’re passing different Uri’s because our sourceUri is different where our selected image is stored and destinationUri where we want to save our cropped image result.

Now if you run the application you’ll see that all the application functionality will work perfectly. I hope this article will help you to crop the profile picture image. If you’ve any queries regarding this post please do comment below.

You can get the complete source code of above app from GitHub.

Thank you for being here and keep reading…

Similar Posts