1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.gallery3d.filtershow.imageshow;
18
19import android.animation.ValueAnimator;
20import android.content.Context;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.Matrix;
25import android.graphics.Paint;
26import android.graphics.Paint.Style;
27import android.graphics.Path;
28import android.graphics.RectF;
29import android.util.AttributeSet;
30import android.view.MotionEvent;
31
32import com.android.gallery3d.filtershow.crop.CropDrawingUtils;
33import com.android.gallery3d.filtershow.editors.EditorStraighten;
34import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
35import com.android.gallery3d.filtershow.filters.FilterRepresentation;
36import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation;
37import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;
38
39import java.util.ArrayList;
40import java.util.Collection;
41
42
43public class ImageStraighten extends ImageShow {
44    private static final String TAG = ImageStraighten.class.getSimpleName();
45    private float mBaseAngle = 0;
46    private float mAngle = 0;
47    private float mInitialAngle = 0;
48    private static final int NBLINES = 16;
49    private boolean mFirstDrawSinceUp = false;
50    private EditorStraighten mEditorStraighten;
51    private FilterStraightenRepresentation mLocalRep = new FilterStraightenRepresentation();
52    private RectF mPriorCropAtUp = new RectF();
53    private RectF mDrawRect = new RectF();
54    private Path mDrawPath = new Path();
55    private GeometryHolder mDrawHolder = new GeometryHolder();
56    private enum MODES {
57        NONE, MOVE
58    }
59    private MODES mState = MODES.NONE;
60    private ValueAnimator mAnimator = null;
61    private int mDefaultGridAlpha = 60;
62    private float mGridAlpha = 1f;
63    private int mOnStartAnimDelay = 1000;
64    private int mAnimDelay = 500;
65    private static final float MAX_STRAIGHTEN_ANGLE
66        = FilterStraightenRepresentation.MAX_STRAIGHTEN_ANGLE;
67    private static final float MIN_STRAIGHTEN_ANGLE
68        = FilterStraightenRepresentation.MIN_STRAIGHTEN_ANGLE;
69    private float mCurrentX;
70    private float mCurrentY;
71    private float mTouchCenterX;
72    private float mTouchCenterY;
73    private RectF mCrop = new RectF();
74    private final Paint mPaint = new Paint();
75
76    public ImageStraighten(Context context) {
77        super(context);
78    }
79
80    public ImageStraighten(Context context, AttributeSet attrs) {
81        super(context, attrs);
82    }
83
84    @Override
85    public void attach() {
86        super.attach();
87        mGridAlpha = 1f;
88        hidesGrid(mOnStartAnimDelay);
89    }
90
91    private void hidesGrid(int delay) {
92        mAnimator = ValueAnimator.ofFloat(1, 0);
93        mAnimator.setStartDelay(delay);
94        mAnimator.setDuration(mAnimDelay);
95        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
96            @Override
97            public void onAnimationUpdate(ValueAnimator animation) {
98                mGridAlpha = ((Float) animation.getAnimatedValue());
99                invalidate();
100            }
101        });
102        mAnimator.start();
103    }
104
105    public void setFilterStraightenRepresentation(FilterStraightenRepresentation rep) {
106        mLocalRep = (rep == null) ? new FilterStraightenRepresentation() : rep;
107        mInitialAngle = mBaseAngle = mAngle = mLocalRep.getStraighten();
108    }
109
110    public Collection<FilterRepresentation> getFinalRepresentation() {
111        ArrayList<FilterRepresentation> reps = new ArrayList<FilterRepresentation>(2);
112        reps.add(mLocalRep);
113        if (mInitialAngle != mLocalRep.getStraighten()) {
114            reps.add(new FilterCropRepresentation(mCrop));
115        }
116        return reps;
117    }
118
119    @Override
120    public boolean onTouchEvent(MotionEvent event) {
121        float x = event.getX();
122        float y = event.getY();
123
124        switch (event.getActionMasked()) {
125            case (MotionEvent.ACTION_DOWN):
126                if (mState == MODES.NONE) {
127                    mTouchCenterX = x;
128                    mTouchCenterY = y;
129                    mCurrentX = x;
130                    mCurrentY = y;
131                    mState = MODES.MOVE;
132                    mBaseAngle = mAngle;
133                }
134                break;
135            case (MotionEvent.ACTION_UP):
136                if (mState == MODES.MOVE) {
137                    mState = MODES.NONE;
138                    mCurrentX = x;
139                    mCurrentY = y;
140                    computeValue();
141                    mFirstDrawSinceUp = true;
142                    hidesGrid(0);
143                }
144                break;
145            case (MotionEvent.ACTION_MOVE):
146                if (mState == MODES.MOVE) {
147                    mCurrentX = x;
148                    mCurrentY = y;
149                    computeValue();
150                }
151                break;
152            default:
153                break;
154        }
155        invalidate();
156        return true;
157    }
158
159    private static float angleFor(float dx, float dy) {
160        return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
161    }
162
163    private float getCurrentTouchAngle() {
164        float centerX = getWidth() / 2f;
165        float centerY = getHeight() / 2f;
166        if (mCurrentX == mTouchCenterX && mCurrentY == mTouchCenterY) {
167            return 0;
168        }
169        float dX1 = mTouchCenterX - centerX;
170        float dY1 = mTouchCenterY - centerY;
171        float dX2 = mCurrentX - centerX;
172        float dY2 = mCurrentY - centerY;
173        float angleA = angleFor(dX1, dY1);
174        float angleB = angleFor(dX2, dY2);
175        return (angleB - angleA) % 360;
176    }
177
178    private void computeValue() {
179        float angle = getCurrentTouchAngle();
180        mAngle = (mBaseAngle - angle) % 360;
181        mAngle = Math.max(MIN_STRAIGHTEN_ANGLE, mAngle);
182        mAngle = Math.min(MAX_STRAIGHTEN_ANGLE, mAngle);
183    }
184
185    public static void getUntranslatedStraightenCropBounds(RectF outRect, float straightenAngle) {
186        float deg = straightenAngle;
187        if (deg < 0) {
188            deg = -deg;
189        }
190        double a = Math.toRadians(deg);
191        double sina = Math.sin(a);
192        double cosa = Math.cos(a);
193        double rw = outRect.width();
194        double rh = outRect.height();
195        double h1 = rh * rh / (rw * sina + rh * cosa);
196        double h2 = rh * rw / (rw * cosa + rh * sina);
197        double hh = Math.min(h1, h2);
198        double ww = hh * rw / rh;
199        float left = (float) ((rw - ww) * 0.5f);
200        float top = (float) ((rh - hh) * 0.5f);
201        float right = (float) (left + ww);
202        float bottom = (float) (top + hh);
203        outRect.set(left, top, right, bottom);
204    }
205
206    private void updateCurrentCrop(Matrix m, GeometryHolder h, RectF tmp, int imageWidth,
207            int imageHeight, int viewWidth, int viewHeight) {
208        tmp.set(0, 0, imageHeight, imageWidth);
209        m.mapRect(tmp);
210        float top = tmp.top;
211        float bottom = tmp.bottom;
212        float left = tmp.left;
213        float right = tmp.right;
214        m.mapRect(tmp);
215        int iw,ih;
216        if (GeometryMathUtils.needsDimensionSwap(h.rotation)) {
217            tmp.set(0, 0, imageHeight, imageWidth);
218            iw = imageHeight;
219            ih = imageWidth;
220        } else {
221            tmp.set(0, 0, imageWidth, imageHeight);
222            iw = imageWidth;
223            ih = imageHeight;
224        }
225        float scale = GeometryMathUtils.scale(iw, ih, viewWidth, viewHeight);
226        scale *= GeometryMathUtils.SHOW_SCALE;
227        GeometryMathUtils.scaleRect(tmp, scale);
228        getUntranslatedStraightenCropBounds(tmp, mAngle);
229        tmp.offset(viewWidth / 2f - tmp.centerX(), viewHeight / 2f - tmp.centerY());
230        h.straighten = 0;
231        Matrix m1 = GeometryMathUtils.getFullGeometryToScreenMatrix(h, imageWidth,
232                imageHeight, viewWidth, viewHeight);
233        m.reset();
234        m1.invert(m);
235        mCrop.set(tmp);
236        m.mapRect(mCrop);
237        FilterCropRepresentation.findNormalizedCrop(mCrop, imageWidth, imageHeight);
238    }
239
240
241    @Override
242    public void onDraw(Canvas canvas) {
243        MasterImage master = MasterImage.getImage();
244        Bitmap image = master.getFiltersOnlyImage();
245        if (image == null) {
246            MasterImage.getImage().invalidateFiltersOnly();
247            return;
248        }
249        GeometryMathUtils.initializeHolder(mDrawHolder, mLocalRep);
250        mDrawHolder.straighten = mAngle;
251        int imageWidth = image.getWidth();
252        int imageHeight = image.getHeight();
253        int viewWidth = canvas.getWidth();
254        int viewHeight = canvas.getHeight();
255
256        // Get matrix for drawing bitmap
257        Matrix m = GeometryMathUtils.getFullGeometryToScreenMatrix(mDrawHolder, imageWidth,
258                imageHeight, viewWidth, viewHeight);
259        mPaint.reset();
260        mPaint.setAntiAlias(true);
261        mPaint.setFilterBitmap(true);
262        canvas.drawBitmap(image, m, mPaint);
263
264        mPaint.setFilterBitmap(false);
265        mPaint.setColor(Color.WHITE);
266        mPaint.setStrokeWidth(2);
267        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
268        updateCurrentCrop(m, mDrawHolder, mDrawRect, imageWidth,
269                imageHeight, viewWidth, viewHeight);
270        if (mFirstDrawSinceUp) {
271            mPriorCropAtUp.set(mCrop);
272            mLocalRep.setStraighten(mAngle);
273            mFirstDrawSinceUp = false;
274        }
275        CropDrawingUtils.drawShade(canvas, mDrawRect);
276        // Draw the grid
277        if (mState == MODES.MOVE || mGridAlpha > 0) {
278            canvas.save();
279            canvas.clipRect(mDrawRect);
280
281            float step = Math.max(viewWidth, viewHeight) / NBLINES;
282            float p = 0;
283            for (int i = 1; i < NBLINES; i++) {
284                p = i * step;
285                int alpha = (int) (mDefaultGridAlpha * mGridAlpha);
286                if (alpha == 0 && mState == MODES.MOVE) {
287                    alpha = mDefaultGridAlpha;
288                }
289                mPaint.setAlpha(alpha);
290                canvas.drawLine(p, 0, p, viewHeight, mPaint);
291                canvas.drawLine(0, p, viewWidth, p, mPaint);
292            }
293            canvas.restore();
294        }
295        mPaint.reset();
296        mPaint.setColor(Color.WHITE);
297        mPaint.setStyle(Style.STROKE);
298        mPaint.setStrokeWidth(3);
299        mDrawPath.reset();
300
301
302        mDrawPath.addRect(mDrawRect, Path.Direction.CW);
303        canvas.drawPath(mDrawPath, mPaint);
304    }
305
306    public void setEditor(EditorStraighten editorStraighten) {
307        mEditorStraighten = editorStraighten;
308    }
309
310}
311