OSDN Git Service

Fix crash / jank / presets add
[android-x86/packages-apps-Gallery2.git] / src / com / android / gallery3d / filtershow / imageshow / ImageShow.java
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.Animator;
20 import android.animation.ValueAnimator;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.graphics.BitmapShader;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.Matrix;
29 import android.graphics.Paint;
30 import android.graphics.Point;
31 import android.graphics.Rect;
32 import android.graphics.RectF;
33 import android.graphics.Shader;
34 import android.graphics.drawable.NinePatchDrawable;
35 import android.support.v4.widget.EdgeEffectCompat;
36 import android.util.AttributeSet;
37 import android.view.GestureDetector;
38 import android.view.GestureDetector.OnDoubleTapListener;
39 import android.view.GestureDetector.OnGestureListener;
40 import android.view.MotionEvent;
41 import android.view.ScaleGestureDetector;
42 import android.view.View;
43 import android.widget.LinearLayout;
44
45 import com.android.gallery3d.R;
46 import com.android.gallery3d.filtershow.FilterShowActivity;
47 import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
48 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
49 import com.android.gallery3d.filtershow.filters.ImageFilter;
50 import com.android.gallery3d.filtershow.pipeline.ImagePreset;
51 import com.android.gallery3d.filtershow.tools.SaveImage;
52
53 import java.io.File;
54 import java.util.ArrayList;
55
56 public class ImageShow extends View implements OnGestureListener,
57         ScaleGestureDetector.OnScaleGestureListener,
58         OnDoubleTapListener {
59
60     private static final String LOGTAG = "ImageShow";
61     private static final boolean ENABLE_ZOOMED_COMPARISON = false;
62
63     protected Paint mPaint = new Paint();
64     protected int mTextSize;
65     protected int mTextPadding;
66
67     protected int mBackgroundColor;
68
69     private GestureDetector mGestureDetector = null;
70     private ScaleGestureDetector mScaleGestureDetector = null;
71
72     protected Rect mImageBounds = new Rect();
73     private boolean mOriginalDisabled = false;
74     private boolean mTouchShowOriginal = false;
75     private long mTouchShowOriginalDate = 0;
76     private final long mTouchShowOriginalDelayMin = 200; // 200ms
77     private int mShowOriginalDirection = 0;
78     private static int UNVEIL_HORIZONTAL = 1;
79     private static int UNVEIL_VERTICAL = 2;
80
81     private NinePatchDrawable mShadow = null;
82     private Rect mShadowBounds = new Rect();
83     private int mShadowMargin = 15; // not scaled, fixed in the asset
84     private boolean mShadowDrawn = false;
85
86     private Point mTouchDown = new Point();
87     private Point mTouch = new Point();
88     private boolean mFinishedScalingOperation = false;
89
90     private int mOriginalTextMargin;
91     private int mOriginalTextSize;
92     private String mOriginalText;
93     private boolean mZoomIn = false;
94     Point mOriginalTranslation = new Point();
95     float mOriginalScale;
96     float mStartFocusX, mStartFocusY;
97
98     private EdgeEffectCompat mEdgeEffect = null;
99     private static final int EDGE_LEFT = 1;
100     private static final int EDGE_TOP = 2;
101     private static final int EDGE_RIGHT = 3;
102     private static final int EDGE_BOTTOM = 4;
103     private int mCurrentEdgeEffect = 0;
104     private int mEdgeSize = 100;
105
106     private static final int mAnimationSnapDelay = 200;
107     private static final int mAnimationZoomDelay = 400;
108     private ValueAnimator mAnimatorScale = null;
109     private ValueAnimator mAnimatorTranslateX = null;
110     private ValueAnimator mAnimatorTranslateY = null;
111
112     private enum InteractionMode {
113         NONE,
114         SCALE,
115         MOVE
116     }
117     InteractionMode mInteractionMode = InteractionMode.NONE;
118
119     private static Bitmap sMask;
120     private Paint mMaskPaint = new Paint();
121     private Matrix mShaderMatrix = new Matrix();
122     private boolean mDidStartAnimation = false;
123
124     private static Bitmap convertToAlphaMask(Bitmap b) {
125         Bitmap a = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ALPHA_8);
126         Canvas c = new Canvas(a);
127         c.drawBitmap(b, 0.0f, 0.0f, null);
128         return a;
129     }
130
131     private static Shader createShader(Bitmap b) {
132         return new BitmapShader(b, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
133     }
134
135     private FilterShowActivity mActivity = null;
136
137     public FilterShowActivity getActivity() {
138         return mActivity;
139     }
140
141     public boolean hasModifications() {
142         return MasterImage.getImage().hasModifications();
143     }
144
145     public void resetParameter() {
146         // TODO: implement reset
147     }
148
149     public void onNewValue(int parameter) {
150         invalidate();
151     }
152
153     public ImageShow(Context context, AttributeSet attrs, int defStyle) {
154         super(context, attrs, defStyle);
155         setupImageShow(context);
156     }
157
158     public ImageShow(Context context, AttributeSet attrs) {
159         super(context, attrs);
160         setupImageShow(context);
161
162     }
163
164     public ImageShow(Context context) {
165         super(context);
166         setupImageShow(context);
167     }
168
169     private void setupImageShow(Context context) {
170         Resources res = context.getResources();
171         mTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_text_size);
172         mTextPadding = res.getDimensionPixelSize(R.dimen.photoeditor_text_padding);
173         mOriginalTextMargin = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_margin);
174         mOriginalTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_size);
175         mBackgroundColor = res.getColor(R.color.background_screen);
176         mOriginalText = res.getString(R.string.original_picture_text);
177         mShadow = (NinePatchDrawable) res.getDrawable(R.drawable.geometry_shadow);
178         setupGestureDetector(context);
179         mActivity = (FilterShowActivity) context;
180         if (sMask == null) {
181             Bitmap mask = BitmapFactory.decodeResource(res, R.drawable.spot_mask);
182             sMask = convertToAlphaMask(mask);
183         }
184         mEdgeEffect = new EdgeEffectCompat(context);
185         mEdgeSize = res.getDimensionPixelSize(R.dimen.edge_glow_size);
186     }
187
188     public void attach() {
189         MasterImage.getImage().addObserver(this);
190         bindAsImageLoadListener();
191         MasterImage.getImage().resetGeometryImages(false);
192     }
193
194     public void detach() {
195         MasterImage.getImage().removeObserver(this);
196         mMaskPaint.reset();
197     }
198
199     public void setupGestureDetector(Context context) {
200         mGestureDetector = new GestureDetector(context, this);
201         mScaleGestureDetector = new ScaleGestureDetector(context, this);
202     }
203
204     @Override
205     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
206         int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
207         int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
208         setMeasuredDimension(parentWidth, parentHeight);
209     }
210
211     public ImageFilter getCurrentFilter() {
212         return MasterImage.getImage().getCurrentFilter();
213     }
214
215     /* consider moving the following 2 methods into a subclass */
216     /**
217      * This function calculates a Image to Screen Transformation matrix
218      *
219      * @param reflectRotation set true if you want the rotation encoded
220      * @return Image to Screen transformation matrix
221      */
222     protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
223         MasterImage master = MasterImage.getImage();
224         if (master.getOriginalBounds() == null) {
225             return new Matrix();
226         }
227         Matrix m = GeometryMathUtils.getImageToScreenMatrix(master.getPreset().getGeometryFilters(),
228                 reflectRotation, master.getOriginalBounds(), getWidth(), getHeight());
229         Point translate = master.getTranslation();
230         float scaleFactor = master.getScaleFactor();
231         m.postTranslate(translate.x, translate.y);
232         m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f);
233         return m;
234     }
235
236     /**
237      * This function calculates a to Screen Image Transformation matrix
238      *
239      * @param reflectRotation set true if you want the rotation encoded
240      * @return Screen to Image transformation matrix
241      */
242     protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
243         Matrix m = getImageToScreenMatrix(reflectRotation);
244         Matrix invert = new Matrix();
245         m.invert(invert);
246         return invert;
247     }
248
249     public ImagePreset getImagePreset() {
250         return MasterImage.getImage().getPreset();
251     }
252
253     @Override
254     public void onDraw(Canvas canvas) {
255         mPaint.reset();
256         mPaint.setAntiAlias(true);
257         mPaint.setFilterBitmap(true);
258         MasterImage.getImage().setImageShowSize(
259                 getWidth() - 2*mShadowMargin,
260                 getHeight() - 2*mShadowMargin);
261
262         MasterImage img = MasterImage.getImage();
263         // Hide the loading indicator as needed
264         if (mActivity.isLoadingVisible() && getFilteredImage() != null) {
265             if ((img.getLoadedPreset() == null)
266                     || (img.getLoadedPreset() != null
267                     && img.getLoadedPreset().equals(img.getCurrentPreset()))) {
268                 mActivity.stopLoadingIndicator();
269             } else if (img.getLoadedPreset() != null) {
270                 return;
271             }
272             mActivity.stopLoadingIndicator();
273         }
274
275         canvas.save();
276
277         mShadowDrawn = false;
278
279         Bitmap highresPreview = MasterImage.getImage().getHighresImage();
280         Bitmap fullHighres = MasterImage.getImage().getPartialImage();
281
282         boolean isDoingNewLookAnimation = MasterImage.getImage().onGoingNewLookAnimation();
283
284         if (highresPreview == null || isDoingNewLookAnimation) {
285             drawImageAndAnimate(canvas, getFilteredImage());
286         } else {
287             drawImageAndAnimate(canvas, highresPreview);
288         }
289
290         drawHighresImage(canvas, fullHighres);
291         drawCompareImage(canvas, getGeometryOnlyImage());
292
293         canvas.restore();
294
295         if (!mEdgeEffect.isFinished()) {
296             canvas.save();
297             float dx = (getHeight() - getWidth()) / 2f;
298             if (getWidth() > getHeight()) {
299                 dx = - (getWidth() - getHeight()) / 2f;
300             }
301             if (mCurrentEdgeEffect == EDGE_BOTTOM) {
302                 canvas.rotate(180, getWidth()/2, getHeight()/2);
303             } else if (mCurrentEdgeEffect == EDGE_RIGHT) {
304                 canvas.rotate(90, getWidth()/2, getHeight()/2);
305                 canvas.translate(0, dx);
306             } else if (mCurrentEdgeEffect == EDGE_LEFT) {
307                 canvas.rotate(270, getWidth()/2, getHeight()/2);
308                 canvas.translate(0, dx);
309             }
310             if (mCurrentEdgeEffect != 0) {
311                 mEdgeEffect.draw(canvas);
312             }
313             canvas.restore();
314             invalidate();
315         } else {
316             mCurrentEdgeEffect = 0;
317         }
318     }
319
320     private void drawHighresImage(Canvas canvas, Bitmap fullHighres) {
321         Matrix originalToScreen = MasterImage.getImage().originalImageToScreen();
322         if (fullHighres != null && originalToScreen != null) {
323             Matrix screenToOriginal = new Matrix();
324             originalToScreen.invert(screenToOriginal);
325             Rect rBounds = new Rect();
326             rBounds.set(MasterImage.getImage().getPartialBounds());
327             if (fullHighres != null) {
328                 originalToScreen.preTranslate(rBounds.left, rBounds.top);
329                 canvas.clipRect(mImageBounds);
330                 canvas.drawBitmap(fullHighres, originalToScreen, mPaint);
331             }
332         }
333     }
334
335     public void resetImageCaches(ImageShow caller) {
336         MasterImage.getImage().invalidatePreview();
337     }
338
339     public Bitmap getFiltersOnlyImage() {
340         return MasterImage.getImage().getFiltersOnlyImage();
341     }
342
343     public Bitmap getGeometryOnlyImage() {
344         return MasterImage.getImage().getGeometryOnlyImage();
345     }
346
347     public Bitmap getFilteredImage() {
348         return MasterImage.getImage().getFilteredImage();
349     }
350
351     public void drawImageAndAnimate(Canvas canvas,
352                                     Bitmap image) {
353         if (image == null) {
354             return;
355         }
356         MasterImage master = MasterImage.getImage();
357         Matrix m = master.computeImageToScreen(image, 0, false);
358         if (m == null) {
359             return;
360         }
361
362         canvas.save();
363
364         RectF d = new RectF(0, 0, image.getWidth(), image.getHeight());
365         m.mapRect(d);
366         d.roundOut(mImageBounds);
367
368         if (master.onGoingNewLookAnimation()
369                 || mDidStartAnimation) {
370             mDidStartAnimation = true;
371             canvas.save();
372
373             // Animation uses the image before the change
374             Bitmap previousImage = master.getPreviousImage();
375             Matrix mp = master.computeImageToScreen(previousImage, 0, false);
376             RectF dp = new RectF(0, 0, previousImage.getWidth(), previousImage.getHeight());
377             mp.mapRect(dp);
378             Rect previousBounds = new Rect();
379             dp.roundOut(previousBounds);
380             float centerX = dp.centerX();
381             float centerY = dp.centerY();
382             boolean needsToDrawImage = true;
383
384             if (master.getCurrentLookAnimation()
385                     == MasterImage.CIRCLE_ANIMATION) {
386                 float maskScale = MasterImage.getImage().getMaskScale();
387                 if (maskScale >= 0.0f) {
388                     float maskW = sMask.getWidth() / 2.0f;
389                     float maskH = sMask.getHeight() / 2.0f;
390
391                     Point point = mActivity.hintTouchPoint(this);
392                     float x = point.x - maskW * maskScale;
393                     float y = point.y - maskH * maskScale;
394
395                     // Prepare the shader
396                     mShaderMatrix.reset();
397                     mShaderMatrix.setScale(1.0f / maskScale, 1.0f / maskScale);
398                     mShaderMatrix.preTranslate(-x + mImageBounds.left, -y + mImageBounds.top);
399                     float scaleImageX = mImageBounds.width() / (float) image.getWidth();
400                     float scaleImageY = mImageBounds.height() / (float) image.getHeight();
401                     mShaderMatrix.preScale(scaleImageX, scaleImageY);
402                     mMaskPaint.reset();
403                     mMaskPaint.setShader(createShader(image));
404                     mMaskPaint.getShader().setLocalMatrix(mShaderMatrix);
405
406                     drawShadow(canvas, mImageBounds); // as needed
407                     canvas.drawBitmap(previousImage, m, mPaint);
408                     canvas.clipRect(mImageBounds);
409                     canvas.translate(x, y);
410                     canvas.scale(maskScale, maskScale);
411                     canvas.drawBitmap(sMask, 0, 0, mMaskPaint);
412                     needsToDrawImage = false;
413                 }
414             } else if (master.getCurrentLookAnimation()
415                     == MasterImage.ROTATE_ANIMATION) {
416                 Rect d1 = computeImageBounds(master.getPreviousImage().getHeight(),
417                         master.getPreviousImage().getWidth());
418                 Rect d2 = computeImageBounds(master.getPreviousImage().getWidth(),
419                         master.getPreviousImage().getHeight());
420                 float finalScale = d1.width() / (float) d2.height();
421                 finalScale = (1.0f * (1.0f - master.getAnimFraction()))
422                         + (finalScale * master.getAnimFraction());
423                 canvas.rotate(master.getAnimRotationValue(), centerX, centerY);
424                 canvas.scale(finalScale, finalScale, centerX, centerY);
425             } else if (master.getCurrentLookAnimation()
426                     == MasterImage.MIRROR_ANIMATION) {
427                 if (master.getCurrentFilterRepresentation()
428                         instanceof FilterMirrorRepresentation) {
429                     FilterMirrorRepresentation rep =
430                             (FilterMirrorRepresentation) master.getCurrentFilterRepresentation();
431
432                     ImagePreset preset = master.getPreset();
433                     ArrayList<FilterRepresentation> geometry =
434                             (ArrayList<FilterRepresentation>) preset.getGeometryFilters();
435                     GeometryMathUtils.GeometryHolder holder = null;
436                     holder = GeometryMathUtils.unpackGeometry(geometry);
437
438                     if (holder.rotation.value() == 90 || holder.rotation.value() == 270) {
439                         if (rep.isHorizontal() && !rep.isVertical()) {
440                             canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
441                         } else if (rep.isVertical() && !rep.isHorizontal()) {
442                             canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
443                         } else if (rep.isHorizontal() && rep.isVertical()) {
444                             canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
445                         } else {
446                             canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
447                         }
448                     } else {
449                         if (rep.isHorizontal() && !rep.isVertical()) {
450                             canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
451                         } else if (rep.isVertical() && !rep.isHorizontal()) {
452                             canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
453                         } else  if (rep.isHorizontal() && rep.isVertical()) {
454                             canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
455                         } else {
456                             canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
457                         }
458                     }
459                 }
460             }
461
462             if (needsToDrawImage) {
463                 drawShadow(canvas, previousBounds); // as needed
464                 canvas.drawBitmap(previousImage, mp, mPaint);
465             }
466
467             canvas.restore();
468         } else {
469             drawShadow(canvas, mImageBounds); // as needed
470             canvas.drawBitmap(image, m, mPaint);
471         }
472
473         if (!master.onGoingNewLookAnimation()
474                 && mDidStartAnimation
475                 && !master.getPreviousPreset().equals(master.getCurrentPreset())) {
476             mDidStartAnimation = false;
477             MasterImage.getImage().resetAnimBitmap();
478         }
479
480         canvas.restore();
481     }
482
483     private Rect computeImageBounds(int imageWidth, int imageHeight) {
484         float scale = GeometryMathUtils.scale(imageWidth, imageHeight,
485                 getWidth(), getHeight());
486
487         float w = imageWidth * scale;
488         float h = imageHeight * scale;
489         float ty = (getHeight() - h) / 2.0f;
490         float tx = (getWidth() - w) / 2.0f;
491         return new Rect((int) tx + mShadowMargin,
492                 (int) ty + mShadowMargin,
493                 (int) (w + tx) - mShadowMargin,
494                 (int) (h + ty) - mShadowMargin);
495     }
496
497     private void drawShadow(Canvas canvas, Rect d) {
498         if (!mShadowDrawn) {
499             mShadowBounds.set(d.left - mShadowMargin, d.top - mShadowMargin,
500                     d.right + mShadowMargin, d.bottom + mShadowMargin);
501             mShadow.setBounds(mShadowBounds);
502             mShadow.draw(canvas);
503             mShadowDrawn = true;
504         }
505     }
506
507     public void drawCompareImage(Canvas canvas, Bitmap image) {
508         MasterImage master = MasterImage.getImage();
509         boolean showsOriginal = master.showsOriginal();
510         if (!showsOriginal && !mTouchShowOriginal)
511             return;
512         canvas.save();
513         if (image != null) {
514             if (mShowOriginalDirection == 0) {
515                 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
516                     mShowOriginalDirection = UNVEIL_VERTICAL;
517                 } else {
518                     mShowOriginalDirection = UNVEIL_HORIZONTAL;
519                 }
520             }
521
522             int px = 0;
523             int py = 0;
524             if (mShowOriginalDirection == UNVEIL_VERTICAL) {
525                 px = mImageBounds.width();
526                 py = mTouch.y - mImageBounds.top;
527             } else {
528                 px = mTouch.x - mImageBounds.left;
529                 py = mImageBounds.height();
530                 if (showsOriginal) {
531                     px = mImageBounds.width();
532                 }
533             }
534
535             Rect d = new Rect(mImageBounds.left, mImageBounds.top,
536                     mImageBounds.left + px, mImageBounds.top + py);
537             if (mShowOriginalDirection == UNVEIL_HORIZONTAL) {
538                 if (mTouchDown.x - mTouch.x > 0) {
539                     d.set(mImageBounds.left + px, mImageBounds.top,
540                             mImageBounds.right, mImageBounds.top + py);
541                 }
542             } else {
543                 if (mTouchDown.y - mTouch.y > 0) {
544                     d.set(mImageBounds.left, mImageBounds.top + py,
545                             mImageBounds.left + px, mImageBounds.bottom);
546                 }
547             }
548             canvas.clipRect(d);
549             Matrix m = master.computeImageToScreen(image, 0, false);
550             canvas.drawBitmap(image, m, mPaint);
551             Paint paint = new Paint();
552             paint.setColor(Color.BLACK);
553             paint.setStrokeWidth(3);
554
555             if (mShowOriginalDirection == UNVEIL_VERTICAL) {
556                 canvas.drawLine(mImageBounds.left, mTouch.y,
557                         mImageBounds.right, mTouch.y, paint);
558             } else {
559                 canvas.drawLine(mTouch.x, mImageBounds.top,
560                         mTouch.x, mImageBounds.bottom, paint);
561             }
562
563             Rect bounds = new Rect();
564             paint.setAntiAlias(true);
565             paint.setTextSize(mOriginalTextSize);
566             paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
567             paint.setColor(Color.BLACK);
568             paint.setStyle(Paint.Style.STROKE);
569             paint.setStrokeWidth(3);
570             canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
571                     mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
572             paint.setStyle(Paint.Style.FILL);
573             paint.setStrokeWidth(1);
574             paint.setColor(Color.WHITE);
575             canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
576                     mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
577         }
578         canvas.restore();
579     }
580
581     public void bindAsImageLoadListener() {
582         MasterImage.getImage().addListener(this);
583     }
584
585     public void updateImage() {
586         invalidate();
587     }
588
589     public void imageLoaded() {
590         updateImage();
591     }
592
593     public void saveImage(FilterShowActivity filterShowActivity, File file) {
594         SaveImage.saveImage(getImagePreset(), filterShowActivity, file);
595     }
596
597
598     public boolean scaleInProgress() {
599         return mScaleGestureDetector.isInProgress();
600     }
601
602     @Override
603     public boolean onTouchEvent(MotionEvent event) {
604         super.onTouchEvent(event);
605         int action = event.getAction();
606         action = action & MotionEvent.ACTION_MASK;
607
608         mGestureDetector.onTouchEvent(event);
609         boolean scaleInProgress = scaleInProgress();
610         mScaleGestureDetector.onTouchEvent(event);
611         if (mInteractionMode == InteractionMode.SCALE) {
612             return true;
613         }
614         if (!scaleInProgress() && scaleInProgress) {
615             // If we were scaling, the scale will stop but we will
616             // still issue an ACTION_UP. Let the subclasses know.
617             mFinishedScalingOperation = true;
618         }
619
620         int ex = (int) event.getX();
621         int ey = (int) event.getY();
622         if (action == MotionEvent.ACTION_DOWN) {
623             mInteractionMode = InteractionMode.MOVE;
624             mTouchDown.x = ex;
625             mTouchDown.y = ey;
626             mTouchShowOriginalDate = System.currentTimeMillis();
627             mShowOriginalDirection = 0;
628             MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
629         }
630
631         if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
632             mTouch.x = ex;
633             mTouch.y = ey;
634
635             float scaleFactor = MasterImage.getImage().getScaleFactor();
636             if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) {
637                 float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
638                 float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
639                 Point originalTranslation = MasterImage.getImage().getOriginalTranslation();
640                 Point translation = MasterImage.getImage().getTranslation();
641                 translation.x = (int) (originalTranslation.x + translateX);
642                 translation.y = (int) (originalTranslation.y + translateY);
643                 MasterImage.getImage().setTranslation(translation);
644                 mTouchShowOriginal = false;
645             } else if (enableComparison() && !mOriginalDisabled
646                     && (System.currentTimeMillis() - mTouchShowOriginalDate
647                             > mTouchShowOriginalDelayMin)
648                     && event.getPointerCount() == 1) {
649                 mTouchShowOriginal = true;
650             }
651         }
652
653         if (action == MotionEvent.ACTION_UP
654                 || action == MotionEvent.ACTION_CANCEL
655                 || action == MotionEvent.ACTION_OUTSIDE) {
656             mInteractionMode = InteractionMode.NONE;
657             mTouchShowOriginal = false;
658             mTouchDown.x = 0;
659             mTouchDown.y = 0;
660             mTouch.x = 0;
661             mTouch.y = 0;
662             if (MasterImage.getImage().getScaleFactor() <= 1) {
663                 MasterImage.getImage().setScaleFactor(1);
664                 MasterImage.getImage().resetTranslation();
665             }
666         }
667
668         float scaleFactor = MasterImage.getImage().getScaleFactor();
669         Point translation = MasterImage.getImage().getTranslation();
670         constrainTranslation(translation, scaleFactor);
671         MasterImage.getImage().setTranslation(translation);
672
673         invalidate();
674         return true;
675     }
676
677     private void startAnimTranslation(int fromX, int toX,
678                                       int fromY, int toY, int delay) {
679         if (fromX == toX && fromY == toY) {
680             return;
681         }
682         if (mAnimatorTranslateX != null) {
683             mAnimatorTranslateX.cancel();
684         }
685         if (mAnimatorTranslateY != null) {
686             mAnimatorTranslateY.cancel();
687         }
688         mAnimatorTranslateX = ValueAnimator.ofInt(fromX, toX);
689         mAnimatorTranslateY = ValueAnimator.ofInt(fromY, toY);
690         mAnimatorTranslateX.setDuration(delay);
691         mAnimatorTranslateY.setDuration(delay);
692         mAnimatorTranslateX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
693             @Override
694             public void onAnimationUpdate(ValueAnimator animation) {
695                 Point translation = MasterImage.getImage().getTranslation();
696                 translation.x = (Integer) animation.getAnimatedValue();
697                 MasterImage.getImage().setTranslation(translation);
698                 invalidate();
699             }
700         });
701         mAnimatorTranslateY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
702             @Override
703             public void onAnimationUpdate(ValueAnimator animation) {
704                 Point translation = MasterImage.getImage().getTranslation();
705                 translation.y = (Integer) animation.getAnimatedValue();
706                 MasterImage.getImage().setTranslation(translation);
707                 invalidate();
708             }
709         });
710         mAnimatorTranslateX.start();
711         mAnimatorTranslateY.start();
712     }
713
714     private void applyTranslationConstraints() {
715         float scaleFactor = MasterImage.getImage().getScaleFactor();
716         Point translation = MasterImage.getImage().getTranslation();
717         int x = translation.x;
718         int y = translation.y;
719         constrainTranslation(translation, scaleFactor);
720
721         if (x != translation.x || y != translation.y) {
722             startAnimTranslation(x, translation.x,
723                                  y, translation.y,
724                                  mAnimationSnapDelay);
725         }
726     }
727
728     protected boolean enableComparison() {
729         return true;
730     }
731
732     @Override
733     public boolean onDoubleTap(MotionEvent arg0) {
734         mZoomIn = !mZoomIn;
735         float scale = 1.0f;
736         final float x = arg0.getX();
737         final float y = arg0.getY();
738         if (mZoomIn) {
739             scale = MasterImage.getImage().getMaxScaleFactor();
740         }
741         if (scale != MasterImage.getImage().getScaleFactor()) {
742             if (mAnimatorScale != null) {
743                 mAnimatorScale.cancel();
744             }
745             mAnimatorScale = ValueAnimator.ofFloat(
746                     MasterImage.getImage().getScaleFactor(),
747                     scale
748             );
749             float translateX = (getWidth() / 2 - x);
750             float translateY = (getHeight() / 2 - y);
751             Point translation = MasterImage.getImage().getTranslation();
752             int startTranslateX = translation.x;
753             int startTranslateY = translation.y;
754             if (scale != 1.0f) {
755                 translation.x = (int) (mOriginalTranslation.x + translateX);
756                 translation.y = (int) (mOriginalTranslation.y + translateY);
757             } else {
758                 translation.x = 0;
759                 translation.y = 0;
760             }
761             constrainTranslation(translation, scale);
762
763             startAnimTranslation(startTranslateX, translation.x,
764                                  startTranslateY, translation.y,
765                                  mAnimationZoomDelay);
766             mAnimatorScale.setDuration(mAnimationZoomDelay);
767             mAnimatorScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
768                 @Override
769                 public void onAnimationUpdate(ValueAnimator animation) {
770                     MasterImage.getImage().setScaleFactor((Float) animation.getAnimatedValue());
771                     invalidate();
772                 }
773             });
774             mAnimatorScale.addListener(new Animator.AnimatorListener() {
775                 @Override
776                 public void onAnimationStart(Animator animation) {
777                 }
778
779                 @Override
780                 public void onAnimationEnd(Animator animation) {
781                     applyTranslationConstraints();
782                     MasterImage.getImage().needsUpdatePartialPreview();
783                     invalidate();
784                 }
785
786                 @Override
787                 public void onAnimationCancel(Animator animation) {
788
789                 }
790
791                 @Override
792                 public void onAnimationRepeat(Animator animation) {
793
794                 }
795             });
796             mAnimatorScale.start();
797         }
798         return true;
799     }
800
801     private void constrainTranslation(Point translation, float scale) {
802         int currentEdgeEffect = 0;
803         if (scale <= 1) {
804             mCurrentEdgeEffect = 0;
805             mEdgeEffect.finish();
806             return;
807         }
808
809         Matrix originalToScreen = MasterImage.getImage().originalImageToScreen();
810         Rect originalBounds = MasterImage.getImage().getOriginalBounds();
811         RectF screenPos = new RectF(originalBounds);
812         originalToScreen.mapRect(screenPos);
813
814         boolean rightConstraint = screenPos.right < getWidth() - mShadowMargin;
815         boolean leftConstraint = screenPos.left > mShadowMargin;
816         boolean topConstraint = screenPos.top > mShadowMargin;
817         boolean bottomConstraint = screenPos.bottom < getHeight() - mShadowMargin;
818
819         if (screenPos.width() > getWidth()) {
820             if (rightConstraint && !leftConstraint) {
821                 float tx = screenPos.right - translation.x * scale;
822                 translation.x = (int) ((getWidth() - mShadowMargin - tx) / scale);
823                 currentEdgeEffect = EDGE_RIGHT;
824             } else if (leftConstraint && !rightConstraint) {
825                 float tx = screenPos.left - translation.x * scale;
826                 translation.x = (int) ((mShadowMargin - tx) / scale);
827                 currentEdgeEffect = EDGE_LEFT;
828             }
829         } else {
830             float tx = screenPos.right - translation.x * scale;
831             float dx = (getWidth() - 2 * mShadowMargin - screenPos.width()) / 2f;
832             translation.x = (int) ((getWidth() - mShadowMargin - tx - dx) / scale);
833         }
834
835         if (screenPos.height() > getHeight()) {
836             if (bottomConstraint && !topConstraint) {
837                 float ty = screenPos.bottom - translation.y * scale;
838                 translation.y = (int) ((getHeight() - mShadowMargin - ty) / scale);
839                 currentEdgeEffect = EDGE_BOTTOM;
840             } else if (topConstraint && !bottomConstraint) {
841                 float ty = screenPos.top - translation.y * scale;
842                 translation.y = (int) ((mShadowMargin - ty) / scale);
843                 currentEdgeEffect = EDGE_TOP;
844             }
845         } else {
846             float ty = screenPos.bottom - translation.y * scale;
847             float dy = (getHeight()- 2 * mShadowMargin - screenPos.height()) / 2f;
848             translation.y = (int) ((getHeight() - mShadowMargin - ty - dy) / scale);
849         }
850
851         if (mCurrentEdgeEffect != currentEdgeEffect) {
852             if (mCurrentEdgeEffect == 0 || currentEdgeEffect != 0) {
853                 mCurrentEdgeEffect = currentEdgeEffect;
854                 mEdgeEffect.finish();
855             }
856             mEdgeEffect.setSize(getWidth(), mEdgeSize);
857         }
858         if (currentEdgeEffect != 0) {
859             mEdgeEffect.onPull(mEdgeSize);
860         }
861     }
862
863     @Override
864     public boolean onDoubleTapEvent(MotionEvent arg0) {
865         return false;
866     }
867
868     @Override
869     public boolean onSingleTapConfirmed(MotionEvent arg0) {
870         return false;
871     }
872
873     @Override
874     public boolean onDown(MotionEvent arg0) {
875         return false;
876     }
877
878     @Override
879     public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
880         if (mActivity == null) {
881             return false;
882         }
883         if (endEvent.getPointerCount() == 2) {
884             return false;
885         }
886         return true;
887     }
888
889     @Override
890     public void onLongPress(MotionEvent arg0) {
891     }
892
893     @Override
894     public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
895         return false;
896     }
897
898     @Override
899     public void onShowPress(MotionEvent arg0) {
900     }
901
902     @Override
903     public boolean onSingleTapUp(MotionEvent arg0) {
904         return false;
905     }
906
907     public boolean useUtilityPanel() {
908         return false;
909     }
910
911     public void openUtilityPanel(final LinearLayout accessoryViewList) {
912     }
913
914     @Override
915     public boolean onScale(ScaleGestureDetector detector) {
916         MasterImage img = MasterImage.getImage();
917         float scaleFactor = img.getScaleFactor();
918
919         scaleFactor = scaleFactor * detector.getScaleFactor();
920         if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
921             scaleFactor = MasterImage.getImage().getMaxScaleFactor();
922         }
923         if (scaleFactor < 1.0f) {
924             scaleFactor = 1.0f;
925         }
926         MasterImage.getImage().setScaleFactor(scaleFactor);
927         scaleFactor = img.getScaleFactor();
928         float focusx = detector.getFocusX();
929         float focusy = detector.getFocusY();
930         float translateX = (focusx - mStartFocusX) / scaleFactor;
931         float translateY = (focusy - mStartFocusY) / scaleFactor;
932         Point translation = MasterImage.getImage().getTranslation();
933         translation.x = (int) (mOriginalTranslation.x + translateX);
934         translation.y = (int) (mOriginalTranslation.y + translateY);
935         MasterImage.getImage().setTranslation(translation);
936         invalidate();
937         return true;
938     }
939
940     @Override
941     public boolean onScaleBegin(ScaleGestureDetector detector) {
942         Point pos = MasterImage.getImage().getTranslation();
943         mOriginalTranslation.x = pos.x;
944         mOriginalTranslation.y = pos.y;
945         mOriginalScale = MasterImage.getImage().getScaleFactor();
946         mStartFocusX = detector.getFocusX();
947         mStartFocusY = detector.getFocusY();
948         mInteractionMode = InteractionMode.SCALE;
949         return true;
950     }
951
952     @Override
953     public void onScaleEnd(ScaleGestureDetector detector) {
954         mInteractionMode = InteractionMode.NONE;
955         if (MasterImage.getImage().getScaleFactor() < 1) {
956             MasterImage.getImage().setScaleFactor(1);
957             invalidate();
958         }
959     }
960
961     public boolean didFinishScalingOperation() {
962         if (mFinishedScalingOperation) {
963             mFinishedScalingOperation = false;
964             return true;
965         }
966         return false;
967     }
968
969 }