19

i just want to share this piece of code that i wrote. I tried searching for a custom crop activity, but most of them leads to the default "com.android.camera.action.CROP" despite the question custom crop, or freehand crop activity. Anyway, i just made one for myself, and hopefully it will help you guys.

public class CropView extends ImageView {

    Paint paint = new Paint();
    private int initial_size = 300;
    private static Point leftTop, rightBottom, center, previous;

    private static final int DRAG= 0;
    private static final int LEFT= 1;
    private static final int TOP= 2;
    private static final int RIGHT= 3;
    private static final int BOTTOM= 4;

    private int imageScaledWidth,imageScaledHeight;
    // Adding parent class constructors   
    public CropView(Context context) {
        super(context);
        initCropView();
    }

    public CropView(Context context, AttributeSet attrs) {
        super(context, attrs, 0);
        initCropView();
    }

    public CropView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initCropView();
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        if(leftTop.equals(0, 0))
            resetPoints();
        canvas.drawRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int eventaction = event.getAction();
        switch (eventaction) { 
            case MotionEvent.ACTION_DOWN:
                previous.set((int)event.getX(), (int)event.getY());
                break; 
            case MotionEvent.ACTION_MOVE: 
                if(isActionInsideRectangle(event.getX(), event.getY())) {
                    adjustRectangle((int)event.getX(), (int)event.getY());
                    invalidate(); // redraw rectangle
                    previous.set((int)event.getX(), (int)event.getY());
                }
                break; 
            case MotionEvent.ACTION_UP: 
                previous = new Point();
                break;
        }         
        return true;
    }

    private void initCropView() {
        paint.setColor(Color.YELLOW);
        paint.setStyle(Style.STROKE);
        paint.setStrokeWidth(5);  
        leftTop = new Point();
        rightBottom = new Point();
        center = new Point();
        previous = new Point();
    }

    public void resetPoints() {
        center.set(getWidth()/2, getHeight()/2);
        leftTop.set((getWidth()-initial_size)/2,(getHeight()-initial_size)/2);
        rightBottom.set(leftTop.x+initial_size, leftTop.y+initial_size);
    }

    private static boolean isActionInsideRectangle(float x, float y) {
        int buffer = 10;
        return (x>=(leftTop.x-buffer)&&x<=(rightBottom.x+buffer)&& y>=(leftTop.y-buffer)&&y<=(rightBottom.y+buffer))?true:false;
    }

    private boolean isInImageRange(PointF point) {
        // Get image matrix values and place them in an array
        float[] f = new float[9];
        getImageMatrix().getValues(f);

        // Calculate the scaled dimensions
        imageScaledWidth = Math.round(getDrawable().getIntrinsicWidth() * f[Matrix.MSCALE_X]);
        imageScaledHeight = Math.round(getDrawable().getIntrinsicHeight() * f[Matrix.MSCALE_Y]);

        return (point.x>=(center.x-(imageScaledWidth/2))&&point.x<=(center.x+(imageScaledWidth/2))&&point.y>=(center.y-(imageScaledHeight/2))&&point.y<=(center.y+(imageScaledHeight/2)))?true:false;
    }

    private void adjustRectangle(int x, int y) {
        int movement;
        switch(getAffectedSide(x,y)) {
            case LEFT:
                movement = x-leftTop.x;
                if(isInImageRange(new PointF(leftTop.x+movement,leftTop.y+movement)))
                    leftTop.set(leftTop.x+movement,leftTop.y+movement);
                break;
            case TOP:
                movement = y-leftTop.y;
                if(isInImageRange(new PointF(leftTop.x+movement,leftTop.y+movement)))
                    leftTop.set(leftTop.x+movement,leftTop.y+movement);
                break;
            case RIGHT:
                movement = x-rightBottom.x;
                if(isInImageRange(new PointF(rightBottom.x+movement,rightBottom.y+movement)))
                    rightBottom.set(rightBottom.x+movement,rightBottom.y+movement);
                break;
            case BOTTOM:
                movement = y-rightBottom.y;
                if(isInImageRange(new PointF(rightBottom.x+movement,rightBottom.y+movement)))
                    rightBottom.set(rightBottom.x+movement,rightBottom.y+movement);
                break;      
            case DRAG:
                movement = x-previous.x;
                int movementY = y-previous.y;
                if(isInImageRange(new PointF(leftTop.x+movement,leftTop.y+movementY)) && isInImageRange(new PointF(rightBottom.x+movement,rightBottom.y+movementY))) {
                    leftTop.set(leftTop.x+movement,leftTop.y+movementY);
                    rightBottom.set(rightBottom.x+movement,rightBottom.y+movementY);
                }
                break;
        }
    }

    private static int getAffectedSide(float x, float y) {
        int buffer = 10;
        if(x>=(leftTop.x-buffer)&&x<=(leftTop.x+buffer))
            return LEFT;
        else if(y>=(leftTop.y-buffer)&&y<=(leftTop.y+buffer))
            return TOP;
        else if(x>=(rightBottom.x-buffer)&&x<=(rightBottom.x+buffer))
            return RIGHT;
        else if(y>=(rightBottom.y-buffer)&&y<=(rightBottom.y+buffer))
            return BOTTOM;
        else
            return DRAG;
    }

    public byte[] getCroppedImage() {
        BitmapDrawable drawable = (BitmapDrawable)getDrawable();
        float x = leftTop.x-center.x+(drawable.getBitmap().getWidth()/2);
        float y = leftTop.y-center.y+(drawable.getBitmap().getHeight()/2);
        Bitmap cropped = Bitmap.createBitmap(drawable.getBitmap(),(int)x,(int)y,(int)rightBottom.x-(int)leftTop.x,(int)rightBottom.y-(int)leftTop.y);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        cropped.compress(Bitmap.CompressFormat.PNG, 100, stream);
        return stream.toByteArray();
    }
}

