8

I have code to crop an image, like this :

public void doCrop(){
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setType("image/");
List<ResolveInfo> list = getPackageManager().queryIntentActivities(intent,0);
int size = list.size();
if (size == 0 ){
   Toast.makeText(this, "Cant find crop app").show();
   return;
} else{
   intent.setData(selectImageUri);
   intent.putExtra("outputX", 300);
   intent.putExtra("outputY", 300);
   intent.putExtra("aspectX", 1);
   intent.putExtra("aspectY", 1);
   intent.putExtra("scale", true);
   intent.putExtra("return-data", true);
   if (size == 1) {
       Intent i = new Intent(intent);
       ResolveInfo res = list.get(0);
       i.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
       startActivityForResult(i, CROP_RESULT);
   }
}
}

public void onActivityResult (int requestCode, int resultCode, Intent dara){
   if (resultCode == RESULT_OK){
      if (requestCode == CROP_RESULT){
          Bundle extras = data.getExtras();
          if (extras != null){
              bmp = extras.getParcelable("data");
          }
          File f = new File(selectImageUri.getPath());
          if (f.exists()) f.delete();
          Intent inten3 = new Intent(this, tabActivity.class);
          startActivity(inten3);
      }
   }
}

from what i have read, the code intent.putExtra("outputX", 300); intent.putExtra("outputY", 300); is use to set the resolution of crop result, but why i can't get the result image resolution higer than 300x300? when I set the intent.putExtra("outputX", 800); intent.putExtra("outputY", 800); the crop function has no result or crash, any idea for this situation?

the log cat say "! ! ! FAILED BINDER TRANSACTION ! ! !

Remees M Syde
  • 2,564
  • 1
  • 19
  • 42
adi.zean
  • 1,085
  • 3
  • 12
  • 15
  • just a note: `if (size==1)` will cripple your app if more than one application that offers the CROP action is installed. I'd skip that part if I were you. Also, consider using the `Intent#createChooser(..)` method instead of starting the first available component - it might not be the preferred app of the user. – Jens Oct 06 '12 at 09:30
  • hahaha yeah i had modify my code and skip the if (size==1) – adi.zean Oct 08 '12 at 00:46

2 Answers2

28

This question is all over stackoverflow. And I'm glad it is since I had to sort this out myself recently. I'll do my best to mark some duplicates as I go but I prefer this one since it addresses the issue with the limited image size.

Short Answer

