1ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin/*
2ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin * Copyright (C) 2009 The Android Open Source Project
3ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin *
4ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin * Licensed under the Apache License, Version 2.0 (the "License");
5ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin * you may not use this file except in compliance with the License.
6ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin * You may obtain a copy of the License at
7ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin *
8ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin *      http://www.apache.org/licenses/LICENSE-2.0
9ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin *
10ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin * Unless required by applicable law or agreed to in writing, software
11ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin * distributed under the License is distributed on an "AS IS" BASIS,
12ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin * See the License for the specific language governing permissions and
14ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin * limitations under the License.
15ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin */
16ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin
176fb969e2edd99340b3b77eae4e7b313a82937930Andy Stadlerpackage com.android.camera.ui;
18ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin
19ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Linimport android.content.Context;
209f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Liimport android.graphics.Bitmap;
21ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Linimport android.graphics.Canvas;
226fb969e2edd99340b3b77eae4e7b313a82937930Andy Stadlerimport android.graphics.Rect;
239f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Liimport android.graphics.drawable.BitmapDrawable;
24ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Linimport android.graphics.drawable.Drawable;
256fb969e2edd99340b3b77eae4e7b313a82937930Andy Stadlerimport android.graphics.drawable.TransitionDrawable;
269f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Liimport android.media.ThumbnailUtils;
27ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Linimport android.util.AttributeSet;
289f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Liimport android.view.ViewGroup.LayoutParams;
296fb969e2edd99340b3b77eae4e7b313a82937930Andy Stadlerimport android.view.animation.AnimationUtils;
30ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Linimport android.widget.ImageView;
31ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin
32ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin/**
33ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin * A @{code ImageView} which can rotate it's content.
34ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin */
35c12e12365fc3b0b3b4718eced8f3dc6a776c0851Angus Kongpublic class RotateImageView extends TwoStateImageView implements Rotatable {
36ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin
37ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin    @SuppressWarnings("unused")
38ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin    private static final String TAG = "RotateImageView";
39ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin
405e270b255775e1aae76cb476873001798ad8e88aWu-cheng Li    private static final int ANIMATION_SPEED = 270; // 270 deg/sec
41b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin
42b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin    private int mCurrentDegree = 0; // [0, 359]
43b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin    private int mStartDegree = 0;
44b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin    private int mTargetDegree = 0;
45b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin
469f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li    private boolean mClockwise = false, mEnableAnimation = true;
47b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin
48b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin    private long mAnimationStartTime = 0;
49b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin    private long mAnimationEndTime = 0;
50ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin
51ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin    public RotateImageView(Context context, AttributeSet attrs) {
52ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin        super(context, attrs);
53ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin    }
54ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin
55cead50ba3ce2df3bd9fcf06c6f54ea0c6980f0d7repo sync    public RotateImageView(Context context) {
56cead50ba3ce2df3bd9fcf06c6f54ea0c6980f0d7repo sync        super(context);
57cead50ba3ce2df3bd9fcf06c6f54ea0c6980f0d7repo sync    }
58cead50ba3ce2df3bd9fcf06c6f54ea0c6980f0d7repo sync
599f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li    public void enableAnimation(boolean enable) {
609f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        mEnableAnimation = enable;
619f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li    }
629f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li
63cead50ba3ce2df3bd9fcf06c6f54ea0c6980f0d7repo sync    protected int getDegree() {
64cead50ba3ce2df3bd9fcf06c6f54ea0c6980f0d7repo sync        return mTargetDegree;
65cead50ba3ce2df3bd9fcf06c6f54ea0c6980f0d7repo sync    }
66cead50ba3ce2df3bd9fcf06c6f54ea0c6980f0d7repo sync
67c5e37cbb96b8284203560c51dd5f41c705a21f58Wu-cheng Li    // Rotate the view counter-clockwise
683ebe49d8f1b4defb7cfd4850a14e795aada2ebd1Chih-yu Huang    public void setOrientation(int degree) {
69ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin        // make sure in the range of [0, 359]
70ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin        degree = degree >= 0 ? degree % 360 : degree % 360 + 360;
71b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        if (degree == mTargetDegree) return;
72b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin
73b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        mTargetDegree = degree;
74b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        mStartDegree = mCurrentDegree;
75b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        mAnimationStartTime = AnimationUtils.currentAnimationTimeMillis();
76b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin
77b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        int diff = mTargetDegree - mCurrentDegree;
78b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        diff = diff >= 0 ? diff : 360 + diff; // make it in range [0, 359]
79b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin
80b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        // Make it in range [-179, 180]. That's the shorted distance between the
81b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        // two angles
82b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        diff = diff > 180 ? diff - 360 : diff;
83b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin
84b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        mClockwise = diff >= 0;
85b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        mAnimationEndTime = mAnimationStartTime
86b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin                + Math.abs(diff) * 1000 / ANIMATION_SPEED;
87b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin
88ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin        invalidate();
89ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin    }
90ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin
91ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin    @Override
92ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin    protected void onDraw(Canvas canvas) {
93ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin        Drawable drawable = getDrawable();
946988d4e2ef8d14d8bf3ee81c8eb3175bbf1b88ecOwen Lin        if (drawable == null) return;
95b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin
96ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin        Rect bounds = drawable.getBounds();
97ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin        int w = bounds.right - bounds.left;
98ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin        int h = bounds.bottom - bounds.top;
99ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin
100ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin        if (w == 0 || h == 0) return; // nothing to draw
101ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin
102b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        if (mCurrentDegree != mTargetDegree) {
103b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin            long time = AnimationUtils.currentAnimationTimeMillis();
104b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin            if (time < mAnimationEndTime) {
105b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin                int deltaTime = (int)(time - mAnimationStartTime);
106b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin                int degree = mStartDegree + ANIMATION_SPEED
107b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin                        * (mClockwise ? deltaTime : -deltaTime) / 1000;
108b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin                degree = degree >= 0 ? degree % 360 : degree % 360 + 360;
109b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin                mCurrentDegree = degree;
110b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin                invalidate();
111b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin            } else {
112b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin                mCurrentDegree = mTargetDegree;
113b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin            }
114b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        }
1156988d4e2ef8d14d8bf3ee81c8eb3175bbf1b88ecOwen Lin
1166988d4e2ef8d14d8bf3ee81c8eb3175bbf1b88ecOwen Lin        int left = getPaddingLeft();
1176988d4e2ef8d14d8bf3ee81c8eb3175bbf1b88ecOwen Lin        int top = getPaddingTop();
1186988d4e2ef8d14d8bf3ee81c8eb3175bbf1b88ecOwen Lin        int right = getPaddingRight();
1196988d4e2ef8d14d8bf3ee81c8eb3175bbf1b88ecOwen Lin        int bottom = getPaddingBottom();
1206988d4e2ef8d14d8bf3ee81c8eb3175bbf1b88ecOwen Lin        int width = getWidth() - left - right;
1216988d4e2ef8d14d8bf3ee81c8eb3175bbf1b88ecOwen Lin        int height = getHeight() - top - bottom;
1226988d4e2ef8d14d8bf3ee81c8eb3175bbf1b88ecOwen Lin
123b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        int saveCount = canvas.getSaveCount();
124b5c569214281e11dc7e8ffc313fbb27e5ff6e5c5Chung-yih Wang
125b5c569214281e11dc7e8ffc313fbb27e5ff6e5c5Chung-yih Wang        // Scale down the image first if required.
126b5c569214281e11dc7e8ffc313fbb27e5ff6e5c5Chung-yih Wang        if ((getScaleType() == ImageView.ScaleType.FIT_CENTER) &&
127b5c569214281e11dc7e8ffc313fbb27e5ff6e5c5Chung-yih Wang                ((width < w) || (height < h))) {
128b5c569214281e11dc7e8ffc313fbb27e5ff6e5c5Chung-yih Wang            float ratio = Math.min((float) width / w, (float) height / h);
129b5c569214281e11dc7e8ffc313fbb27e5ff6e5c5Chung-yih Wang            canvas.scale(ratio, ratio, width / 2.0f, height / 2.0f);
130b5c569214281e11dc7e8ffc313fbb27e5ff6e5c5Chung-yih Wang        }
1316988d4e2ef8d14d8bf3ee81c8eb3175bbf1b88ecOwen Lin        canvas.translate(left + width / 2, top + height / 2);
132b77f928e01f24fc36ea4808b7373f502e7e6be41Owen Lin        canvas.rotate(-mCurrentDegree);
1336988d4e2ef8d14d8bf3ee81c8eb3175bbf1b88ecOwen Lin        canvas.translate(-w / 2, -h / 2);
134ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin        drawable.draw(canvas);
135ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin        canvas.restoreToCount(saveCount);
136ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin    }
1379f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li
1389f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li    private Bitmap mThumb;
1399f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li    private Drawable[] mThumbs;
1409f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li    private TransitionDrawable mThumbTransition;
1419f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li
1428ab2b624d51b3b8254ece98c46a7e22a6fb5d4aaWu-cheng Li    public void setBitmap(Bitmap bitmap) {
1439f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        // Make sure uri and original are consistently both null or both
1449f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        // non-null.
1458ab2b624d51b3b8254ece98c46a7e22a6fb5d4aaWu-cheng Li        if (bitmap == null) {
1469f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            mThumb = null;
1479f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            mThumbs = null;
1489f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            setImageDrawable(null);
1498ab2b624d51b3b8254ece98c46a7e22a6fb5d4aaWu-cheng Li            setVisibility(GONE);
1509f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            return;
1519f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        }
1529f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li
1539f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        LayoutParams param = getLayoutParams();
1549f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        final int miniThumbWidth = param.width
1559f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li                - getPaddingLeft() - getPaddingRight();
1569f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        final int miniThumbHeight = param.height
1579f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li                - getPaddingTop() - getPaddingBottom();
1589f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        mThumb = ThumbnailUtils.extractThumbnail(
1598ab2b624d51b3b8254ece98c46a7e22a6fb5d4aaWu-cheng Li                bitmap, miniThumbWidth, miniThumbHeight);
1609f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        Drawable drawable;
1619f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        if (mThumbs == null || !mEnableAnimation) {
1629f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            mThumbs = new Drawable[2];
1639f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            mThumbs[1] = new BitmapDrawable(getContext().getResources(), mThumb);
1649f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            setImageDrawable(mThumbs[1]);
1659f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        } else {
1669f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            mThumbs[0] = mThumbs[1];
1679f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            mThumbs[1] = new BitmapDrawable(getContext().getResources(), mThumb);
1689f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            mThumbTransition = new TransitionDrawable(mThumbs);
1699f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            setImageDrawable(mThumbTransition);
1709f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li            mThumbTransition.startTransition(500);
1719f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li        }
1728ab2b624d51b3b8254ece98c46a7e22a6fb5d4aaWu-cheng Li        setVisibility(VISIBLE);
1739f73bd9a85d295091fae39dc256a122e1843e2e8Wu-cheng Li    }
174ff58fb6921ccb7398ad30c8cec604c38c76be4fcOwen Lin}
175