How to crop large photos with Android MediaStore
Overview
I’m writing this article to publish my brief research on the MediaStore crop functionality. Basically, if you want to write an application that leverages the existing Media Gallery and allows a user to pick (with optional face recognition) a portion of his/her image for use inside your application. There is an intent to do this, some bugs associated with it, and an overall lack of documentation on how to do it. Also, this article is based on 2.1 and tested on a Nexus One. The implementation of the Nexus One appears to be written by these guys (according to their site), seeĀ Media Gallery for Nexus One.
Feedback
This article is based on my research, needed for an application I wrote. If you find improvements over my suggested implementation, please let me know. I’ll update this post accordingly.
Intent Details:
First, lets discuss this Intent, and its features. After seeing some example code, I found that I could call it easily using an Intent like this:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
intent.setType(“image/*”);
intent.putExtra(“crop”, “true”);
…
However, this is when I lacked additional documentation, what are the options, etc… So, I have put together a table, and a demo program, in an attempt to demonstrated all available options from this control. You may use my example code in your application, and expand upon it. I’ll attach it to this post.
| SetExtra | DataType | Description |
| crop | String | Signals the crop feature |
| aspectX | int | Aspect Ratio |
| aspectY | int | Aspect Ratio |
| outputX | int | width of output created from this Intent |
| outputY | int | width of output created from this Intent |
| scale | boolean | should it scale |
| return-data | boolean | Return the bitmap with Action=inline-data by using the data |
| data | Parcelable | Bitmap to process, you may provide it a bitmap (not tested) |
| circleCrop | String | if this string is not null, it will provide some circular cr |
| MediaStore.EXTRA_OUTPUT ("output") | URI | Set this URi to a File:///, see example code |
Now, the biggest confusion is going to be around this MediaStore.EXTRA_OUTPUT and return-data options.
You basically have 2 ways to get the bitmap back from this Intent, you can ask for it inline or provide a Uri that it can write to.
Option #1: If you set return-data to "true", you will get back an Action = inline-data, and the bitmap returned will be inside the (Bitmap)extras.getParcelable("data"). Note: If the image ends up being to large, you will get a nasty exception with this method, so you are restricted to keeping your outputX and outputY small. In my code example attached, I did NOT use that method because of this reason.
Here is the code from CropImage.java (Android source code) where they write it inline:
// Return the cropped image directly or save it to the specified URI.
Bundle myExtras = getIntent().getExtras();
if (myExtras != null && (myExtras.getParcelable("data") != null
|| myExtras.getBoolean("return-data")))
{
Bundle extras = new Bundle();
extras.putParcelable("data", croppedImage);
setResult(RESULT_OK,(new Intent()).setAction("inline-data").putExtras(extras));
finish();
Option #2: If you set return-data to "false", you will not receive a Bitmap back from the onActivityResult Intent in-line, instead you will need to set MediaStore.EXTRA_OUTPUT to a Uri (of File scheme only) where you want the Bitmap to be stored. This has some restrictions, first you need to have a temp filesystem location in order to give the file scheme URI, not a huge problem (except on some devices that don't have sdcards).
Here is the code from CropImage.java (Android source code) where they write it to your Uri:
if (mSaveUri != null) {
OutputStream outputStream = null;
try {
outputStream = mContentResolver.openOutputStream(mSaveUri);
if (outputStream != null) {
croppedImage.compress(mOutputFormat, 75, outputStream);
}
} catch (IOException ex) {
// TODO: report error to caller
Log.e(TAG, "Cannot open file: " + mSaveUri, ex);
} finally {
Util.closeSilently(outputStream);
}
Bundle extras = new Bundle();
setResult(RESULT_OK, new Intent(mSaveUri.toString())
.putExtras(extras));
}
Example Code
I have attached some example code that should allow you to test various configurations. Let me know if you find it useful (or not).D
Download here ->MediaStoreTest
From the code (the essential parts)
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
thiz = this;
setContentView(R.layout.main);
mBtn = (Button) findViewById(R.id.btnLaunch);
photo = (ImageView) findViewById(R.id.imgPhoto);
mBtn.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
try {
// Launch picker to choose photo for selected contact
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
intent.setType("image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", aspectX);
intent.putExtra("aspectY", aspectY);
intent.putExtra("outputX", outputX);
intent.putExtra("outputY", outputY);
intent.putExtra("scale", scale);
intent.putExtra("return-data", return_data);
intent.putExtra(MediaStore.EXTRA_OUTPUT, getTempUri());
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection",!faceDetection); // lol, negative boolean noFaceDetection
if (circleCrop) {
intent.putExtra("circleCrop", true);
}
startActivityForResult(intent, PHOTO_PICKED);
} catch (ActivityNotFoundException e) {
Toast.makeText(thiz, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
}
}
});
}
private Uri getTempUri() {
return Uri.fromFile(getTempFile());
}
private File getTempFile() {
if (isSDCARDMounted()) {
File f = new File(Environment.getExternalStorageDirectory(),TEMP_PHOTO_FILE);
try {
f.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
Toast.makeText(thiz, R.string.fileIOIssue, Toast.LENGTH_LONG).show();
}
return f;
} else {
return null;
}
}
private boolean isSDCARDMounted(){
String status = Environment.getExternalStorageState();
if (status.equals(Environment.MEDIA_MOUNTED))
return true;
return false;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case PHOTO_PICKED:
if (resultCode == RESULT_OK) {
if (data == null) {
Log.w(TAG, "Null data, but RESULT_OK, from image picker!");
Toast t = Toast.makeText(this, R.string.no_photo_picked,
Toast.LENGTH_SHORT);
t.show();
return;
}
final Bundle extras = data.getExtras();
if (extras != null) {
File tempFile = getTempFile();
// new logic to get the photo from a URI
if (data.getAction() != null) {
processPhotoUpdate(tempFile);
}
}
}
break;
}
}
Tags: Android, Android UI, crop, example, photo
Hi,
This is really a good example of cropping of an selected iamge.
But my requirement is that i have already selected image and then i have to perform the cropping on the image.Can you please guide me how to do this taks.
This was amazingly helpful. I knew there were different options for this intent, but could not find a good description. With the Galaxy S phones having problems returning data from the gallery the temp file option is a great solution. The only thing it was missing was using the Parcelable Data to provide a bitmap to the crop function… if in fact this is actually possible. I would love to have that ability, but have not gotten it to work yet.
I appreciate you work and research into this. It was very useful to me and very well presented!
You save my life
I am sure going to try this. I hate to limit my app to 2.1+, but I will to get this to work. Thank you.! -UnConn
Great work. Where did you get those options for “image/*” from? I haven’t found any official documentation on the subject.
This project is awseome and has saved me lots of time!!! One question how does the circle crop work? I have an instance where this could be very beneficial.
Thanks!
This is nice demo for croping image. But how does the circle crop work. I want to know.
Please reply.
thanks
Thanks, great explanation, but unfortunately the demo code doesn’t seem to work for me.
It always returns a null intent but RESULT_OK. I’ve debugged and the getTempUri seems to return a valid URI. I also tried disconnecting it from the PC, in case the SD card was busy or something.
Any ideas what could be going wrong?
Thanks!
Hi,
Thanks a lot. The post was really helpful.
There is small remark. The code above has problem with newest android.Gallery3D. This gallery never returns action. I.e.data.getAction() is null always, even if gallery returns image successfully. So, I would suggest to comment part of the code:
//if (data.getAction() != null) {
processPhotoUpdate(tempFile);
//}
BTW: sometime, image could be selected not from a file, but from other source. It’s more common way to use URI instead of filename. See:
http://stackoverflow.com/questions/5944282/retrieve-picasa-image-for-upload-from-gallery
and sources of Android Bery library (class GetImageResult):
http://code.google.com/p/android-beryl/source/browse/beryl-intents/src/org/beryl/intents/android/Gallery.java?r=e02c30d60aa22b2037ea18b7e6dab74ae4342555
This was really very very very helpful. I have learnt almost all operations in CROP from this post. But i have one issue while passing MediaStore.EXTRA_OUTPUT parameter. It is saving the cropped image in with the file name that i have passed as URI. But the image was corrupted. I mean i can’t able to open the image. What is the mistake that i have done ????? PLease correct me.