What i did was, i extended the ImageView and added Cropping powers. It is pretty easy to use. Once the class is saved, just use it in the layout like this.

    <"your package name".CropView
        android:id="@+id/image_preview"
        android:layout_width="fill_parent"
        android:layout_height="match_parent" />

Thats it! Hope it helps! If you encounter any problem, please feel free to ask :)

Seph Remotigue
  • 427
  • 1
  • 3
  • 9
  • 1
    I used the above code ,but the cropped image is not getting correctly,it is getting zoom. – Santosh Jul 10 '13 at 06:58
  • Same problem it doesnt crop the image within the bounds of the rectangle. It zooms some how. I tried @TheHippo answer still the same problem. – Hasham Dec 24 '13 at 10:15
  • but were should i put this code <"your package name".CropView android:id="@+id/image_preview" android:layout_width="fill_parent" android:layout_height="match_parent" /> – Ashish Augustine Feb 12 '14 at 14:43
  • I really like how you implemented this custom cropper with an imageview. You cannot find a custom cropper that extends ImageView. There is one thing however, have you found a way to make the cropper rectangle scale easier? – superuserdo Nov 26 '14 at 00:40
  • thanks, that was my main problem back then wen i made this code. i was not able to optimize the rectangle depending on the image. if you were able to do it, please give me an update. thanks! :) right now i cant allot time to fix it :( – Seph Remotigue Nov 27 '14 at 03:40
  • great! I suggest you to create a repository on Github or somewhere you like it in order to improve this nice, readable, usable and low count LOC by other developers with your name. also they can fork you solution and add some more features and fixing some bugs. I'm adding some features to it and trying to fix some bugs I found. – Mohammad Rafigh Feb 02 '15 at 11:34
  • 1
    @Seph Remotigue can you explain how to use this? – Nilabja Jun 15 '17 at 12:51
  • not perfect use this code so please Mr.seph explain above code , i think understand .. – Ramani Hitesh Nov 20 '17 at 11:49

3 Answers3

2

You should try:

@Override
protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);

    setContentView(R.layout.crop_layout);


    myCropView = new CropView(this);

    Uri imageUri = getIntent().getExtras().getParcelable("path");

    b = (BitmapDrawable) BitmapDrawable.createFromPath(imageUri.getPath()); 

    myCropView.setImageURI(imageUri);
}

(Taken from an edit to your question.)

TheHippo
  • 61,720
  • 15
  • 75
  • 100
1

thanks thehippo... but i've solved finding the view by the layout

Uri imageUri = getIntent().getExtras().getParcelable("path");
    b = (BitmapDrawable) BitmapDrawable.createFromPath(getRealPathFromURI(imageUri));
    myCropView = (CropView) findViewById(R.id.image_preview);
    myCropView.setBackground(b);

but now i can't handle the touch event. The rectangle stay still even if i touch the screen...

EDIT: ok, i've made it works. But now, the rectangle move only inside a smaller area, not in the entire image. I suppose that there's something wrong here

private boolean isInImageRange(PointF point) {
    // Get image matrix values and place them in an array
    float[] f = new float[9];
    getImageMatrix().getValues(f);

    // Calculate the scaled dimensions
    imageScaledWidth = Math.round(getBackground().getIntrinsicWidth() * f[Matrix.MSCALE_X]);
    imageScaledHeight = Math.round(getBackground().getIntrinsicHeight() * f[Matrix.MSCALE_Y]);

    return (point.x>=(center.x-(imageScaledWidth/2))&&point.x<=(center.x+(imageScaledWidth/2))&&point.y>=(center.y-(imageScaledHeight/2))&&point.y<=(center.y+(imageScaledHeight/2)))?true:false;
}

i've done a little change to make work the code: getBackground() instead of getDrawable

EDIT 2: OK ive got it, i was doing this in the wrong way. You're code is good. To set the image i was using the view.seBackground()...instead of view.setImageDrawable(). Now everything works. Maybe i will only check if it's possible to create a larger area that fires the scaling of the rectangle

Vahn84
  • 197
  • 1
  • 3
  • 6
  • hi @Vahn84! sorry i got you confused with the code. How did you fix your problem? i got lost in the thread. Also thanks @TheHippo for the suggestion. i think his answer is the best way to set the image in place. – Seph Remotigue May 24 '13 at 14:40
  • 1
    Can anyone who got it working edit the final code and way above ?? – Hasham Dec 24 '13 at 10:56
  • Hi! Code posted by TheHippo works! It was me doing it in the wrong way! – Vahn84 Feb 18 '14 at 10:04
1

I found a library which supports this: SimpleCropView from https://android-arsenal.com/details/1/2366. In general, I would not recommend it, its performance is far from the native android cropping apps.

I've tried using it, and my thoughts are:

  • Really simple to implement in your app, took me about 5 minutes to get crop and rotation functionality working with my existing app

  • The re-sizing of the crop area is painfully slow, I wouldn't like my user to see it.

UPDATE: In fact, I've found a really good solution as jdamcd/android-crop on Github: https://github.com/jdamcd/android-crop Summary:

  • Very simple to use in your app

  • Fast, since it uses the code from native gallery app

  • Customizable, if you want to spend some time playing with it. By default, it provides you with an Activity where you do the cropping. If you want to integrate it into your own activity, it will take a bit mor time. (For my project I would have liked to integrate it, and will do in the future, but for now a separate activity is enough).

Hope this provides some insight!

mmagician
  • 1,970
  • 2
  • 15
  • 26