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