The short answer is not to use the return-data option. Read more about that option and how to retrieve the image then here: http://www.androidworks.com/crop_large_photos_with_android. The article does a great job listing the (known) configuration options for the Intent and how to use them.

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

    /** 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.makeText(this, R.string.no_photo_picked,
Toast.LENGTH_SHORT).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;
    }
}

Code example from: http://www.androidworks.com/crop_large_photos_with_android

More Information

The long answer is not to use that intent at all. Continue reading to find out why.

Unofficial API

The core of the problem is the unofficial intent. Unofficial as in not part of the public API. Currently it works for most devices but most just isn't enough. Also Google can change this intent anytime without letting you know. Breaking along all the apps using it. Much like how the calendar API was once unofficial. In fact this crop intent has already changed once. So avoid using this intent. There are alternatives. Feel free to ignore this advice.

Just to proof the "works for some devices statement" follow this link and enjoy frustrated Android developers discussing what should be considered part of the core android features (and isn't): https://code.google.com/p/android/issues/detail?id=1480

Time for example code? Check this github project: https://github.com/lorensiuswlt/AndroidImageCrop

About Size Limit

Another issue I experienced while exploring this intent is the image crop size limit. This can easily be reproduced using the above example code and an image size of anything above 300 pixels. Basically what this original question is all about. In the best case your app will crash. But I've seen worse up to hanging devices that could only be reset by removing the battery.

Now if you remove that 'return-data' option you'll be able to run it again. More information on how to get to the result is found in the short answer where I referenced this link already: http://www.androidworks.com/crop_large_photos_with_android

The Solution

So a lot of problems. Problems require a solution. The only decent solution until Google comes up with a public API for this is to provide your own crop intent. Just get your hands on a proper crop library like this one on github: https://github.com/lvillani/android-cropimage

The project lacks some documentation but since it's an extract of the unofficial android crop intent you can use the examples listed on top to get started. Just make sure not to use the return-data option. Ah and check out the CropImageIntentBuilder class. That should let you easily create an intent for the cropping. Don't forget to add this Activity to your manifest and the permissions to write to external data storage.

private void doCrop(File croppedResult){
        CropImageIntentBuilder builder = new CropImageIntentBuilder(600,600, croppedResult);
        // don't forget this, the error handling within the library is just ignoring if you do
        builder.setSourceImage(mImageCaptureUri);
        Intent  intent = builder.getIntent(getApplicationContext());
        // do not use return data for big images
        intent.putExtra("return-data", false);
        // start an activity and then get the result back in onActivtyResult
        startActivityForResult(intent, CROP_FROM_CAMERA);
    }

Using this library also opens the doors to more customisation. Good to know is the core bitmap functionally used for resizing: How to crop the parsed image in android?

And that's it. Enjoy!

Community
  • 1
  • 1
hcpl
  • 17,382
  • 7
  • 72
  • 73
  • I tried integrating this library with my app however I wasn't able to do so. Could you provide instructions on how to do this? At the moment, I copied and pasted the files into my app and I am hoping there is a way of referencing this library without bringing it into my app. – Parth Shah Sep 10 '14 at 08:59
  • You are a magical. Solved my every problem with image croping – Jigar Dec 26 '14 at 09:07
  • 1
    the link to the article is broken :( – ezain Jun 29 '15 at 13:41
8

That Intent is not part of the public Android API and is not guaranteed to work on all devices. It was used in earlier versions of android 1.x and 2.x but it's not used anymore and is not recommended. That is probably why it's crashing all over the palce or working improperly.

Use methods such as Bitmap.createBitmap(..) or Bitmap.createScaledBitmap(..) to create a resized or cropped version of your original image. These are part of the Android API and are guaranteed to work.

See official docs here and here

To crop a bitmap, you can use Bitmap.createBitmap(Bitmap, int x, int y, int width, int height). For example, if you need to crop 10 pixels from each side of a bitmap then use this:

Bitmap croppedBitmap = Bitmap.createBitmap(originalBitmap, 10, 10, originalBitmap.getWidth() - 20, originalBitmap.getHeight() - 20);

If you need to show the selector to the user. Then you can do something like this:

private static final String TEMP_PHOTO_FILE = "temporary_holder.jpg";  

Intent photoPickerIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
photoPickerIntent.setType("image/*");
photoPickerIntent.putExtra("crop", "true");
photoPickerIntent.putExtra(MediaStore.EXTRA_OUTPUT, getTempUri());
photoPickerIntent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
startActivityForResult(photoPickerIntent, REQ_CODE_PICK_IMAGE);


    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) {

    }
    return f;
    } else {
    return null;
    }
    }

    private boolean isSDCARDMounted(){
    String status = Environment.getExternalStorageState();
    if (status.equals(Environment.MEDIA_MOUNTED))
    return true;
    return false;
    }




protected void onActivityResult(int requestCode, int resultCode,
        Intent imageReturnedIntent) {
    super.onActivityResult(requestCode, resultCode, imageReturnedIntent);

    switch (requestCode) {
    case REQ_CODE_PICK_IMAGE:
        if (resultCode == RESULT_OK) {  
          if (imageReturnedIntent!=null){



               File tempFile = getTempFile();

              String filePath= Environment.getExternalStorageDirectory()
            + "/temporary_holder.jpg";
              System.out.println("path "+filePath);


    Bitmap selectedImage =  BitmapFactory.decodeFile(filePath);
    _image = (ImageView) findViewById(R.id.image);
    _image.setImageBitmap(selectedImage );

}
}
}

code from here

Community
  • 1
  • 1
Anup Cowkur
  • 20,443
  • 6
  • 51
  • 84
  • hmmm.. i don't know how to make the selection area like this intent, thats why i used this intent.. do you know how to make the selection area?? – adi.zean Oct 08 '12 at 00:45
  • I've updated my answer, you can use the code I've posted to make a selection area using an intent that will call any app the user has that can crop the image. Then you can retrieve the cropped image and set it as shown. – Anup Cowkur Oct 08 '12 at 05:11
  • 1
    yeah i have tryed the code and its work,, but one more question, can I get the cordinate of the square? i want to edit the ori bitmap, and replace the croped image to ori bitmap in the same place.. – adi.zean Oct 15 '12 at 00:30