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;
}
}