18537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk/*
2b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk * Copyright (C) 2013 The Android Open Source Project
38537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk *
48537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk * Licensed under the Apache License, Version 2.0 (the "License");
58537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk * you may not use this file except in compliance with the License.
68537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk * You may obtain a copy of the License at
78537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk *
88537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk *      http://www.apache.org/licenses/LICENSE-2.0
98537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk *
108537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk * Unless required by applicable law or agreed to in writing, software
118537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk * distributed under the License is distributed on an "AS IS" BASIS,
128537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk * See the License for the specific language governing permissions and
148537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk * limitations under the License.
158537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk */
168537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk
178537d097f8827caedc8c39564de54d36eae8b16fRuben Brunkpackage com.android.gallery3d.filtershow.imageshow;
188537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk
198537d097f8827caedc8c39564de54d36eae8b16fRuben Brunkimport android.content.Context;
208537d097f8827caedc8c39564de54d36eae8b16fRuben Brunkimport android.content.res.Resources;
218537d097f8827caedc8c39564de54d36eae8b16fRuben Brunkimport android.graphics.Bitmap;
228537d097f8827caedc8c39564de54d36eae8b16fRuben Brunkimport android.graphics.Canvas;
238537d097f8827caedc8c39564de54d36eae8b16fRuben Brunkimport android.graphics.Matrix;
248537d097f8827caedc8c39564de54d36eae8b16fRuben Brunkimport android.graphics.Paint;
258537d097f8827caedc8c39564de54d36eae8b16fRuben Brunkimport android.graphics.RectF;
268537d097f8827caedc8c39564de54d36eae8b16fRuben Brunkimport android.graphics.drawable.Drawable;
278537d097f8827caedc8c39564de54d36eae8b16fRuben Brunkimport android.util.AttributeSet;
28b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunkimport android.util.Log;
29b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunkimport android.view.MotionEvent;
30c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard
31e533f65961ed601ded1803caeab6cef0a778d2f2nicolasroardimport com.android.gallery3d.R;
32b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunkimport com.android.gallery3d.filtershow.crop.CropDrawingUtils;
336fe165b7d28299d5b2f97deb135b233d84eb300fRuben Brunkimport com.android.gallery3d.filtershow.crop.CropMath;
34b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunkimport com.android.gallery3d.filtershow.crop.CropObject;
35d61a2f9fcaad0309132b6b9b666c3dc6df62fed7John Hofordimport com.android.gallery3d.filtershow.editors.EditorCrop;
36b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunkimport com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
37b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunkimport com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;
38b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk
39b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunkpublic class ImageCrop extends ImageShow {
40b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private static final String TAG = ImageCrop.class.getSimpleName();
41b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private RectF mImageBounds = new RectF();
42b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private RectF mScreenCropBounds = new RectF();
43b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private Paint mPaint = new Paint();
44b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private CropObject mCropObj = null;
45b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private GeometryHolder mGeometry = new GeometryHolder();
46b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private GeometryHolder mUpdateHolder = new GeometryHolder();
47b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private Drawable mCropIndicator;
48b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private int mIndicatorSize;
49b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private boolean mMovingBlock = false;
50b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private Matrix mDisplayMatrix = null;
51b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private Matrix mDisplayCropMatrix = null;
52b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private Matrix mDisplayMatrixInverse = null;
53b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private float mPrevX = 0;
54b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private float mPrevY = 0;
55b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private int mMinSideSize = 90;
56b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private int mTouchTolerance = 40;
57b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private enum Mode {
58b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        NONE, MOVE
59b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    }
60b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private Mode mState = Mode.NONE;
61b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private boolean mValidDraw = false;
62b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    FilterCropRepresentation mLocalRep = new FilterCropRepresentation();
63b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    EditorCrop mEditorCrop;
648537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk
658537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk    public ImageCrop(Context context) {
668537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk        super(context);
67b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        setup(context);
688537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk    }
698537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk
708537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk    public ImageCrop(Context context, AttributeSet attrs) {
718537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk        super(context, attrs);
72b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        setup(context);
738537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk    }
748537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk
75b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    public ImageCrop(Context context, AttributeSet attrs, int defStyle) {
76b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        super(context, attrs, defStyle);
77b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        setup(context);
78a41224997ef9be9c0d04534f7b6b9c6b933bfe05Ruben Brunk    }
79a41224997ef9be9c0d04534f7b6b9c6b933bfe05Ruben Brunk
80b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private void setup(Context context) {
81b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        Resources rsc = context.getResources();
82b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
83b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
84b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
85b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
86f46da69aefd9afe0b4326a2fcea8e33c294136bbRuben Brunk    }
87f46da69aefd9afe0b4326a2fcea8e33c294136bbRuben Brunk
88b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    public void setFilterCropRepresentation(FilterCropRepresentation crop) {
89b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mLocalRep = (crop == null) ? new FilterCropRepresentation() : crop;
90b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        GeometryMathUtils.initializeHolder(mUpdateHolder, mLocalRep);
91b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mValidDraw = true;
920f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk    }
930f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk
94b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    public FilterCropRepresentation getFinalRepresentation() {
95b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        return mLocalRep;
966416dd59687768d4152d5d954dd0e8c3617b9d97Ruben Brunk    }
976416dd59687768d4152d5d954dd0e8c3617b9d97Ruben Brunk
98b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private void internallyUpdateLocalRep(RectF crop, RectF image) {
99b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        FilterCropRepresentation
100b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                .findNormalizedCrop(crop, (int) image.width(), (int) image.height());
101b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mGeometry.crop.set(crop);
102b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mUpdateHolder.set(mGeometry);
103b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mLocalRep.setCrop(crop);
1046416dd59687768d4152d5d954dd0e8c3617b9d97Ruben Brunk    }
1056416dd59687768d4152d5d954dd0e8c3617b9d97Ruben Brunk
106b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    @Override
107b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    public boolean onTouchEvent(MotionEvent event) {
108b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        float x = event.getX();
109b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        float y = event.getY();
110b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
111b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            return true;
112b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        }
113b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        float[] touchPoint = {
114b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                x, y
115b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        };
116b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mDisplayMatrixInverse.mapPoints(touchPoint);
117b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        x = touchPoint[0];
118b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        y = touchPoint[1];
119b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        switch (event.getActionMasked()) {
120b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            case (MotionEvent.ACTION_DOWN):
121b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                if (mState == Mode.NONE) {
122b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    if (!mCropObj.selectEdge(x, y)) {
123b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                        mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
124b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    }
125b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mPrevX = x;
126b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mPrevY = y;
127b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mState = Mode.MOVE;
128b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                }
129b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                break;
130b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            case (MotionEvent.ACTION_UP):
131b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                if (mState == Mode.MOVE) {
132b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mCropObj.selectEdge(CropObject.MOVE_NONE);
133b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mMovingBlock = false;
134b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mPrevX = x;
135b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mPrevY = y;
136b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mState = Mode.NONE;
137b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
138b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                }
139b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                break;
140b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            case (MotionEvent.ACTION_MOVE):
141b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                if (mState == Mode.MOVE) {
142b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    float dx = x - mPrevX;
143b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    float dy = y - mPrevY;
144b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mCropObj.moveCurrentSelection(dx, dy);
145b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mPrevX = x;
146b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mPrevY = y;
147b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                }
148b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                break;
149b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            default:
150b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                break;
151b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        }
1520f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk        invalidate();
153b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        return true;
1540f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk    }
1550f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk
156b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private void clearDisplay() {
157b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mDisplayMatrix = null;
158b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mDisplayMatrixInverse = null;
1590f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk        invalidate();
1600f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk    }
1610f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk
162b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    public void applyFreeAspect() {
163b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mCropObj.unsetAspectRatio();
1640f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk        invalidate();
1650f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk    }
1660f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk
167b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    public void applyOriginalAspect() {
168b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        RectF outer = mCropObj.getOuterBounds();
169b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        float w = outer.width();
170b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        float h = outer.height();
171b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        if (w > 0 && h > 0) {
172b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            applyAspect(w, h);
173b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mCropObj.resetBoundsTo(outer, outer);
174b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
1756416dd59687768d4152d5d954dd0e8c3617b9d97Ruben Brunk        } else {
176b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            Log.w(TAG, "failed to set aspect ratio original");
1776416dd59687768d4152d5d954dd0e8c3617b9d97Ruben Brunk        }
1788537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk        invalidate();
1798537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk    }
1808537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk
181b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    public void applyAspect(float x, float y) {
182b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        if (x <= 0 || y <= 0) {
183b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            throw new IllegalArgumentException("Bad arguments to applyAspect");
1848537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk        }
185b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        // If we are rotated by 90 degrees from horizontal, swap x and y
186b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        if (GeometryMathUtils.needsDimensionSwap(mGeometry.rotation)) {
187b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            float tmp = x;
188b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            x = y;
189b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            y = tmp;
19062e962bcb9fc03f3cfeac5ece8d3e95fc2dd0718Ruben Brunk        }
191b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        if (!mCropObj.setInnerAspectRatio(x, y)) {
192b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            Log.w(TAG, "failed to set aspect ratio");
1930f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk        }
194b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        internallyUpdateLocalRep(mCropObj.getInnerBounds(), mCropObj.getOuterBounds());
1958537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk        invalidate();
1968537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk    }
1978537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk
198b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    /**
199b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk     * Rotates first d bits in integer x to the left some number of times.
200b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk     */
201b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private int bitCycleLeft(int x, int times, int d) {
202b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        int mask = (1 << d) - 1;
203b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        int mout = x & mask;
204b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        times %= d;
205b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        int hi = mout >> (d - times);
206b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        int low = (mout << times) & mask;
207b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        int ret = x & ~mask;
208b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        ret |= low;
209b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        ret |= hi;
210b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        return ret;
2110f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk    }
2120f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk
213b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    /**
214b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk     * Find the selected edge or corner in screen coordinates.
215b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk     */
216b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private int decode(int movingEdges, float rotation) {
217b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        int rot = CropMath.constrainedRotation(rotation);
218b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        switch (rot) {
219b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            case 90:
220b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                return bitCycleLeft(movingEdges, 1, 4);
221b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            case 180:
222b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                return bitCycleLeft(movingEdges, 2, 4);
223b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            case 270:
224b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                return bitCycleLeft(movingEdges, 3, 4);
225b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            default:
226b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                return movingEdges;
2278537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk        }
2288537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk    }
2298537d097f8827caedc8c39564de54d36eae8b16fRuben Brunk
230b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    private void forceStateConsistency() {
231b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        MasterImage master = MasterImage.getImage();
232b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        Bitmap image = master.getFiltersOnlyImage();
233b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        int width = image.getWidth();
234b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        int height = image.getHeight();
235b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        if (mCropObj == null || !mUpdateHolder.equals(mGeometry)
236b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                || mImageBounds.width() != width || mImageBounds.height() != height
237b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                || !mLocalRep.getCrop().equals(mUpdateHolder.crop)) {
238b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mImageBounds.set(0, 0, width, height);
239b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mGeometry.set(mUpdateHolder);
240b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mLocalRep.setCrop(mUpdateHolder.crop);
241b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            RectF scaledCrop = new RectF(mUpdateHolder.crop);
242b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            FilterCropRepresentation.findScaledCrop(scaledCrop, width, height);
243b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mCropObj = new CropObject(mImageBounds, scaledCrop, (int) mUpdateHolder.straighten);
244b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mState = Mode.NONE;
245b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            clearDisplay();
2460f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk        }
2476e2dd284681a716c55e0937ef2e15a1c7507a1b2Ruben Brunk    }
2486e2dd284681a716c55e0937ef2e15a1c7507a1b2Ruben Brunk
2496e2dd284681a716c55e0937ef2e15a1c7507a1b2Ruben Brunk    @Override
2506e2dd284681a716c55e0937ef2e15a1c7507a1b2Ruben Brunk    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
251b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        super.onSizeChanged(w, h, oldw, oldh);
252b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        clearDisplay();
25313b2fd3ab7ff65ac5c18b4c9de69062f3a549669Ruben Brunk    }
25413b2fd3ab7ff65ac5c18b4c9de69062f3a549669Ruben Brunk
25513b2fd3ab7ff65ac5c18b4c9de69062f3a549669Ruben Brunk    @Override
256b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk    public void onDraw(Canvas canvas) {
257b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        Bitmap bitmap = MasterImage.getImage().getFiltersOnlyImage();
25880b216d5c8e17e0358cf1508a1e177ccada8383fnicolasroard        if (bitmap == null) {
25980b216d5c8e17e0358cf1508a1e177ccada8383fnicolasroard            MasterImage.getImage().invalidateFiltersOnly();
26080b216d5c8e17e0358cf1508a1e177ccada8383fnicolasroard        }
261b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        if (!mValidDraw || bitmap == null) {
262b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            return;
2635d834ce0c65f89cf3f249f586b360c1a6d7ab99bRuben Brunk        }
264b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        forceStateConsistency();
265b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mImageBounds.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
266b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        // If display matrix doesn't exist, create it and its dependencies
267b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        if (mDisplayCropMatrix == null || mDisplayMatrix == null || mDisplayMatrixInverse == null) {
2684a5fd0627e5c3f3094127dfbd782cd96080482d4John Hoford            mCropObj.unsetAspectRatio();
269b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mDisplayMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry,
270b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
271b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            float straighten = mGeometry.straighten;
272b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mGeometry.straighten = 0;
273b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mDisplayCropMatrix = GeometryMathUtils.getFullGeometryToScreenMatrix(mGeometry,
274b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    bitmap.getWidth(), bitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
275b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mGeometry.straighten = straighten;
276b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mDisplayMatrixInverse = new Matrix();
277b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mDisplayMatrixInverse.reset();
278b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            if (!mDisplayCropMatrix.invert(mDisplayMatrixInverse)) {
279b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                Log.w(TAG, "could not invert display matrix");
280b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                mDisplayMatrixInverse = null;
281b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                return;
2826e2dd284681a716c55e0937ef2e15a1c7507a1b2Ruben Brunk            }
283b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            // Scale min side and tolerance by display matrix scale factor
284b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
285b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
286ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford            // drive Crop engine to clamp to crop bounds
287ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford            int[] sides = {CropObject.MOVE_TOP,
288ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford                    CropObject.MOVE_BOTTOM,
289ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford                    CropObject.MOVE_LEFT,
290ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford                    CropObject.MOVE_RIGHT};
291ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford            int delta = Math.min(canvas.getWidth(), canvas.getHeight()) / 4;
292ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford            int[] dy = {delta, -delta, 0, 0};
293ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford            int[] dx = {0, 0, delta, -delta};
294ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford
295ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford            for (int i = 0; i < sides.length; i++) {
296ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford                mCropObj.selectEdge(sides[i]);
297ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford
298ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford                mCropObj.moveCurrentSelection(dx[i], dy[i]);
299ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford                mCropObj.moveCurrentSelection(-dx[i], -dy[i]);
300ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford            }
301ec1f915006e5ec2db8be633b5d3d73e568951da4John Hoford            mCropObj.selectEdge(CropObject.MOVE_NONE);
30262e962bcb9fc03f3cfeac5ece8d3e95fc2dd0718Ruben Brunk        }
303b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        // Draw actual bitmap
304b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mPaint.reset();
305b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mPaint.setAntiAlias(true);
306b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mPaint.setFilterBitmap(true);
307b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        canvas.drawBitmap(bitmap, mDisplayMatrix, mPaint);
308b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        mCropObj.getInnerBounds(mScreenCropBounds);
309b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        RectF outer = mCropObj.getOuterBounds();
310b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        FilterCropRepresentation.findNormalizedCrop(mScreenCropBounds, (int) outer.width(),
311b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                (int) outer.height());
312b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        FilterCropRepresentation.findScaledCrop(mScreenCropBounds, bitmap.getWidth(),
313b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                bitmap.getHeight());
314b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk        if (mDisplayCropMatrix.mapRect(mScreenCropBounds)) {
315b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            // Draw crop rect and markers
316b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
317805a7fe57da2534462f4720abc7fe9cdb8f7ecc9John Hoford            CropDrawingUtils.drawShade(canvas, mScreenCropBounds);
318b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
319b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk            CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize,
320b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    mScreenCropBounds, mCropObj.isFixedAspect(),
321b0f7a8f7f7d95ae12e92f529fd9a8a37f75b105cRuben Brunk                    decode(mCropObj.getSelectState(), mGeometry.rotation.value()));
3229820e7e753b7e1977ef3d2163605431769ce9165nicolasroard        }
3239820e7e753b7e1977ef3d2163605431769ce9165nicolasroard    }
3249820e7e753b7e1977ef3d2163605431769ce9165nicolasroard
325d61a2f9fcaad0309132b6b9b666c3dc6df62fed7John Hoford    public void setEditor(EditorCrop editorCrop) {
326d61a2f9fcaad0309132b6b9b666c3dc6df62fed7John Hoford        mEditorCrop = editorCrop;
327d61a2f9fcaad0309132b6b9b666c3dc6df62fed7John Hoford    }
3280f7dc6ef6e736c0993240450b50b91721c79c43eRuben Brunk}
329