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 
17 package com.android.gallery3d.filtershow.imageshow;
18 
19 import android.animation.ValueAnimator;
20 import android.content.Context;
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.Color;
24 import android.graphics.Matrix;
25 import android.graphics.Paint;
26 import android.graphics.Paint.Style;
27 import android.graphics.Path;
28 import android.graphics.RectF;
29 import android.util.AttributeSet;
30 import android.view.MotionEvent;
31 
32 import com.android.gallery3d.filtershow.crop.CropDrawingUtils;
33 import com.android.gallery3d.filtershow.editors.EditorStraighten;
34 import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
35 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
36 import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation;
37 import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils.GeometryHolder;
38 
39 import java.util.ArrayList;
40 import java.util.Collection;
41 
42 
43 public 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 
ImageStraighten(Context context)76     public ImageStraighten(Context context) {
77         super(context);
78     }
79 
ImageStraighten(Context context, AttributeSet attrs)80     public ImageStraighten(Context context, AttributeSet attrs) {
81         super(context, attrs);
82     }
83 
84     @Override
attach()85     public void attach() {
86         super.attach();
87         mGridAlpha = 1f;
88         hidesGrid(mOnStartAnimDelay);
89     }
90 
hidesGrid(int delay)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 
setFilterStraightenRepresentation(FilterStraightenRepresentation rep)105     public void setFilterStraightenRepresentation(FilterStraightenRepresentation rep) {
106         mLocalRep = (rep == null) ? new FilterStraightenRepresentation() : rep;
107         mInitialAngle = mBaseAngle = mAngle = mLocalRep.getStraighten();
108     }
109 
getFinalRepresentation()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
onTouchEvent(MotionEvent event)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 
angleFor(float dx, float dy)159     private static float angleFor(float dx, float dy) {
160         return (float) (Math.atan2(dx, dy) * 180 / Math.PI);
161     }
162 
getCurrentTouchAngle()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 
computeValue()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 
getUntranslatedStraightenCropBounds(RectF outRect, float straightenAngle)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 
updateCurrentCrop(Matrix m, GeometryHolder h, RectF tmp, int imageWidth, int imageHeight, int viewWidth, int viewHeight)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
onDraw(Canvas canvas)242     public void onDraw(Canvas canvas) {
243         PrimaryImage primary = PrimaryImage.getImage();
244         Bitmap image = primary.getFiltersOnlyImage();
245         if (image == null) {
246             PrimaryImage.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 
setEditor(EditorStraighten editorStraighten)306     public void setEditor(EditorStraighten editorStraighten) {
307         mEditorStraighten = editorStraighten;
308     }
309 
310 }
311